am 1347eb70: am e4279ccb: am ed2852cf: am a6ca91d9: am f1f502b5: Merge "Skip CTS tests if zen_mode or airplane setting is not set or if voice intents are not supported by the platform. This can happen on certain platforms (fugu, for example) or on devices (zen_mode ma

* commit '1347eb705f1a82fa5e7f53873fc1038f5ff984f8':
  Skip CTS tests if zen_mode or airplane setting is not set or if voice intents are not supported by the platform. This can happen on certain platforms (fugu, for example) or on devices (zen_mode may not be in Settings, for example).
diff --git a/CtsTestCaseList.mk b/CtsTestCaseList.mk
index 41f9dc3..c6ae86c 100644
--- a/CtsTestCaseList.mk
+++ b/CtsTestCaseList.mk
@@ -72,7 +72,6 @@
     CtsUnaffiliatedAccountAuthenticators
 
 cts_support_packages := \
-    CtsAccelerationTestStubs \
     CtsAlarmClockService \
     CtsAppTestStubs \
     CtsAssistService \
@@ -171,7 +170,6 @@
     CtsLocationTestCases \
     CtsLocation2TestCases \
     CtsMediaStressTestCases \
-    CtsMediaTestCases \
     CtsMidiTestCases \
     CtsNativeOpenGLTestCases \
     CtsNdefTestCases \
diff --git a/apps/CtsVerifier/Android.mk b/apps/CtsVerifier/Android.mk
index 8cec7ea..f1e5774 100644
--- a/apps/CtsVerifier/Android.mk
+++ b/apps/CtsVerifier/Android.mk
@@ -27,7 +27,7 @@
 
 LOCAL_STATIC_JAVA_LIBRARIES := android-ex-camera2 \
                                android-support-v4 \
-                               compatibility-common-util-devicesidelib_v2 \
+                               compatibility-common-util-devicesidelib \
                                cts-sensors-tests \
                                ctstestrunner \
                                apache-commons-math \
@@ -37,11 +37,11 @@
                                android-support-v4  \
                                mockito-target \
                                mockwebserver \
-                               compatibility-device-util_v2 \
+                               compatibility-device-util \
 
 LOCAL_PACKAGE_NAME := CtsVerifier
 
-LOCAL_AAPT_FLAGS += --version-name "6.0_r0 $(BUILD_NUMBER)"
+LOCAL_AAPT_FLAGS += --version-name "5.0_r1.91 $(BUILD_NUMBER)"
 
 LOCAL_JNI_SHARED_LIBRARIES := libctsverifier_jni libaudioloopback_jni
 
diff --git a/apps/CtsVerifier/AndroidManifest.xml b/apps/CtsVerifier/AndroidManifest.xml
index d0d5ff0..9316dfc 100644
--- a/apps/CtsVerifier/AndroidManifest.xml
+++ b/apps/CtsVerifier/AndroidManifest.xml
@@ -19,7 +19,7 @@
       package="com.android.cts.verifier"
       android:versionCode="5">
 
-    <uses-sdk android:minSdkVersion="19" android:targetSdkVersion="23"/>
+    <uses-sdk android:minSdkVersion="19" android:targetSdkVersion="21"/>
 
     <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/TestResultsReport.java b/apps/CtsVerifier/src/com/android/cts/verifier/TestResultsReport.java
index 05c5e77..ff6bc76 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/TestResultsReport.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/TestResultsReport.java
@@ -16,17 +16,17 @@
 
 package com.android.cts.verifier;
 
+import android.content.Context;
+import android.os.Build;
+import android.text.TextUtils;
+import android.util.Xml;
+
 import com.android.compatibility.common.util.MetricsXmlSerializer;
 import com.android.compatibility.common.util.ReportLog;
 import com.android.cts.verifier.TestListAdapter.TestListItem;
 
 import org.xmlpull.v1.XmlSerializer;
 
-import android.content.Context;
-import android.os.Build;
-import android.text.TextUtils;
-import android.util.Xml;
-
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.text.DateFormat;
@@ -131,6 +131,7 @@
                     xml.endTag(null, TEST_DETAILS_TAG);
                 }
 
+                // TODO(stuartscott): For v2: ReportLog.serialize(xml, mAdapter.getReportLog(i));
                 ReportLog reportLog = mAdapter.getReportLog(i);
                 if (reportLog != null) {
                     MetricsXmlSerializer metricsXmlSerializer = new MetricsXmlSerializer(xml);
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/sample/SampleTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/sample/SampleTestActivity.java
index 41bc303..678aeca 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/sample/SampleTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/sample/SampleTestActivity.java
@@ -77,14 +77,15 @@
         double[] metricValues = new double[] {1, 11, 21, 1211, 111221};
 
         // Record metric results
-        getReportLog().setSummary(
-                "Sample Summary", 1.0, ResultType.HIGHER_BETTER, ResultUnit.BYTE);
-        getReportLog().addValues("Sample Values", metricValues, ResultType.NEUTRAL, ResultUnit.FPS);
+        getReportLog().setSummary("Sample Summary", 1.0, ResultType.HIGHER_BETTER,
+                ResultUnit.BYTE);
+        getReportLog().addValues("Sample Values", metricValues, ResultType.NEUTRAL,
+                ResultUnit.FPS);
 
         // Alternatively, activities can invoke TestResult directly to record metrics
         ReportLog reportLog = new PassFailButtons.CtsVerifierReportLog();
         reportLog.setSummary("Sample Summary", 1.0, ResultType.HIGHER_BETTER, ResultUnit.BYTE);
-        getReportLog().addValues("Sample Values", metricValues, ResultType.NEUTRAL, ResultUnit.FPS);
+        reportLog.addValues("Sample Values", metricValues, ResultType.NEUTRAL, ResultUnit.FPS);
         TestResult.setPassedResult(this, "manualSample", "manualDetails", reportLog);
     }
 
diff --git a/build/compatibility_test_suite.mk b/build/compatibility_test_suite.mk
new file mode 100644
index 0000000..6430efa
--- /dev/null
+++ b/build/compatibility_test_suite.mk
@@ -0,0 +1,51 @@
+# 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.
+
+#
+# Builds a compatibility test suite.
+#
+
+# Generate the SuiteInfo.java
+suite_info_java := $(call intermediates-dir-for,JAVA_LIBRARIES,$(LOCAL_MODULE),true,COMMON)/com/android/compatibility/SuiteInfo.java
+$(suite_info_java): PRIVATE_SUITE_BUILD_NUMBER := $(LOCAL_SUITE_BUILD_NUMBER)
+$(suite_info_java): PRIVATE_SUITE_NAME := $(LOCAL_SUITE_NAME)
+$(suite_info_java): PRIVATE_SUITE_FULLNAME := $(LOCAL_SUITE_FULLNAME)
+$(suite_info_java): PRIVATE_SUITE_VERSION := $(LOCAL_SUITE_VERSION)
+$(suite_info_java): cts/build/compatibility_test_suite.mk $(LOCAL_MODULE_MAKEFILE)
+	@echo Generating: $@
+	$(hide) mkdir -p $(dir $@)
+	$(hide) echo "/* This file is auto generated by Android.mk.  Do not modify. */" > $@
+	$(hide) echo "package com.android.compatibility;" >> $@
+	$(hide) echo "public class SuiteInfo {" >> $@
+	$(hide) echo "    public static final String BUILD_NUMBER = \"$(PRIVATE_SUITE_BUILD_NUMBER)\";" >> $@
+	$(hide) echo "    public static final String NAME = \"$(PRIVATE_SUITE_NAME)\";" >> $@
+	$(hide) echo "    public static final String FULLNAME = \"$(PRIVATE_SUITE_FULLNAME)\";" >> $@
+	$(hide) echo "    public static final String VERSION = \"$(PRIVATE_SUITE_VERSION)\";" >> $@
+	$(hide) echo "}" >> $@
+
+# Reset variables
+LOCAL_SUITE_BUILD_NUMBER :=
+LOCAL_SUITE_NAME :=
+LOCAL_SUITE_FULLNAME :=
+LOCAL_SUITE_VERSION :=
+
+# Include the SuiteInfo.java
+LOCAL_GENERATED_SOURCES := $(suite_info_java)
+
+# Add the base libraries
+LOCAL_JAVA_LIBRARIES += tradefed-prebuilt hosttestlib compatibility-host-util
+
+LOCAL_MODULE_TAGS := optional
+
+include $(BUILD_HOST_JAVA_LIBRARY)
diff --git a/build/config.mk b/build/config.mk
index 56d4ae6..eae7c00 100644
--- a/build/config.mk
+++ b/build/config.mk
@@ -16,6 +16,8 @@
 # directory before creating the final CTS distribution.
 CTS_TESTCASES_OUT := $(HOST_OUT)/cts/android-cts/repository/testcases
 
+COMPATIBILITY_TESTCASES_OUT_cts_v2 := $(HOST_OUT)/cts_v2/android-cts_v2/testcases
+
 # Scanners of source files for tests which are then inputed into
 # the XML generator to produce test XMLs.
 CTS_NATIVE_TEST_SCANNER := $(HOST_OUT_EXECUTABLES)/cts-native-scanner
@@ -38,6 +40,7 @@
 CTS_MODULE_TEST_CONFIG := AndroidTest.xml
 
 # CTS build rules
+BUILD_COMPATIBILITY_SUITE := cts/build/compatibility_test_suite.mk
 BUILD_CTS_EXECUTABLE := cts/build/test_executable.mk
 BUILD_CTS_PACKAGE := cts/build/test_package.mk
 BUILD_CTS_GTEST_PACKAGE := cts/build/test_gtest_package.mk
diff --git a/build/module_test_config.mk b/build/module_test_config.mk
index 6584ef2..fec4893 100644
--- a/build/module_test_config.mk
+++ b/build/module_test_config.mk
@@ -12,10 +12,10 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-cts_module_test_config := $(if $(wildcard \
-	$(LOCAL_PATH)/$(CTS_MODULE_TEST_CONFIG)), \
-	$(CTS_TESTCASES_OUT)/$(LOCAL_MODULE).config)
-ifneq ($(cts_module_test_config),)
-$(cts_module_test_config): $(LOCAL_PATH)/$(CTS_MODULE_TEST_CONFIG) | $(ACP)
+ifneq ($(LOCAL_CTS_MODULE_CONFIG),)
+cts_module_test_config := $(CTS_TESTCASES_OUT)/$(LOCAL_MODULE).config
+$(cts_module_test_config): $(LOCAL_CTS_MODULE_CONFIG) | $(ACP)
 	$(call copy-file-to-target)
 endif
+# clear var
+LOCAL_CTS_MODULE_CONFIG :=
diff --git a/common/device-side/device-info/src/com/android/compatibility/common/deviceinfo/DeviceInfoActivity.java b/common/device-side/device-info/src/com/android/compatibility/common/deviceinfo/DeviceInfoActivity.java
index f9de6eb..ed2587a 100644
--- a/common/device-side/device-info/src/com/android/compatibility/common/deviceinfo/DeviceInfoActivity.java
+++ b/common/device-side/device-info/src/com/android/compatibility/common/deviceinfo/DeviceInfoActivity.java
@@ -447,4 +447,3 @@
         return value;
     }
 }
-
diff --git a/common/device-side/device-info/src/com/android/compatibility/common/deviceinfo/DeviceInfoInstrument.java b/common/device-side/device-info/src/com/android/compatibility/common/deviceinfo/DeviceInfoInstrument.java
index 2f80911..655e627 100644
--- a/common/device-side/device-info/src/com/android/compatibility/common/deviceinfo/DeviceInfoInstrument.java
+++ b/common/device-side/device-info/src/com/android/compatibility/common/deviceinfo/DeviceInfoInstrument.java
@@ -124,10 +124,12 @@
         String errorMessage = activity.getErrorMessage();
         if (TextUtils.isEmpty(errorMessage)) {
             mBundle.putString(className, activity.getResultFilePath());
+            if (activity instanceof GenericDeviceInfo) {
+                ((GenericDeviceInfo) activity).putDeviceInfo(mBundle);
+            }
         } else {
             mBundle.putString(className, errorMessage);
             throw new Exception(errorMessage);
         }
     }
 }
-
diff --git a/common/device-side/device-info/src/com/android/compatibility/common/deviceinfo/GenericDeviceInfo.java b/common/device-side/device-info/src/com/android/compatibility/common/deviceinfo/GenericDeviceInfo.java
index f6e7c57..44c0b5f 100644
--- a/common/device-side/device-info/src/com/android/compatibility/common/deviceinfo/GenericDeviceInfo.java
+++ b/common/device-side/device-info/src/com/android/compatibility/common/deviceinfo/GenericDeviceInfo.java
@@ -40,7 +40,10 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashSet;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
 import java.util.Scanner;
 import java.util.Set;
 
@@ -51,6 +54,7 @@
  */
 public class GenericDeviceInfo extends DeviceInfoActivity {
 
+    public static final String DEVICE_INFO = "DEVICE_INFO_%s";
     public static final String BUILD_ID = "build_id";
     public static final String BUILD_PRODUCT = "build_product";
     public static final String BUILD_DEVICE = "build_device";
@@ -69,6 +73,8 @@
     public static final String BUILD_VERSION_RELEASE = "build_version_release";
     public static final String BUILD_VERSION_SDK = "build_version_sdk";
 
+    private final Map<String, String> mDeviceInfo = new HashMap<>();
+
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
@@ -76,22 +82,33 @@
 
     @Override
     protected void collectDeviceInfo() {
-        addResult(BUILD_ID, Build.ID);
-        addResult(BUILD_PRODUCT, Build.PRODUCT);
-        addResult(BUILD_DEVICE, Build.DEVICE);
-        addResult(BUILD_BOARD, Build.BOARD);
-        addResult(BUILD_MANUFACTURER, Build.MANUFACTURER);
-        addResult(BUILD_BRAND, Build.BRAND);
-        addResult(BUILD_MODEL, Build.MODEL);
-        addResult(BUILD_TYPE, Build.TYPE);
-        addResult(BUILD_FINGERPRINT, Build.FINGERPRINT);
-        addResult(BUILD_ABI, Build.CPU_ABI);
-        addResult(BUILD_ABI2, Build.CPU_ABI2);
-        addResult(BUILD_ABIS, TextUtils.join(",", Build.SUPPORTED_ABIS));
-        addResult(BUILD_ABIS_32, TextUtils.join(",", Build.SUPPORTED_32_BIT_ABIS));
-        addResult(BUILD_ABIS_64, TextUtils.join(",", Build.SUPPORTED_64_BIT_ABIS));
-        addResult(BUILD_SERIAL, Build.SERIAL);
-        addResult(BUILD_VERSION_RELEASE, Build.VERSION.RELEASE);
-        addResult(BUILD_VERSION_SDK, Build.VERSION.SDK);
+        addDeviceInfo(BUILD_ID, Build.ID);
+        addDeviceInfo(BUILD_PRODUCT, Build.PRODUCT);
+        addDeviceInfo(BUILD_DEVICE, Build.DEVICE);
+        addDeviceInfo(BUILD_BOARD, Build.BOARD);
+        addDeviceInfo(BUILD_MANUFACTURER, Build.MANUFACTURER);
+        addDeviceInfo(BUILD_BRAND, Build.BRAND);
+        addDeviceInfo(BUILD_MODEL, Build.MODEL);
+        addDeviceInfo(BUILD_TYPE, Build.TYPE);
+        addDeviceInfo(BUILD_FINGERPRINT, Build.FINGERPRINT);
+        addDeviceInfo(BUILD_ABI, Build.CPU_ABI);
+        addDeviceInfo(BUILD_ABI2, Build.CPU_ABI2);
+        addDeviceInfo(BUILD_ABIS, TextUtils.join(",", Build.SUPPORTED_ABIS));
+        addDeviceInfo(BUILD_ABIS_32, TextUtils.join(",", Build.SUPPORTED_32_BIT_ABIS));
+        addDeviceInfo(BUILD_ABIS_64, TextUtils.join(",", Build.SUPPORTED_64_BIT_ABIS));
+        addDeviceInfo(BUILD_SERIAL, Build.SERIAL);
+        addDeviceInfo(BUILD_VERSION_RELEASE, Build.VERSION.RELEASE);
+        addDeviceInfo(BUILD_VERSION_SDK, Build.VERSION.SDK);
     }
-}
\ No newline at end of file
+
+    private void addDeviceInfo(String key, String value) {
+        mDeviceInfo.put(key, value);
+        addResult(key, value);
+    }
+
+    protected void putDeviceInfo(Bundle bundle) {
+        for (Entry<String, String> entry : mDeviceInfo.entrySet()) {
+            bundle.putString(String.format(DEVICE_INFO, entry.getKey()), entry.getValue());
+        }
+    }
+}
diff --git a/common/device-side/device-info/tests/Android.mk b/common/device-side/device-info/tests/Android.mk
index e24c5c1..a8d9e038 100644
--- a/common/device-side/device-info/tests/Android.mk
+++ b/common/device-side/device-info/tests/Android.mk
@@ -27,4 +27,3 @@
 LOCAL_MODULE := compatibility-device-info-tests
 
 include $(BUILD_STATIC_JAVA_LIBRARY)
-
diff --git a/common/device-side/device-info/tests/src/com/android/compatibility/common/deviceinfo/DeviceInfoActivityTest.java b/common/device-side/device-info/tests/src/com/android/compatibility/common/deviceinfo/DeviceInfoActivityTest.java
index d092f4e..5b834c7 100644
--- a/common/device-side/device-info/tests/src/com/android/compatibility/common/deviceinfo/DeviceInfoActivityTest.java
+++ b/common/device-side/device-info/tests/src/com/android/compatibility/common/deviceinfo/DeviceInfoActivityTest.java
@@ -52,6 +52,9 @@
     }
 
     public void testJsonFile() throws IOException {
+        String errorMessage = mActivity.getErrorMessage();
+        // Check no errors
+        assertEquals("Expected no errors", null, errorMessage);
         String resultFilePath = mActivity.getResultFilePath();
         // Check file path exist
         assertNotNull("Expected a non-null resultFilePath", resultFilePath);
@@ -75,5 +78,4 @@
         bufferedReader.close();
         return stringBuilder.toString();
     }
-}
-
+}
\ No newline at end of file
diff --git a/common/device-side/device-info/tests/src/com/android/compatibility/common/deviceinfo/SampleDeviceInfo.java b/common/device-side/device-info/tests/src/com/android/compatibility/common/deviceinfo/SampleDeviceInfo.java
deleted file mode 100644
index 7da9951..0000000
--- a/common/device-side/device-info/tests/src/com/android/compatibility/common/deviceinfo/SampleDeviceInfo.java
+++ /dev/null
@@ -1,73 +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.deviceinfo;
-
-import android.os.Bundle;
-
-import java.lang.StringBuilder;
-
-/**
- * Sample device info collector.
- */
-public class SampleDeviceInfo extends DeviceInfoActivity {
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-    }
-
-    @Override
-    protected void collectDeviceInfo() {
-        boolean[] booleans = {Boolean.TRUE, Boolean.FALSE};
-        double[] doubles = {Double.MAX_VALUE, Double.MIN_VALUE};
-        int[] ints = {Integer.MAX_VALUE, Integer.MIN_VALUE};
-        long[] longs = {Long.MAX_VALUE, Long.MIN_VALUE};
-
-        // Group Foo
-        startGroup("foo");
-        addResult("foo_boolean", Boolean.TRUE);
-
-        // Group Bar
-        startGroup("bar");
-        addArray("bar_string", new String[] {
-                "bar-string-1",
-                "bar-string-2",
-                "bar-string-3"});
-
-        addArray("bar_boolean", booleans);
-        addArray("bar_double", doubles);
-        addArray("bar_int", ints);
-        addArray("bar_long", longs);
-        endGroup(); // bar
-
-        addResult("foo_double", Double.MAX_VALUE);
-        addResult("foo_int", Integer.MAX_VALUE);
-        addResult("foo_long", Long.MAX_VALUE);
-        addResult("foo_string", "foo-string");
-
-        StringBuilder sb = new StringBuilder();
-        int[] arr = new int[1001];
-        for (int i = 0; i < 1001; i++) {
-            sb.append("a");
-            arr[i] = i;
-        }
-        addResult("long_string", sb.toString());
-        addArray("long_int_array", arr);
-
-        endGroup(); // foo
-    }
-}
-
diff --git a/common/device-side/device-setup/Android.mk b/common/device-side/device-setup/Android.mk
deleted file mode 100644
index bc7c504..0000000
--- a/common/device-side/device-setup/Android.mk
+++ /dev/null
@@ -1,43 +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.
-
-LOCAL_PATH:= $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_MODULE_TAGS := optional
-
-LOCAL_MODULE := compatibility-device-setup_v2
-
-LOCAL_SDK_VERSION := current
-
-include $(BUILD_STATIC_JAVA_LIBRARY)
-
-###############################################################################
-# Build the tests
-###############################################################################
-
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := $(call all-java-files-under, tests/src)
-
-LOCAL_JAVA_LIBRARIES := junit
-
-LOCAL_MODULE_TAGS := optional
-
-LOCAL_MODULE := compatibility-device-setup-tests_v2
-
-include $(BUILD_HOST_JAVA_LIBRARY)
diff --git a/common/device-side/device-setup/src/com/android/compatibility/common/devicesetup/DeviceInfoConstants.java b/common/device-side/device-setup/src/com/android/compatibility/common/devicesetup/DeviceInfoConstants.java
deleted file mode 100644
index 7b19b00..0000000
--- a/common/device-side/device-setup/src/com/android/compatibility/common/devicesetup/DeviceInfoConstants.java
+++ /dev/null
@@ -1,79 +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.
- */
-
-package com.android.compatibility.common.devicesetup;
-
-/**
- * Constants for device info attributes to be sent as instrumentation keys.
- */
-public interface DeviceInfoConstants {
-
-    public static final String BUILD_ABI = "buildAbi";
-    public static final String BUILD_ABI2 = "buildAbi2";
-    public static final String BUILD_BOARD = "buildBoard";
-    public static final String BUILD_BRAND = "buildBrand";
-    public static final String BUILD_DEVICE = "buildDevice";
-    public static final String BUILD_FINGERPRINT = "buildFingerprint";
-    public static final String BUILD_ID = "buildId";
-    public static final String BUILD_MANUFACTURER = "buildManufacturer";
-    public static final String BUILD_MODEL = "buildModel";
-    public static final String BUILD_TAGS = "buildTags";
-    public static final String BUILD_TYPE = "buildType";
-    public static final String BUILD_VERSION = "buildVersion";
-
-    public static final String FEATURES = "features";
-
-    public static final String GRAPHICS_RENDERER = "graphicsRenderer";
-    public static final String GRAPHICS_VENDOR = "graphicsVendor";
-
-    public static final String IMEI = "imei";
-    public static final String IMSI = "imsi";
-
-    public static final String KEYPAD = "keypad";
-
-    public static final String LOCALES = "locales";
-
-    public static final String MULTI_USER = "multiUser";
-
-    public static final String NAVIGATION = "navigation";
-    public static final String NETWORK = "network";
-
-    public static final String OPEN_GL_ES_VERSION = "openGlEsVersion";
-    public static final String OPEN_GL_EXTENSIONS = "openGlExtensions";
-    public static final String OPEN_GL_COMPRESSED_TEXTURE_FORMATS =
-            "openGlCompressedTextureFormats";
-
-    public static final String PARTITIONS = "partitions";
-    public static final String PHONE_NUMBER = "phoneNumber";
-    public static final String PROCESSES = "processes";
-    public static final String PRODUCT_NAME = "productName";
-
-    public static final String RESOLUTION = "resolution";
-
-    public static final String SCREEN_DENSITY = "screenDensity";
-    public static final String SCREEN_DENSITY_BUCKET = "screenDensityBucket";
-    public static final String SCREEN_DENSITY_X = "screenDensityX";
-    public static final String SCREEN_DENSITY_Y = "screenDensityY";
-    public static final String SCREEN_SIZE = "screenSize";
-    public static final String SERIAL_NUMBER = "deviceId";
-    public static final String STORAGE_DEVICES = "storageDevices";
-    public static final String SYS_LIBRARIES = "systemLibraries";
-
-    public static final String TOUCH = "touch";
-
-    public static final String VERSION_RELEASE = "versionRelease";
-    public static final String VERSION_SDK_INT = "versionSdkInt";
-}
diff --git a/common/device-side/device-setup/tests/src/com/android/compatibility/common/devicesetup/DeviceSetupTest.java b/common/device-side/device-setup/tests/src/com/android/compatibility/common/devicesetup/DeviceSetupTest.java
deleted file mode 100644
index ee55a66..0000000
--- a/common/device-side/device-setup/tests/src/com/android/compatibility/common/devicesetup/DeviceSetupTest.java
+++ /dev/null
@@ -1,25 +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.
- */
-
-package com.android.compatibility.common.devicesetup;
-
-import junit.framework.TestCase;
-
-public class DeviceSetupTest extends TestCase {
-
-    // TODO(stuartscott): Add tests when there is something to test.
-
-}
diff --git a/common/device-side/test-app/Android.mk b/common/device-side/test-app/Android.mk
new file mode 100755
index 0000000..6adce97
--- /dev/null
+++ b/common/device-side/test-app/Android.mk
@@ -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.
+
+# Build an APK which contains the device-side libraries and their tests,
+# this then gets instrumented in order to test the aforementioned libraries.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_DEX_PREOPT := false
+LOCAL_PROGUARD_ENABLED := disabled
+# don't include this package in any target
+LOCAL_MODULE_TAGS := optional
+# and when built explicitly put it in the data partition
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test\
+    compatibility-common-util-devicesidelib\
+    compatibility-device-info-tests\
+    compatibility-device-info\
+    compatibility-device-util-tests\
+    compatibility-device-util
+
+LOCAL_PACKAGE_NAME := CompatibilityTestApp
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_PACKAGE)
diff --git a/common/device-side/test-app/AndroidManifest.xml b/common/device-side/test-app/AndroidManifest.xml
new file mode 100755
index 0000000..9c857f0
--- /dev/null
+++ b/common/device-side/test-app/AndroidManifest.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.compatibility.common">
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+    <application>
+        <uses-library android:name="android.test.runner" />
+        <activity android:name="com.android.compatibility.common.deviceinfo.TestDeviceInfo" />
+    </application>
+
+    <!--  self-instrumenting test package. -->
+    <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.compatibility.common"
+                     android:label="Tests for device-side Compatibility common code">
+    </instrumentation>
+
+</manifest>
+
diff --git a/common/device-side/util/Android.mk b/common/device-side/util/Android.mk
index c8104bf..350c2db 100644
--- a/common/device-side/util/Android.mk
+++ b/common/device-side/util/Android.mk
@@ -12,34 +12,20 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-LOCAL_PATH:= $(call my-dir)
+LOCAL_PATH := $(call my-dir)
 
 include $(CLEAR_VARS)
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
-LOCAL_STATIC_JAVA_LIBRARIES := compatibility-common-util-devicesidelib_v2
+LOCAL_STATIC_JAVA_LIBRARIES := compatibility-common-util-devicesidelib
 
 LOCAL_MODULE_TAGS := optional
 
-LOCAL_MODULE := compatibility-device-util_v2
+LOCAL_MODULE := compatibility-device-util
 
 LOCAL_SDK_VERSION := current
 
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
-################################################################################
-# Build the tests
-###############################################################################
-
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := $(call all-java-files-under, tests/src)
-
-LOCAL_JAVA_LIBRARIES := junit
-
-LOCAL_MODULE_TAGS := optional
-
-LOCAL_MODULE := compatibility-device-util-tests_v2
-
-include $(BUILD_HOST_JAVA_LIBRARY)
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/DeviceReportLog.java b/common/device-side/util/src/com/android/compatibility/common/util/DeviceReportLog.java
index 273cdf5..d87ebbc 100644
--- a/common/device-side/util/src/com/android/compatibility/common/util/DeviceReportLog.java
+++ b/common/device-side/util/src/com/android/compatibility/common/util/DeviceReportLog.java
@@ -22,6 +22,10 @@
 
 import com.android.compatibility.common.util.ReportLog;
 
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+
 /**
  * Handles adding results to the report for device side tests.
  *
@@ -30,13 +34,20 @@
  */
 public class DeviceReportLog extends ReportLog {
     private static final String TAG = DeviceReportLog.class.getSimpleName();
-    private static final String RESULT = "RESULT";
+    private static final String RESULT = "COMPATIBILITY_TEST_RESULT";
+    private static final int INST_STATUS_ERROR = -1;
     private static final int INST_STATUS_IN_PROGRESS = 2;
 
     public void submit(Instrumentation instrumentation) {
-        Log.i(TAG, "submit");
-        Bundle output = new Bundle();
-        output.putSerializable(RESULT, this);
-        instrumentation.sendStatus(INST_STATUS_IN_PROGRESS, output);
+        Log.i(TAG, "Submit");
+        try {
+            Bundle output = new Bundle();
+            output.putString(RESULT, serialize(this));
+            instrumentation.sendStatus(INST_STATUS_IN_PROGRESS, output);
+        } catch (IllegalArgumentException | IllegalStateException | XmlPullParserException
+                | IOException e) {
+            Log.e(TAG, "Submit Failed", e);
+            instrumentation.sendStatus(INST_STATUS_ERROR, null);
+        }
     }
 }
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/DynamicConfigDeviceSide.java b/common/device-side/util/src/com/android/compatibility/common/util/DynamicConfigDeviceSide.java
new file mode 100644
index 0000000..303efec
--- /dev/null
+++ b/common/device-side/util/src/com/android/compatibility/common/util/DynamicConfigDeviceSide.java
@@ -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.
+ */
+
+package com.android.compatibility.common.util;
+
+import android.os.Environment;
+import android.util.Log;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * Load dynamic config for device side test cases
+ */
+public class DynamicConfigDeviceSide extends DynamicConfig {
+    private static String LOG_TAG = DynamicConfigDeviceSide.class.getSimpleName();
+
+    public DynamicConfigDeviceSide(String moduleName) throws XmlPullParserException, IOException {
+        if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
+            throw new IOException("External storage is not mounted");
+        }
+        File configFile = getConfigFile(new File(CONFIG_FOLDER_ON_DEVICE), moduleName);
+        initConfigFromXml(configFile);
+    }
+}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/EvaluateJsResultPollingCheck.java b/common/device-side/util/src/com/android/compatibility/common/util/EvaluateJsResultPollingCheck.java
index 521dc40..61f1bb8 100644
--- a/common/device-side/util/src/com/android/compatibility/common/util/EvaluateJsResultPollingCheck.java
+++ b/common/device-side/util/src/com/android/compatibility/common/util/EvaluateJsResultPollingCheck.java
@@ -18,7 +18,7 @@
 
 import android.webkit.ValueCallback;
 
-public class EvaluateJsResultPollingCheck  extends PollingCheck
+public class EvaluateJsResultPollingCheck extends PollingCheck
         implements ValueCallback<String> {
     private String mActualResult;
     private String mExpectedResult;
diff --git a/tests/tests/acceleration/Android.mk b/common/device-side/util/tests/Android.mk
similarity index 64%
copy from tests/tests/acceleration/Android.mk
copy to common/device-side/util/tests/Android.mk
index d417371..fa7424d 100644
--- a/tests/tests/acceleration/Android.mk
+++ b/common/device-side/util/tests/Android.mk
@@ -1,10 +1,10 @@
-# Copyright (C) 2011 The Android Open Source Project
+# 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
+# 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,
@@ -16,18 +16,12 @@
 
 include $(CLEAR_VARS)
 
-LOCAL_MODULE_TAGS := optional
-
-LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-
-LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner
-
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
-LOCAL_PACKAGE_NAME := CtsAccelerationTestCases
+LOCAL_STATIC_JAVA_LIBRARIES := compatibility-device-util
 
-LOCAL_INSTRUMENTATION_FOR := CtsAccelerationTestStubs
+LOCAL_MODULE_TAGS := optional
 
-LOCAL_SDK_VERSION := current
+LOCAL_MODULE := compatibility-device-util-tests
 
-include $(BUILD_CTS_PACKAGE)
+include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/common/device-side/util/tests/src/com/android/compatibility/common/util/DeviceReportTest.java b/common/device-side/util/tests/src/com/android/compatibility/common/util/DeviceReportTest.java
new file mode 100644
index 0000000..7dbece0
--- /dev/null
+++ b/common/device-side/util/tests/src/com/android/compatibility/common/util/DeviceReportTest.java
@@ -0,0 +1,67 @@
+/*
+ * 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.util;
+
+import android.app.Instrumentation;
+import android.os.Bundle;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests for {@line DeviceReportLog}.
+ */
+public class DeviceReportTest extends TestCase {
+
+    /**
+     * A stub of {@link Instrumentation}
+     */
+    public class TestInstrumentation extends Instrumentation {
+
+        private int mResultCode = -1;
+        private Bundle mResults = null;
+
+        @Override
+        public void sendStatus(int resultCode, Bundle results) {
+            mResultCode = resultCode;
+            mResults = results;
+        }
+    }
+
+    private static final int RESULT_CODE = 2;
+    private static final String RESULT_KEY = "COMPATIBILITY_TEST_RESULT";
+    private static final String TEST_MESSAGE_1 = "Foo";
+    private static final double TEST_VALUE_1 = 3;
+    private static final ResultType TEST_TYPE_1 = ResultType.HIGHER_BETTER;
+    private static final ResultUnit TEST_UNIT_1 = ResultUnit.SCORE;
+    private static final String TEST_MESSAGE_2 = "Bar";
+    private static final double TEST_VALUE_2 = 5;
+    private static final ResultType TEST_TYPE_2 = ResultType.LOWER_BETTER;
+    private static final ResultUnit TEST_UNIT_2 = ResultUnit.COUNT;
+
+    public void testSubmit() throws Exception {
+        DeviceReportLog log = new DeviceReportLog();
+        log.addValue(TEST_MESSAGE_1, TEST_VALUE_1, TEST_TYPE_1, TEST_UNIT_1);
+        log.setSummary(TEST_MESSAGE_2, TEST_VALUE_2, TEST_TYPE_2, TEST_UNIT_2);
+        TestInstrumentation inst = new TestInstrumentation();
+        log.submit(inst);
+        assertEquals("Incorrect result code", RESULT_CODE, inst.mResultCode);
+        assertNotNull("Bundle missing", inst.mResults);
+        String metrics = inst.mResults.getString(RESULT_KEY);
+        assertNotNull("Metrics missing", metrics);
+        ReportLog result = ReportLog.parse(metrics);
+        assertNotNull("Metrics could not be decoded", result);
+    }
+}
diff --git a/common/device-side/util/tests/src/com/android/compatibility/common/util/DeviceUtilTest.java b/common/device-side/util/tests/src/com/android/compatibility/common/util/DeviceUtilTest.java
deleted file mode 100644
index a7e81d7..0000000
--- a/common/device-side/util/tests/src/com/android/compatibility/common/util/DeviceUtilTest.java
+++ /dev/null
@@ -1,23 +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.
- */
-
-package com.android.compatibility.common.util;
-
-import junit.framework.TestCase;
-
-public class DeviceUtilTest extends TestCase {
-
-}
diff --git a/common/host-side/java-scanner/Android.mk b/common/host-side/java-scanner/Android.mk
deleted file mode 100644
index 7c101ff..0000000
--- a/common/host-side/java-scanner/Android.mk
+++ /dev/null
@@ -1,47 +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.
-
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_JAVA_LIBRARIES := compatibility-common-util-hostsidelib_v2
-
-LOCAL_JAR_MANIFEST := MANIFEST.mf
-
-LOCAL_CLASSPATH := $(HOST_JDK_TOOLS_JAR)
-
-LOCAL_MODULE := compatibility-java-scanner_v2
-
-LOCAL_MODULE_TAGS := optional
-
-include $(BUILD_HOST_JAVA_LIBRARY)
-
-################################################################################
-# Build the tests
-###############################################################################
-
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := $(call all-java-files-under, tests/src)
-
-LOCAL_JAVA_LIBRARIES := compatibility-tradefed_v2 compatibility-java-scanner_v2 junit
-
-LOCAL_MODULE := compatibility-java-scanner-tests_v2
-
-LOCAL_MODULE_TAGS := optional
-
-include $(BUILD_HOST_JAVA_LIBRARY)
diff --git a/common/host-side/java-scanner/MANIFEST.mf b/common/host-side/java-scanner/MANIFEST.mf
deleted file mode 100644
index 975b1ef..0000000
--- a/common/host-side/java-scanner/MANIFEST.mf
+++ /dev/null
@@ -1,3 +0,0 @@
-Manifest-Version: 1.0
-Main-Class: com.android.compatibility.common.scanner.JavaScanner
-Class-Path: compatibility-common-util-hostsidelib_v2.jar
diff --git a/common/host-side/java-scanner/src/com/android/compatibility/common/scanner/JavaScanner.java b/common/host-side/java-scanner/src/com/android/compatibility/common/scanner/JavaScanner.java
deleted file mode 100644
index f3f8a49..0000000
--- a/common/host-side/java-scanner/src/com/android/compatibility/common/scanner/JavaScanner.java
+++ /dev/null
@@ -1,148 +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.
- */
-
-package com.android.compatibility.common.scanner;
-
-import com.android.compatibility.common.util.KeyValueArgsParser;
-
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileFilter;
-import java.io.InputStreamReader;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
-
-/**
- * Scans a source directory for java tests and outputs a list of test classes and methods.
- */
-public class JavaScanner {
-
-    static final String[] SOURCE_PATHS = {
-        "./frameworks/base/core/java",
-        "./frameworks/base/test-runner/src",
-        "./external/junit/src",
-        "./development/tools/hosttestlib/src",
-        "./libcore/dalvik/src/main/java",
-        "./common/device-side/util/src",
-        "./common/host-side/tradefed/src",
-        "./common/util/src"
-    };
-    static final String[] CLASS_PATHS = {
-        "./prebuilts/misc/common/tradefed/tradefed-prebuilt.java",
-        "./prebuilts/misc/common/ub-uiautomator/ub-uiautomator.java"
-    };
-    private final File mSourceDir;
-    private final File mDocletDir;
-
-    /**
-     * @param sourceDir The directory holding the source to scan.
-     * @param docletDir The directory holding the doclet (or its jar).
-     */
-    JavaScanner(File sourceDir, File docletDir) {
-        this.mSourceDir = sourceDir;
-        this.mDocletDir = docletDir;
-    }
-
-    int scan() throws Exception {
-        final ArrayList<String> args = new ArrayList<String>();
-        args.add("javadoc");
-        args.add("-doclet");
-        args.add("com.android.compatibility.common.scanner.JavaScannerDoclet");
-        args.add("-sourcepath");
-        args.add(getSourcePath(mSourceDir));
-        args.add("-classpath");
-        args.add(getClassPath());
-        args.add("-docletpath");
-        args.add(mDocletDir.toString());
-        args.addAll(getSourceFiles(mSourceDir));
-
-        // Dont want p to get blocked due to a full pipe.
-        final Process p = new ProcessBuilder(args).redirectErrorStream(true).start();
-        final BufferedReader in = new BufferedReader(new InputStreamReader(p.getInputStream()));
-        try {
-            String line = null;
-            while ((line = in.readLine()) != null) {
-                if (line.startsWith("suite:") ||
-                    line.startsWith("case:") ||
-                    line.startsWith("test:")) {
-                    System.out.println(line);
-                }
-            }
-        } finally {
-          if (in != null) {
-              in.close();
-          }
-        }
-
-        return p.waitFor();
-    }
-
-    private static String getSourcePath(File sourceDir) {
-        final ArrayList<String> sourcePath = new ArrayList<String>(Arrays.asList(SOURCE_PATHS));
-        sourcePath.add(sourceDir.toString());
-        return join(sourcePath, ":");
-    }
-
-    private static String getClassPath() {
-        return join(Arrays.asList(CLASS_PATHS), ":");
-    }
-
-    private static ArrayList<String> getSourceFiles(File sourceDir) {
-        final ArrayList<String> sourceFiles = new ArrayList<String>();
-        final File[] files = sourceDir.listFiles(new FileFilter() {
-            public boolean accept(File pathname) {
-                return pathname.isDirectory() || pathname.toString().endsWith(".java");
-            }
-        });
-        for (File f : files) {
-            if (f.isDirectory()) {
-                sourceFiles.addAll(getSourceFiles(f));
-            } else {
-                sourceFiles.add(f.toString());
-            }
-        }
-        return sourceFiles;
-    }
-
-    private static String join(List<String> list, String delimiter) {
-        final StringBuilder builder = new StringBuilder();
-        for (String s : list) {
-            builder.append(s);
-            builder.append(delimiter);
-        }
-        // Adding the delimiter each time and then removing the last one at the end is more
-        // efficient than doing a check in each iteration of the loop.
-        return builder.substring(0, builder.length() - delimiter.length());
-    }
-
-    public static void main(String[] args) throws Exception {
-        final HashMap<String, String> argsMap = KeyValueArgsParser.parse(args);
-        final String sourcePath = argsMap.get("-s");
-        final String docletPath = argsMap.get("-d");
-        if (sourcePath == null || docletPath == null) {
-            usage(args);
-        }
-        System.exit(new JavaScanner(new File(sourcePath), new File(docletPath)).scan());
-    }
-
-    private static void usage(String[] args) {
-        System.err.println("Arguments: " + Arrays.toString(args));
-        System.err.println("Usage: javascanner -s SOURCE_DIR -d DOCLET_PATH");
-        System.exit(1);
-    }
-}
diff --git a/common/host-side/java-scanner/src/com/android/compatibility/common/scanner/JavaScannerDoclet.java b/common/host-side/java-scanner/src/com/android/compatibility/common/scanner/JavaScannerDoclet.java
deleted file mode 100644
index 94eccd0..0000000
--- a/common/host-side/java-scanner/src/com/android/compatibility/common/scanner/JavaScannerDoclet.java
+++ /dev/null
@@ -1,73 +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.
- */
-
-package com.android.compatibility.common.scanner;
-
-import com.sun.javadoc.ClassDoc;
-import com.sun.javadoc.Doclet;
-import com.sun.javadoc.MethodDoc;
-import com.sun.javadoc.RootDoc;
-
-import java.io.PrintWriter;
-
-/**
- * Doclet that scans java files looking for tests.
- *
- * Sample Ouput;
- * suite:com.android.sample.cts
- * case:SampleDeviceTest
- * test:testSharedPreferences
- */
-public class JavaScannerDoclet extends Doclet {
-
-    private static final String JUNIT_TEST_CASE_CLASS_NAME = "junit.framework.testcase";
-
-    public static boolean start(RootDoc root) {
-        ClassDoc[] classes = root.classes();
-        if (classes == null) {
-            return false;
-        }
-
-        PrintWriter writer = new PrintWriter(System.out);
-
-        for (ClassDoc clazz : classes) {
-            if (clazz.isAbstract() || !isValidJUnitTestCase(clazz)) {
-                continue;
-            }
-            writer.append("suite:").println(clazz.containingPackage().name());
-            writer.append("case:").println(clazz.name());
-            for (; clazz != null; clazz = clazz.superclass()) {
-                for (MethodDoc method : clazz.methods()) {
-                    if (method.name().startsWith("test")) {
-                        writer.append("test:").println(method.name());
-                    }
-                }
-            }
-        }
-
-        writer.close();
-        return true;
-    }
-
-    private static boolean isValidJUnitTestCase(ClassDoc clazz) {
-        while ((clazz = clazz.superclass()) != null) {
-            if (JUNIT_TEST_CASE_CLASS_NAME.equals(clazz.qualifiedName().toLowerCase())) {
-                return true;
-            }
-        }
-        return false;
-    }
-}
diff --git a/common/host-side/java-scanner/tests/src/com/android/compatibility/common/scanner/JavaScannerTest.java b/common/host-side/java-scanner/tests/src/com/android/compatibility/common/scanner/JavaScannerTest.java
deleted file mode 100644
index 4159f0e..0000000
--- a/common/host-side/java-scanner/tests/src/com/android/compatibility/common/scanner/JavaScannerTest.java
+++ /dev/null
@@ -1,133 +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.
- */
-
-package com.android.compatibility.common.scanner;
-
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.InputStreamReader;
-import java.io.PrintWriter;
-import java.util.ArrayList;
-
-import junit.framework.TestCase;
-
-public class JavaScannerTest extends TestCase {
-
-    private static final String JAR = "out/host/linux-x86/framework/compatibility-java-scanner_v2.jar";
-    private static final String VALID_RESULT =
-        "suite:com.android.test" +
-        "case:ValidTest" +
-        "test:testA";
-
-    private static final String VALID_FILENAME = "ValidTest";
-    private static final String VALID =
-        "package com.android.test;" +
-        "import junit.framework.TestCase;" +
-        "public class ValidTest extends TestCase {" +
-        "  public void testA() throws Exception {" +
-        "    helper();" +
-        "  }" +
-        "  public void helper() {" +
-        "    fail();" +
-        "  }" +
-        "}";
-
-    // TestCases must have TestCase in their hierarchy
-    private static final String INVALID_A_FILENAME = "NotTestCase";
-    private static final String INVALID_A =
-        "package com.android.test;" +
-        "public class NotTestCase {" +
-        "  public void testA() throws Exception {" +
-        "    helper();" +
-        "  }" +
-        "  public void helper() {" +
-        "    fail();" +
-        "  }" +
-        "}";
-
-    // TestCases cant be abstract classes
-    private static final String INVALID_B_FILENAME = "AbstractClass";
-    private static final String INVALID_B =
-        "package com.android.test;" +
-        "import junit.framework.TestCase;" +
-        "public abstract class AbstractClass extends TestCase {" +
-        "  public void testA() throws Exception {" +
-        "    helper();" +
-        "  }" +
-        "  public void helper() {" +
-        "    fail();" +
-        "  }" +
-        "}";
-
-    public void testValidFile() throws Exception {
-        String result = runScanner(VALID_FILENAME, VALID);
-        assertEquals(VALID_RESULT, result);
-    }
-
-    public void testInvalidFileA() throws Exception {
-        assertEquals("", runScanner(INVALID_A_FILENAME, INVALID_A));
-    }
-
-    public void testInvalidFileB() throws Exception {
-        assertEquals("", runScanner(INVALID_B_FILENAME, INVALID_B));
-    }
-
-    private static String runScanner(String filename, String content) throws Exception {
-        final File parent0 = new File(System.getProperty("java.io.tmpdir"));
-        final File parent1 = new File(parent0, "tmp" + System.currentTimeMillis());
-        final File parent2 = new File(parent1, "com");
-        final File parent3 = new File(parent2, "android");
-        final File parent4 = new File(parent3, "test");
-        File f = null;
-        try {
-            parent4.mkdirs();
-            f = new File(parent4, filename + ".java");
-            final PrintWriter out = new PrintWriter(f);
-            out.print(content);
-            out.flush();
-            out.close();
-            ArrayList<String> args = new ArrayList<String>();
-            args.add("java");
-            args.add("-jar");
-            args.add(JAR);
-            args.add("-s");
-            args.add(parent1.toString());
-            args.add("-d");
-            args.add(JAR);
-
-            final Process p = new ProcessBuilder(args).start();
-            final StringBuilder output = new StringBuilder();
-            final BufferedReader in = new BufferedReader(new InputStreamReader(p.getInputStream()));
-            String line = null;
-            while ((line = in.readLine()) != null) {
-                output.append(line);
-            }
-            int ret = p.waitFor();
-            if (ret == 0) {
-                return output.toString();
-            }
-        } finally {
-            if (f != null) {
-                f.delete();
-            }
-            parent4.delete();
-            parent3.delete();
-            parent2.delete();
-            parent1.delete();
-        }
-        return null;
-    }
-}
diff --git a/common/host-side/manifest-generator/Android.mk b/common/host-side/manifest-generator/Android.mk
index ca08928..b976329 100644
--- a/common/host-side/manifest-generator/Android.mk
+++ b/common/host-side/manifest-generator/Android.mk
@@ -30,4 +30,4 @@
 
 include $(BUILD_HOST_JAVA_LIBRARY)
 
-include $(call all-makefiles-under,$(LOCAL_PATH))
+include $(call all-makefiles-under,$(LOCAL_PATH))
\ No newline at end of file
diff --git a/common/host-side/native-scanner/Android.mk b/common/host-side/native-scanner/Android.mk
deleted file mode 100644
index 184cdc0..0000000
--- a/common/host-side/native-scanner/Android.mk
+++ /dev/null
@@ -1,47 +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.
-
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_JAR_MANIFEST := MANIFEST.mf
-
-LOCAL_CLASSPATH := $(HOST_JDK_TOOLS_JAR)
-
-LOCAL_JAVA_LIBRARIES := compatibility-common-util-hostsidelib_v2
-
-LOCAL_MODULE := compatibility-native-scanner_v2
-
-LOCAL_MODULE_TAGS := optional
-
-include $(BUILD_HOST_JAVA_LIBRARY)
-
-################################################################################
-# Build the tests
-###############################################################################
-
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := $(call all-java-files-under, tests/src)
-
-LOCAL_JAVA_LIBRARIES := compatibility-tradefed_v2 compatibility-native-scanner_v2 junit
-
-LOCAL_MODULE := compatibility-native-scanner-tests_v2
-
-LOCAL_MODULE_TAGS := optional
-
-include $(BUILD_HOST_JAVA_LIBRARY)
diff --git a/common/host-side/native-scanner/MANIFEST.mf b/common/host-side/native-scanner/MANIFEST.mf
deleted file mode 100644
index c5641ca..0000000
--- a/common/host-side/native-scanner/MANIFEST.mf
+++ /dev/null
@@ -1,3 +0,0 @@
-Manifest-Version: 1.0
-Main-Class: com.android.compatibility.common.scanner.NativeScanner
-Class-Path: compatibility-common-util-hostsidelib_v2.jar
diff --git a/common/host-side/native-scanner/src/com/android/compatibility/common/scanner/NativeScanner.java b/common/host-side/native-scanner/src/com/android/compatibility/common/scanner/NativeScanner.java
deleted file mode 100644
index 7b9e447..0000000
--- a/common/host-side/native-scanner/src/com/android/compatibility/common/scanner/NativeScanner.java
+++ /dev/null
@@ -1,84 +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.
- */
-
-package com.android.compatibility.common.scanner;
-
-import com.android.compatibility.common.util.KeyValueArgsParser;
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.util.ArrayList;
-import java.util.HashMap;
-
-/**
- * Passes the gtest output and outputs a list of test classes and methods.
- */
-public final class NativeScanner {
-
-    private static final String TEST_SUITE_ARG = "t";
-    private static final String USAGE = "Usage: compatibility-native-scanner -t TEST_SUITE"
-        + "  This code reads from stdin the list of tests."
-        + "  The format expected:"
-        + "    TEST_CASE_NAME."
-        + "      TEST_NAME";
-
-    /**
-     * @return An {@link ArrayList} of suites, classes and method names.
-     */
-    static ArrayList<String> getTestNames(BufferedReader reader, String testSuite)
-            throws IOException {
-        ArrayList<String> testNames = new ArrayList<String>();
-        testNames.add("suite:" + testSuite);
-
-        String testCaseName = null;
-        String line;
-        while ((line = reader.readLine()) != null) {
-            if (line.length() == 0) {
-                continue;
-            }
-            if (line.charAt(0) == ' ') {
-                if (testCaseName == null) {
-                    throw new RuntimeException("TEST_CASE_NAME not defined before first test.");
-                }
-                testNames.add("test:" + line.trim());
-            } else {
-                testCaseName = line.trim();
-                if (testCaseName.endsWith(".")) {
-                    testCaseName = testCaseName.substring(0, testCaseName.length()-1);
-                }
-                testNames.add("case:" + testCaseName);
-            }
-        }
-        return testNames;
-    }
-
-    /** Lookup test suite argument and scan {@code System.in} for test cases */
-    public static void main(String[] args) throws IOException {
-        HashMap<String, String> argMap = KeyValueArgsParser.parse(args);
-        if (!argMap.containsKey(TEST_SUITE_ARG)) {
-            System.err.println(USAGE);
-            System.exit(1);
-        }
-
-        String testSuite = argMap.get(TEST_SUITE_ARG);
-
-        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
-        for (String name : getTestNames(reader, testSuite)) {
-            System.out.println(name);
-        }
-    }
-}
diff --git a/common/host-side/native-scanner/tests/src/com/android/compatibility/common/scanner/NativeScannerTest.java b/common/host-side/native-scanner/tests/src/com/android/compatibility/common/scanner/NativeScannerTest.java
deleted file mode 100644
index c5d3157..0000000
--- a/common/host-side/native-scanner/tests/src/com/android/compatibility/common/scanner/NativeScannerTest.java
+++ /dev/null
@@ -1,70 +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.
- */
-
-package com.android.compatibility.common.scanner;
-
-import com.android.compatibility.common.scanner.NativeScanner;
-
-import junit.framework.TestCase;
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.StringReader;
-import java.util.List;
-import java.util.Iterator;
-
-public class NativeScannerTest extends TestCase {
-
-    public void testSingleTestNamesCase() throws Exception {
-        StringReader singleTestString = new StringReader("FakeTestCase.\n  FakeTestName\n");
-        BufferedReader reader = new BufferedReader(singleTestString);
-
-        List<String> names = NativeScanner.getTestNames(reader, "TestSuite");
-        Iterator<String> it = names.iterator();
-        assertEquals("suite:TestSuite", it.next());
-        assertEquals("case:FakeTestCase", it.next());
-        assertEquals("test:FakeTestName", it.next());
-        assertFalse(it.hasNext());
-    }
-
-    public void testMultipleTestNamesCase() throws Exception {
-        StringReader singleTestString = new StringReader(
-          "Case1.\n  Test1\n  Test2\nCase2.\n  Test3\n Test4\n");
-        BufferedReader reader = new BufferedReader(singleTestString);
-
-        List<String> names = NativeScanner.getTestNames(reader, "TestSuite");
-
-        Iterator<String> it = names.iterator();
-        assertEquals("suite:TestSuite", it.next());
-        assertEquals("case:Case1", it.next());
-        assertEquals("test:Test1", it.next());
-        assertEquals("test:Test2", it.next());
-        assertEquals("case:Case2", it.next());
-        assertEquals("test:Test3", it.next());
-        assertEquals("test:Test4", it.next());
-        assertFalse(it.hasNext());
-    }
-
-    public void testMissingTestCaseNameCase() throws IOException {
-        StringReader singleTestString = new StringReader("  Test1\n");
-        BufferedReader reader = new BufferedReader(singleTestString);
-
-        try {
-            NativeScanner.getTestNames(reader, "TestSuite");
-            fail("Expected RuntimeException");
-        } catch (RuntimeException expected) {}
-    }
-}
diff --git a/common/host-side/scripts/compatibility-tests_v2 b/common/host-side/scripts/compatibility-tests_v2
deleted file mode 100755
index 797909e..0000000
--- a/common/host-side/scripts/compatibility-tests_v2
+++ /dev/null
@@ -1,61 +0,0 @@
-#!/bin/bash
-#
-# 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.
-
-checkFile() {
-    if [ ! -f "$1" ]; then
-        echo "Unable to locate $1"
-        exit
-    fi;
-}
-
-HOST_JAR_DIR=${ANDROID_HOST_OUT}/framework
-HOST_JARS="ddmlib-prebuilt tradefed-prebuilt hosttestlib\
-    compatibility-tradefed_v2 compatibility-tradefed-tests_v2\
-    compatibility-java-scanner_v2 compatibility-java-scanner-tests_v2\
-    compatibility-native-scanner_v2 compatibility-native-scanner-tests_v2\
-    compatibility-xml-plan-generator_v2 compatibility-xml-plan-generator-tests_v2\
-    compatibility-device-util-tests_v2 compatibility-device-setup-tests_v2\
-    compatibility-common-util-hostsidelib_v2 compatibility-common-util-tests_v2"
-
-for JAR in ${HOST_JARS}; do
-    checkFile ${HOST_JAR_DIR}/${JAR}.jar
-    JAR_PATH=${JAR_PATH}:${HOST_JAR_DIR}/${JAR}.jar
-done
-
-DEVICE_LIBS_DIR=${ANDROID_PRODUCT_OUT}/obj/JAVA_LIBRARIES
-DEVICE_LIBS="compatibility-common-util-devicesidelib_v2 compatibility-device-util_v2\
-    compatibility-device-setup_v2"
-
-for LIB in ${DEVICE_LIBS}; do
-    checkFile ${DEVICE_LIBS_DIR}/${LIB}_intermediates/javalib.jar
-    JAR_PATH=${JAR_PATH}:${DEVICE_LIBS_DIR}/${LIB}_intermediates/javalib.jar
-done
-
-# TODO(stuartscott): Currently the test classes are explicitly set here, but
-# once our wrappers for tradefed are in place we can make it scan and generate
-# the list of test at runtime.
-TEST_CLASSES="com.android.compatibility.common.devicesetup.DeviceSetupTest\
-    com.android.compatibility.common.scanner.JavaScannerTest\
-    com.android.compatibility.common.scanner.NativeScannerTest\
-    com.android.compatibility.common.tradefed.TradefedTest\
-    com.android.compatibility.common.util.DeviceUtilTest\
-    com.android.compatibility.common.util.CommonUtilTest\
-    com.android.compatibility.common.xmlgenerator.XmlPlanGeneratorTest"
-
-for CLASS in ${TEST_CLASSES}; do
-    java $RDBG_FLAG -cp ${JAR_PATH} com.android.compatibility.common.tradefed.command.CompatibilityConsole run\
-            singleCommand host -n --class ${CLASS} "$@"
-done
diff --git a/common/host-side/tradefed/Android.mk b/common/host-side/tradefed/Android.mk
index 8ff7c8c..3ed225ed 100644
--- a/common/host-side/tradefed/Android.mk
+++ b/common/host-side/tradefed/Android.mk
@@ -1,4 +1,4 @@
-# Copyright (C) 2014 The Android Open Source Project
+# 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.
@@ -12,40 +12,4 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-###############################################################################
-# Builds the compatibility tradefed host library
-###############################################################################
-
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-#LOCAL_JAVA_RESOURCE_DIRS := res
-
-LOCAL_MODULE := compatibility-tradefed_v2
-
-LOCAL_MODULE_TAGS := optional
-
-LOCAL_JAVA_LIBRARIES := tradefed-prebuilt hosttestlib compatibility-common-util-hostsidelib_v2
-
-include $(BUILD_HOST_JAVA_LIBRARY)
-
-###############################################################################
-# Build the compatibility tradefed tests
-###############################################################################
-
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := $(call all-java-files-under, tests/src)
-
-LOCAL_MODULE := compatibility-tradefed-tests_v2
-
-LOCAL_MODULE_TAGS := optional
-
-LOCAL_JAVA_LIBRARIES := tradefed-prebuilt compatibility-tradefed_v2 junit
-
-LOCAL_STATIC_JAVA_LIBRARIES := easymock
-
-include $(BUILD_HOST_JAVA_LIBRARY)
+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
new file mode 100644
index 0000000..5bac748
--- /dev/null
+++ b/common/host-side/tradefed/res/config/common-compatibility-config.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<configuration description="Common config for Compatibility suites">
+
+    <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.CompatibilityTest" />
+    <logger class="com.android.tradefed.log.FileLogger" />
+    <result_reporter class="com.android.compatibility.common.tradefed.result.ResultReporter" />
+
+</configuration>
diff --git a/common/host-side/tradefed/res/config/common-config.xml b/common/host-side/tradefed/res/config/common-config.xml
new file mode 100644
index 0000000..e05fc0d
--- /dev/null
+++ b/common/host-side/tradefed/res/config/common-config.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<configuration description="Common base configuration for Compatibility tests">
+    <!--
+      This common base configuration contains some commonly used preparers
+      -->
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+        <option name="cleanup" value="true" />
+    </target_preparer>
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.ApkInstaller">
+        <option name="cleanup-apks" value="true" />
+    </target_preparer>
+    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer" />
+</configuration>
diff --git a/common/host-side/tradefed/res/config/everything.xml b/common/host-side/tradefed/res/config/everything.xml
new file mode 100644
index 0000000..048d13e
--- /dev/null
+++ b/common/host-side/tradefed/res/config/everything.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<configuration description="Common config for Compatibility suites to run all modules">
+
+    <include name="common-compatibility-config" />
+    <option name="compatibility-test:include-filter" value=".*" />
+    <option name="compatibility-test:plan" value="everything" />
+
+</configuration>
diff --git a/common/host-side/tradefed/res/report/compatibility-result.css b/common/host-side/tradefed/res/report/compatibility-result.css
new file mode 100644
index 0000000..c667e138
--- /dev/null
+++ b/common/host-side/tradefed/res/report/compatibility-result.css
@@ -0,0 +1,164 @@
+/* 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.
+*/
+
+body {
+    font-family:arial,sans-serif;
+    color:#000;
+    font-size:13px;
+    color:#333;
+    padding:10;
+    margin:10;
+}
+
+/* Report logo and device name */
+table.title {
+    padding:5px;
+    border-width: 0px;
+    margin-left:auto;
+    margin-right:auto;
+    vertical-align:middle;
+}
+
+table.summary {
+    background-color: rgb(212, 233, 169);
+    border-collapse:collapse;
+    border: 0px solid #A5C639;
+    margin-left:auto;
+    margin-right:auto;
+}
+
+table.summary th {
+    background-color: #A5C639;
+    font-size: 1.2em;
+    padding: 0.5em;
+}
+
+table.summary td {
+    border-width: 0px 0px 0px 0px;
+    border-color: gray;
+    border-style: inset;
+    font-size: 1em;
+    padding: 0.5em;
+    vertical-align: top;
+}
+
+table.testsummary {
+    background-color: rgb(212, 233, 169);
+    border-collapse:collapse;
+    margin-left:auto;
+    margin-right:auto;
+}
+
+table.testsummary th {
+    background-color: #A5C639;
+    border: 1px outset gray;
+    padding: 0.5em;
+}
+
+table.testsummary td {
+    border: 1px outset #A5C639;
+    padding: 0.5em;
+    text-align: center;
+}
+
+table.testdetails {
+    background-color: rgb(212, 233, 169);
+    border-collapse:collapse;
+    border-width:1;
+    border-color: #A5C639;
+    margin-left:auto;
+    margin-right:auto;
+    margin-bottom: 2em;
+    vertical-align: top;
+    width: 95%;
+}
+
+table.testdetails th {
+    background-color: #A5C639;
+    border-width: 1px;
+    border-color: gray;
+    border-style: outset;
+    height: 2em;
+    padding: 0.2em;
+}
+
+table.testdetails td {
+    border-width: 1px;
+    border-color: #A5C639;
+    border-style: outset;
+    text-align: left;
+    vertical-align: top;
+    padding: 0.2em;
+}
+
+table.testdetails td.module {
+    background-color: white;
+    border: 0px;
+    font-weight: bold;
+}
+
+/* Test cell details */
+td.failed {
+    background-color: #FA5858;
+    font-weight:bold;
+    vertical-align: top;
+    text-align: center;
+}
+
+td.failuredetails {
+    text-align: left;
+}
+
+td.pass {
+    text-align: center;
+    margin-left:auto;
+    margin-right:auto;
+}
+
+td.not-executed {
+    background-color: #A5C639;
+    vertical-align: top;
+    text-align: center;
+}
+
+td.testname {
+    border-width: 1px;
+    border-color: #A5C639;
+    border-style: outset;
+    text-align: left;
+    vertical-align: top;
+    padding:1;
+    overflow:hidden;
+}
+
+td.testcase {
+    border-width: 1px;
+    border-color: #A5C639;
+    border-style: outset;
+    text-align: left;
+    vertical-align: top;
+    padding:1;
+    overflow:hidden;
+    font-weight:bold;
+}
+
+div.details {
+    white-space: pre-wrap;       /* css-3 */
+    white-space: -moz-pre-wrap;  /* Mozilla, since 1999 */
+    white-space: -pre-wrap;      /* Opera 4-6 */
+    white-space: -o-pre-wrap;    /* Opera 7 */
+    word-wrap: break-word;       /* Internet Explorer 5.5+ */
+    overflow:auto;
+}
diff --git a/common/host-side/tradefed/res/report/compatibility-result.xsd b/common/host-side/tradefed/res/report/compatibility-result.xsd
new file mode 100644
index 0000000..770ae5a
--- /dev/null
+++ b/common/host-side/tradefed/res/report/compatibility-result.xsd
@@ -0,0 +1,127 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ * 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.
+ -->
+
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
+           targetNamespace="http://compatibility.android.com/compatibility_result/1.15"
+           xmlns="http://compatibility.android.com/compatibility_result/1.15"
+           elementFormDefault="qualified">
+
+  <xs:element name="Result">
+    <xs:complexType>
+      <xs:sequence>
+        <xs:element name="Summary" type="summaryType"/>
+        <xs:element name="Module" type="moduleType" minOccurs="1" maxOccurs="unbounded"/>
+      </xs:sequence>
+      <xs:attribute name="start" type="xs:string"/>
+      <xs:attribute name="end" type="xs:string"/>
+      <xs:attribute name="plan" type="xs:string"/>
+      <xs:attribute name="suite-name" type="xs:string"/>
+      <xs:attribute name="suite-version" type="xs:string"/>
+    </xs:complexType>
+  </xs:element>
+
+  <xs:complexType name="summaryType">
+    <xs:attribute name="failed" type="xs:integer"/>
+    <xs:attribute name="not-executed" type="xs:integer"/>
+    <xs:attribute name="pass" type="xs:integer"/>
+  </xs:complexType>
+
+  <xs:complexType name="moduleType">
+    <xs:sequence>
+      <xs:element name="Test" type="testType" minOccurs="1" maxOccurs="unbounded" />
+    </xs:sequence>
+    <xs:attribute name="name" type="xs:string" use="required"/>
+    <xs:attribute name="abi" type="xs:string"/>
+  </xs:complexType>
+
+  <xs:simpleType name="unitType">
+    <xs:restriction base="xs:string">
+      <xs:enumeration value="none"/>
+      <xs:enumeration value="ms"/>
+      <xs:enumeration value="fps"/>
+      <xs:enumeration value="ops"/>
+      <xs:enumeration value="kbps"/>
+      <xs:enumeration value="mbps"/>
+      <xs:enumeration value="byte"/>
+      <xs:enumeration value="count"/>
+      <xs:enumeration value="score"/>
+    </xs:restriction>
+  </xs:simpleType>
+
+  <xs:simpleType name="scoreTypeType">
+    <xs:restriction base="xs:string">
+      <xs:enumeration value="higher-better"/>
+      <xs:enumeration value="lower-better"/>
+      <xs:enumeration value="neutral"/>
+      <xs:enumeration value="warning"/>
+    </xs:restriction>
+  </xs:simpleType>
+
+  <xs:complexType name="testType">
+    <xs:sequence>
+      <xs:element name="Failure" minOccurs="0" maxOccurs="1">
+        <xs:complexType>
+          <xs:sequence>
+            <xs:element name="StackTrace" type="xs:string" minOccurs="0" maxOccurs="1"/>
+          </xs:sequence>
+          <xs:attribute name="message" type="xs:string"/>
+        </xs:complexType>
+      </xs:element>
+      <xs:element name="Summary" minOccurs="0" maxOccurs="1">
+        <xs:complexType>
+          <xs:simpleContent>
+            <xs:extension base="xs:decimal">
+              <xs:attribute name="message" type="xs:string" use="required" />
+              <xs:attribute name="scoreType" type="scoreTypeType" use="required" />
+              <xs:attribute name="unit" type="unitType" use="required" />
+              <xs:attribute name="target" type="xs:decimal" />
+            </xs:extension>
+          </xs:simpleContent>
+        </xs:complexType>
+      </xs:element>
+      <xs:element name="Details" minOccurs="0" maxOccurs="1">
+        <xs:complexType>
+          <xs:sequence>
+            <xs:element name="ValueArray" minOccurs="0" maxOccurs="unbounded">
+              <xs:complexType>
+                <xs:sequence>
+                  <xs:element name="Value" type="xs:decimal" minOccurs="0" maxOccurs="unbounded" />
+                </xs:sequence>
+                <xs:attribute name="source" type="xs:string" use="required" />
+                <xs:attribute name="message" type="xs:string" use="required" />
+                <xs:attribute name="scoreType" type="scoreTypeType" use="required" />
+                <xs:attribute name="unit" type="unitType" use="required" />
+              </xs:complexType>
+            </xs:element>
+          </xs:sequence>
+        </xs:complexType>
+      </xs:element>
+    </xs:sequence>
+    <xs:attribute name="name" type="xs:string" use="required"/>
+    <xs:attribute name="result" type="resultType" use="required"/>
+    <xs:attribute name="start" type="xs:string"/>
+    <xs:attribute name="end" type="xs:string"/>
+  </xs:complexType>
+
+  <xs:simpleType name="resultType">
+    <xs:restriction base="xs:string">
+      <xs:enumeration value="pass"/>
+      <xs:enumeration value="fail"/>
+      <xs:enumeration value="not-executed"/>
+    </xs:restriction>
+  </xs:simpleType>
+</xs:schema>
\ No newline at end of file
diff --git a/common/host-side/tradefed/res/report/compatibility-result.xsl b/common/host-side/tradefed/res/report/compatibility-result.xsl
new file mode 100644
index 0000000..6334826
--- /dev/null
+++ b/common/host-side/tradefed/res/report/compatibility-result.xsl
@@ -0,0 +1,273 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<!DOCTYPE xsl:stylesheet [ <!ENTITY nbsp "&#160;"> ]>
+<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+    <xsl:output method="html" version="1.0" encoding="UTF-8" indent="yes"/>
+
+    <xsl:template match="/">
+
+        <html>
+            <head>
+                <title>Test Report</title>
+                <style type="text/css">
+                    @import "compatibility-result.css";
+                </style>
+            </head>
+            <body>
+                <div>
+                    <table class="title">
+                        <tr>
+                            <td width="40%" align="left"><img src="logo.png"></img></td>
+                            <td width="60%" align="left">
+                                <h1>Test Report</h1>
+                            </td>
+                        </tr>
+                    </table>
+                </div>
+                <img src="newrule-green.png" align="left"></img>
+
+                <br></br>
+
+                <center>
+                    <a href="device-info.xml" >Device Information</a>
+                </center>
+
+                <br></br>
+
+                <div>
+                    <table class="summary">
+                        <tr>
+                            <th colspan="2">Test Summary</th>
+                        </tr>
+                        <tr>
+                            <td class="rowtitle">Compatibility Suite</td>
+                            <td>
+                                <xsl:value-of select="Result/@suite-name"/>
+                            </td>
+                        </tr>
+                        <tr>
+                            <td class="rowtitle">Compatibility Version</td>
+                            <td>
+                                <xsl:value-of select="Result/@suite-version"/>
+                            </td>
+                        </tr>
+                        <tr>
+                            <td class="rowtitle">Report Version</td>
+                            <td>
+                                <xsl:value-of select="Result/@report-version"/>
+                            </td>
+                        </tr>
+                        <tr>
+                            <td class="rowtitle">Host Info</td>
+                            <td>
+                                <xsl:value-of select="Result/@host-name"/>
+                                (<xsl:value-of select="Result/@os-name"/> - <xsl:value-of select="Result/@os-version"/>)
+                            </td>
+                        </tr>
+                        <tr>
+                            <td class="rowtitle">Plan</td>
+                            <td>
+                                <xsl:value-of select="Result/@plan"/>
+                            </td>
+                        </tr>
+                        <tr>
+                            <td class="rowtitle">Start time</td>
+                            <td>
+                                <xsl:value-of select="Result/@start"/>
+                            </td>
+                        </tr>
+                        <tr>
+                            <td class="rowtitle">End time</td>
+                            <td>
+                                <xsl:value-of select="Result/@end"/>
+                            </td>
+                        </tr>
+                        <tr>
+                            <td class="rowtitle">Tests Passed</td>
+                            <td>
+                                <xsl:value-of select="Result/Summary/@pass"/>
+                            </td>
+                        </tr>
+                        <tr>
+                            <td class="rowtitle">Tests Failed</td>
+                            <td>
+                                <xsl:value-of select="Result/Summary/@failed"/>
+                            </td>
+                        </tr>
+                        <tr>
+                            <td class="rowtitle">Tests Not Executed</td>
+                            <td>
+                                <xsl:value-of select="Result/Summary/@not-executed"/>
+                            </td>
+                        </tr>
+                    </table>
+                </div>
+
+                <!-- High level summary of test execution -->
+                <h2 align="center">Test Summary by Module</h2>
+                <div>
+                    <table class="testsummary">
+                        <tr>
+                            <th>Module</th>
+                            <th>Passed</th>
+                            <th>Failed</th>
+                            <th>Not Executed</th>
+                            <th>Total Tests</th>
+                        </tr>
+                        <xsl:for-each select="Result/Module">
+                            <tr>
+                                <td>
+                                    <xsl:variable name="href"><xsl:value-of select="@name"/> - <xsl:value-of select="@abi"/></xsl:variable>
+                                    <a href="#{$href}"><xsl:value-of select="@name"/> - <xsl:value-of select="@abi"/></a>
+                                </td>
+                                <td>
+                                    <xsl:value-of select="count(Test[@result = 'pass'])"/>
+                                </td>
+                                <td>
+                                    <xsl:value-of select="count(Test[@result = 'fail'])"/>
+                                </td>
+                                <td>
+                                    <xsl:value-of select="count(Test[@result = 'not-executed'])"/>
+                                </td>
+                                <td>
+                                    <xsl:value-of select="count(Test)"/>
+                                </td>
+                            </tr>
+                        </xsl:for-each> <!-- end Module -->
+                    </table>
+                </div>
+
+                <xsl:call-template name="filteredResultTestReport">
+                    <xsl:with-param name="header" select="'Failured Tests'" />
+                    <xsl:with-param name="resultFilter" select="'fail'" />
+                </xsl:call-template>
+
+                <xsl:call-template name="filteredResultTestReport">
+                    <xsl:with-param name="header" select="'Not Executed Tests'" />
+                    <xsl:with-param name="resultFilter" select="'not-executed'" />
+                </xsl:call-template>
+
+                <h2 align="center">Detailed Test Report</h2>
+                <xsl:call-template name="detailedTestReport" />
+
+            </body>
+        </html>
+    </xsl:template>
+
+    <xsl:template name="filteredResultTestReport">
+        <xsl:param name="header" />
+        <xsl:param name="resultFilter" />
+        <xsl:variable name="numMatching" select="count(Result/Module/Test[@result=$resultFilter])" />
+        <xsl:if test="$numMatching &gt; 0">
+            <h2 align="center"><xsl:value-of select="$header" /> (<xsl:value-of select="$numMatching"/>)</h2>
+            <xsl:call-template name="detailedTestReport">
+                <xsl:with-param name="resultFilter" select="$resultFilter"/>
+            </xsl:call-template>
+        </xsl:if>
+    </xsl:template>
+
+    <xsl:template name="detailedTestReport">
+        <xsl:param name="resultFilter" />
+        <div>
+            <xsl:for-each select="Result/Module">
+                <xsl:if test="$resultFilter=''
+                        or count(Test[@result=$resultFilter]) &gt; 0">
+
+                    <table class="testdetails">
+                        <tr>
+                            <td class="module" colspan="3">
+                                <xsl:variable name="href"><xsl:value-of select="@name"/> - <xsl:value-of select="@abi"/></xsl:variable>
+                                <a name="{$href}"><xsl:value-of select="@name"/> - <xsl:value-of select="@abi"/></a>
+                            </td>
+                        </tr>
+
+                        <tr>
+                            <th width="30%">Test</th>
+                            <th width="5%">Result</th>
+                            <th>Details</th>
+                        </tr>
+
+                        <!-- test -->
+                        <xsl:for-each select="Test">
+                            <xsl:if test="$resultFilter='' or $resultFilter=@result">
+                                <tr>
+                                    <td class="testname"> -- <xsl:value-of select="@name"/></td>
+
+                                    <!-- test results -->
+                                    <xsl:if test="@result='pass'">
+                                        <td class="pass">
+                                            <div style="text-align: center; margin-left:auto; margin-right:auto;">
+                                                <xsl:value-of select="@result"/>
+                                            </div>
+                                        </td>
+                                        <td class="failuredetails"/>
+                                    </xsl:if>
+
+                                    <xsl:if test="@result='fail'">
+                                        <td class="failed">
+                                            <div style="text-align: center; margin-left:auto; margin-right:auto;">
+                                                <xsl:value-of select="@result"/>
+                                            </div>
+                                        </td>
+                                        <td class="failuredetails">
+                                            <div class="details">
+                                                <xsl:value-of select="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;">
+                                                <xsl:value-of select="@result"/>
+                                            </div>
+                                        </td>
+                                        <td class="failuredetails"></td>
+                                    </xsl:if>
+                                </tr> <!-- finished with a row -->
+                            </xsl:if>
+                        </xsl:for-each> <!-- end test -->
+                    </table>
+                </xsl:if>
+            </xsl:for-each> <!-- end test Module -->
+        </div>
+    </xsl:template>
+
+    <!-- Take a delimited string and insert line breaks after a some number of elements. -->
+    <xsl:template name="formatDelimitedString">
+        <xsl:param name="string" />
+        <xsl:param name="numTokensPerRow" select="10" />
+        <xsl:param name="tokenIndex" select="1" />
+        <xsl:if test="$string">
+            <!-- Requires the last element to also have a delimiter after it. -->
+            <xsl:variable name="token" select="substring-before($string, ';')" />
+            <xsl:value-of select="$token" />
+            <xsl:text>&#160;</xsl:text>
+
+            <xsl:if test="$tokenIndex mod $numTokensPerRow = 0">
+                <br />
+            </xsl:if>
+
+            <xsl:call-template name="formatDelimitedString">
+                <xsl:with-param name="string" select="substring-after($string, ';')" />
+                <xsl:with-param name="numTokensPerRow" select="$numTokensPerRow" />
+                <xsl:with-param name="tokenIndex" select="$tokenIndex + 1" />
+            </xsl:call-template>
+        </xsl:if>
+    </xsl:template>
+
+</xsl:stylesheet>
diff --git a/common/host-side/tradefed/res/report/logo.png b/common/host-side/tradefed/res/report/logo.png
new file mode 100644
index 0000000..61970b3
--- /dev/null
+++ b/common/host-side/tradefed/res/report/logo.png
Binary files differ
diff --git a/common/host-side/tradefed/res/report/newrule-green.png b/common/host-side/tradefed/res/report/newrule-green.png
new file mode 100644
index 0000000..10a4194
--- /dev/null
+++ b/common/host-side/tradefed/res/report/newrule-green.png
Binary files differ
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
new file mode 100644
index 0000000..39a5ca6
--- /dev/null
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/build/CompatibilityBuildHelper.java
@@ -0,0 +1,200 @@
+/*
+ * 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.build;
+
+import com.android.compatibility.SuiteInfo;
+import com.android.tradefed.build.IFolderBuildInfo;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * A simple helper that stores and retrieves information from a {@link IFolderBuildInfo}.
+ */
+public class CompatibilityBuildHelper {
+
+    private static final String ROOT_DIR = "ROOT_DIR";
+    private static final String SUITE_BUILD = "SUITE_BUILD";
+    private static final String SUITE_NAME = "SUITE_NAME";
+    private static final String SUITE_FULL_NAME = "SUITE_FULL_NAME";
+    private static final String SUITE_VERSION = "SUITE_VERSION";
+    private static final String SUITE_PLAN = "SUITE_PLAN";
+    private static final String RESULT_DIR = "RESULT_DIR";
+    private static final String CONFIG_PATH_PREFIX = "DYNAMIC_CONFIG_FILE:";
+    private static final String DYNAMIC_CONFIG_OVERRIDE_URL = "DYNAMIC_CONFIG_OVERRIDE_URL";
+    private final IFolderBuildInfo mBuildInfo;
+    private boolean mInitialized = false;
+
+    /**
+     * Creates a {@link CompatibilityBuildHelper} wrapping the given {@link IFolderBuildInfo}.
+     */
+    public CompatibilityBuildHelper(IFolderBuildInfo buildInfo) {
+        mBuildInfo = buildInfo;
+    }
+
+    /**
+     * Initializes the {@link IFolderBuildInfo} from the manifest.
+     */
+    public void init(String suitePlan, String dynamicConfigUrl) {
+        if (mInitialized) {
+            return;
+        }
+        mInitialized = true;
+        mBuildInfo.addBuildAttribute(SUITE_BUILD, SuiteInfo.BUILD_NUMBER);
+        mBuildInfo.addBuildAttribute(SUITE_NAME, SuiteInfo.NAME);
+        mBuildInfo.addBuildAttribute(SUITE_FULL_NAME, SuiteInfo.FULLNAME);
+        mBuildInfo.addBuildAttribute(SUITE_VERSION, SuiteInfo.VERSION);
+        mBuildInfo.addBuildAttribute(SUITE_PLAN, suitePlan);
+        String mRootDirPath = System.getProperty(String.format("%s_ROOT", SuiteInfo.NAME));
+        if (mRootDirPath == null || mRootDirPath.trim().equals("")) {
+            File root = mBuildInfo.getRootDir();
+            if (root != null) {
+                mRootDirPath = root.getAbsolutePath();
+            }
+            if (mRootDirPath == null || mRootDirPath.trim().equals("")) {
+                throw new IllegalArgumentException(
+                        String.format("Missing install path property %s_ROOT", SuiteInfo.NAME));
+            }
+        }
+        File rootDir = new File(mRootDirPath);
+        if (!rootDir.exists()) {
+            throw new IllegalArgumentException(
+                    String.format("Root directory doesn't exist %s", rootDir.getAbsolutePath()));
+        }
+        mBuildInfo.addBuildAttribute(ROOT_DIR, rootDir.getAbsolutePath());
+        mBuildInfo.setRootDir(rootDir);
+        if (dynamicConfigUrl != null && !dynamicConfigUrl.isEmpty()) {
+            mBuildInfo.addBuildAttribute(DYNAMIC_CONFIG_OVERRIDE_URL,
+                    dynamicConfigUrl.replace("{suite-name}", getSuiteName()));
+        }
+    }
+
+    public String getSuiteBuild() {
+        return mBuildInfo.getBuildAttributes().get(SUITE_BUILD);
+    }
+
+    public String getSuiteName() {
+        return mBuildInfo.getBuildAttributes().get(SUITE_NAME);
+    }
+
+    public String getSuiteFullName() {
+        return mBuildInfo.getBuildAttributes().get(SUITE_FULL_NAME);
+    }
+
+    public String getSuiteVersion() {
+        return mBuildInfo.getBuildAttributes().get(SUITE_VERSION);
+    }
+
+    public String getSuitePlan() {
+        return mBuildInfo.getBuildAttributes().get(SUITE_PLAN);
+    }
+
+    public String getDynamicConfigUrl() {
+        return mBuildInfo.getBuildAttributes().get(DYNAMIC_CONFIG_OVERRIDE_URL);
+    }
+
+    public void addDynamicConfigFile(String moduleName, File configFile) {
+        mBuildInfo.addBuildAttribute(CONFIG_PATH_PREFIX + moduleName, configFile.getAbsolutePath());
+    }
+
+    public Map<String, File> getDynamicConfigFiles() {
+        Map<String, File> configMap = new HashMap<>();
+        for (String key : mBuildInfo.getBuildAttributes().keySet()) {
+            if (key.startsWith(CONFIG_PATH_PREFIX)) {
+                configMap.put(key.substring(CONFIG_PATH_PREFIX.length()),
+                        new File(mBuildInfo.getBuildAttributes().get(key)));
+            }
+        }
+        return configMap;
+    }
+
+    /**
+     * @return a {@link File} representing the directory holding the Compatibility installation
+     * @throws FileNotFoundException if the directory does not exist
+     */
+    public File getRootDir() throws FileNotFoundException {
+        File dir = new File(mBuildInfo.getBuildAttributes().get(ROOT_DIR));
+        if (!dir.exists()) {
+            throw new FileNotFoundException(String.format(
+                    "Compatibility root directory %s does not exist",
+                    dir.getAbsolutePath()));
+        }
+        return dir;
+    }
+
+    /**
+     * @return a {@link File} representing the "android-<suite>" folder of the Compatibility
+     * installation
+     * @throws FileNotFoundException if the directory does not exist
+     */
+    public File getDir() throws FileNotFoundException {
+        File dir = new File(getRootDir(), String.format("android-%s", getSuiteName().toLowerCase()));
+        if (!dir.exists()) {
+            throw new FileNotFoundException(String.format(
+                    "Compatibility install folder %s does not exist",
+                    dir.getAbsolutePath()));
+        }
+        return dir;
+    }
+
+    /**
+     * @return a {@link File} representing the results directory.
+     * @throws FileNotFoundException if the directory structure is not valid.
+     */
+    public File getResultsDir() throws FileNotFoundException {
+        return new File(getDir(), "results");
+    }
+
+    /**
+     * Sets the name of the current invocation's result directory.
+     */
+    public void setResultDir(String resultDir) {
+        mBuildInfo.addBuildAttribute(RESULT_DIR, resultDir);
+    }
+
+    /**
+     * @return a {@link File} representing the result directory of the current invocation.
+     * @throws FileNotFoundException if the directory structure is not valid.
+     */
+    public File getResultDir() throws FileNotFoundException {
+        return new File(getResultsDir(), mBuildInfo.getBuildAttributes().get(RESULT_DIR));
+    }
+
+    /**
+     * @return a {@link File} representing the directory to store result logs.
+     * @throws FileNotFoundException if the directory structure is not valid.
+     */
+    public File getLogsDir() throws FileNotFoundException {
+        return new File(getDir(), "logs");
+    }
+
+    /**
+     * @return a {@link File} representing the test modules directory.
+     * @throws FileNotFoundException if the directory structure is not valid.
+     */
+    public File getTestsDir() throws FileNotFoundException {
+        File testsDir = new File(getDir(), "testcases");
+        if (!testsDir.exists()) {
+            throw new FileNotFoundException(String.format(
+                    "Compatibility tests folder %s does not exist",
+                    testsDir.getAbsolutePath()));
+        }
+        return testsDir;
+    }
+
+}
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/build/CompatibilityBuildProvider.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/build/CompatibilityBuildProvider.java
new file mode 100644
index 0000000..d026869
--- /dev/null
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/build/CompatibilityBuildProvider.java
@@ -0,0 +1,53 @@
+/*
+ * 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.build;
+
+import com.android.tradefed.build.FolderBuildInfo;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.build.IBuildProvider;
+import com.android.tradefed.config.OptionClass;
+
+/**
+ * A simple {@link IBuildProvider} that uses a pre-existing Compatibility install.
+ */
+@OptionClass(alias="compatibility-build-provider")
+public class CompatibilityBuildProvider implements IBuildProvider {
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public IBuildInfo getBuild() {
+        // Create a blank FolderBuildInfo which will get populated later.
+        return new FolderBuildInfo("" /* buildId */, "" /* testTarget */, "" /* buildName */);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void buildNotTested(IBuildInfo info) {
+        // ignore
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void cleanUp(IBuildInfo info) {
+        // ignore
+    }
+}
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 dfd1c71..95fc891 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
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2014 The Android Open Source Project
+ * 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.
@@ -13,19 +13,217 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 package com.android.compatibility.common.tradefed.command;
 
+import com.android.compatibility.SuiteInfo;
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildProvider;
+import com.android.compatibility.common.tradefed.result.IInvocationResultRepo;
+import com.android.compatibility.common.tradefed.result.InvocationResultRepo;
+import com.android.compatibility.common.tradefed.testtype.ModuleRepo;
+import com.android.compatibility.common.util.IInvocationResult;
+import com.android.compatibility.common.util.TestStatus;
+import com.android.tradefed.build.IFolderBuildInfo;
 import com.android.tradefed.command.Console;
 import com.android.tradefed.config.ConfigurationException;
+import com.android.tradefed.util.ArrayUtil;
+import com.android.tradefed.util.FileUtil;
+import com.android.tradefed.util.RegexTrie;
+import com.android.tradefed.util.TableFormatter;
+import com.android.tradefed.util.TimeUtil;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
 
 /**
  * An extension of Tradefed's console which adds features specific to compatibility testing.
  */
 public class CompatibilityConsole extends Console {
 
+    private CompatibilityBuildHelper mBuildHelper;
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void run() {
+        printLine(String.format("Android %s %s (%s)", SuiteInfo.FULLNAME, SuiteInfo.VERSION,
+                SuiteInfo.BUILD_NUMBER));
+        super.run();
+    }
+
+    /**
+     * Adds the 'list plans', 'list modules' and 'list results' commands
+     */
+    @Override
+    protected void setCustomCommands(RegexTrie<Runnable> trie, List<String> genericHelp,
+            Map<String, String> commandHelp) {
+        trie.put(new Runnable() {
+            @Override
+            public void run() {
+                // TODO(stuartscott)" listPlans(buildHelper);
+            }
+        }, LIST_PATTERN, "p(?:lans)?");
+        trie.put(new Runnable() {
+            @Override
+            public void run() {
+                listModules();
+            }
+        }, LIST_PATTERN, "m(?:odules)?");
+        trie.put(new Runnable() {
+            @Override
+            public void run() {
+                listResults();
+            }
+        }, LIST_PATTERN, "r(?:esults)?");
+
+        // find existing help for 'LIST_PATTERN' commands, and append these commands help
+        String listHelp = commandHelp.get(LIST_PATTERN);
+        if (listHelp == null) {
+            // no help? Unexpected, but soldier on
+            listHelp = new String();
+        }
+        String combinedHelp = listHelp +
+                "\tp[lans]\tList all plans" + LINE_SEPARATOR +
+                "\tm[odules]\tList all modules" + LINE_SEPARATOR +
+                "\tr[esults]\tList all results" + LINE_SEPARATOR;
+        commandHelp.put(LIST_PATTERN, combinedHelp);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected String getConsolePrompt() {
+        return String.format("%s-tf > ", SuiteInfo.NAME.toLowerCase());
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected String getGenericHelpString(List<String> genericHelp) {
+        StringBuilder helpBuilder = new StringBuilder();
+        helpBuilder.append(SuiteInfo.FULLNAME);
+        helpBuilder.append("\n\n");
+        helpBuilder.append(SuiteInfo.NAME);
+        helpBuilder.append(" is the test harness for running the Android Compatibility Suite, ");
+        helpBuilder.append("built on top of Trade Federation.\n\n");
+        helpBuilder.append("Available commands and options\n");
+        helpBuilder.append("Host:\n");
+        helpBuilder.append("  help: show this message\n");
+        helpBuilder.append("  help all: show the complete tradefed help\n");
+        helpBuilder.append("  version: show the version\n");
+        helpBuilder.append("  exit: gracefully exit the compatibiltiy console, waiting until all ");
+        helpBuilder.append("invocations have completed\n");
+        helpBuilder.append("Run:\n");
+        final String runPrompt = "  run <plan> ";
+        helpBuilder.append(runPrompt);
+        helpBuilder.append("--module/-m <module>: run a test module\n");
+        helpBuilder.append(runPrompt);
+        helpBuilder.append("--module/-m <module> --test/-t <test_name>: run a specific test from");
+        helpBuilder.append(" the module. Test name can be <package>, ");
+        helpBuilder.append("<package>.<class>, <package>.<class>#<method> or <native_name>");
+        helpBuilder.append(runPrompt);
+        helpBuilder.append("--retry <session_id>: run all failed tests from a previous session\n");
+        helpBuilder.append(runPrompt);
+        helpBuilder.append("[options] --serial/-s <device_id>: run on the specified device\n");
+        helpBuilder.append(runPrompt);
+        helpBuilder.append("[options] --abi/-a <abi>: the ABI to run the test against\n");
+        helpBuilder.append(runPrompt);
+        helpBuilder.append("[options] --shard <shards>: shards a run into the given number of ");
+        helpBuilder.append("independent chunks, to run on multiple devices in parallel\n");
+        helpBuilder.append(runPrompt);
+        helpBuilder.append("--help/--help-all: get help for ");
+        helpBuilder.append(SuiteInfo.FULLNAME);
+        helpBuilder.append("\n");
+        helpBuilder.append("List:\n");
+        helpBuilder.append("  l/list d/devices: list connected devices and their state\n");
+        helpBuilder.append("  l/list m/modules: list test modules\n");
+        helpBuilder.append("  l/list i/invocations: list invocations aka test runs currently in ");
+        helpBuilder.append("progress\n");
+        helpBuilder.append("  l/list c/commands: list commands: aka test run commands currently");
+        helpBuilder.append("in the queue waiting to be allocated devices\n");
+        helpBuilder.append("  l/list r/results: list results currently in the repository\n");
+        helpBuilder.append("Dump:\n");
+        helpBuilder.append("  d/dump l/logs: dump the tradefed logs for all running invocations\n");
+        helpBuilder.append("Options:\n");
+        helpBuilder.append("  --disable-reboot : Do not reboot device after running some amount ");
+        helpBuilder.append(" of tests.\n");
+        return helpBuilder.toString();
+    }
+
+    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()));
+            }
+            Collections.sort(modules);
+            for (String module : modules) {
+                printLine(module);
+            }
+        } else {
+            printLine("No modules found");
+        }
+    }
+
+    private void listResults() {
+        TableFormatter tableFormatter = new TableFormatter();
+        List<List<String>> table = new ArrayList<>();
+        table.add(Arrays.asList("Session","Pass", "Fail", "Not Executed", "Start Time", "Test Plan",
+                "Device serial(s)"));
+        IInvocationResultRepo testResultRepo = null;
+        List<IInvocationResult> results = null;
+        try {
+            testResultRepo = new InvocationResultRepo(getBuildHelper().getResultsDir());
+            results = testResultRepo.getResults();
+        } catch (FileNotFoundException e) {
+            printLine(e.getMessage());
+            e.printStackTrace();
+        }
+        if (testResultRepo != null && results.size() > 0) {
+            for (int i = 0; i < results.size(); i++) {
+                IInvocationResult result = results.get(i);
+                table.add(Arrays.asList(Integer.toString(i),
+                        Integer.toString(result.countResults(TestStatus.PASS)),
+                        Integer.toString(result.countResults(TestStatus.FAIL)),
+                        Integer.toString(result.countResults(TestStatus.NOT_EXECUTED)),
+                        TimeUtil.formatTimeStamp(result.getStartTime()),
+                        result.getTestPlan(),
+                        ArrayUtil.join(", ", result.getDeviceSerials())));
+            }
+            tableFormatter.displayTable(table, new PrintWriter(System.out, true));
+        } else {
+            printLine(String.format("No results found"));
+        }
+    }
+
+    private CompatibilityBuildHelper getBuildHelper() {
+        if (mBuildHelper == null) {
+            CompatibilityBuildProvider buildProvider = new CompatibilityBuildProvider();
+            IFolderBuildInfo buildInfo = (IFolderBuildInfo) buildProvider.getBuild();
+            mBuildHelper = new CompatibilityBuildHelper(buildInfo);
+            mBuildHelper.init("" /* suite plan */, "" /* dynamic config url */);
+        }
+        return mBuildHelper;
+    }
+
     public static void main(String[] args) throws InterruptedException, ConfigurationException {
-        CompatibilityConsole console = new CompatibilityConsole();
+        Console console = new CompatibilityConsole();
         Console.startConsole(console, args);
     }
 }
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/IInvocationResultRepo.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/IInvocationResultRepo.java
new file mode 100644
index 0000000..c07223b0
--- /dev/null
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/IInvocationResultRepo.java
@@ -0,0 +1,42 @@
+/*
+ * 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.result;
+
+import com.android.compatibility.common.util.IInvocationResult;
+import com.android.compatibility.common.util.InvocationResult;
+
+import java.util.List;
+
+/**
+ * Repository for Compatibility results.
+ */
+public interface IInvocationResultRepo {
+
+    /**
+     * @return the list of {@link IInvocationResult}s. The index is its session id
+     */
+    List<IInvocationResult> getResults();
+
+    /**
+     * Get the {@link IInvocationResult} for given session id.
+     *
+     * @param sessionId the session id
+     * @return the {@link InvocationResult} or <code>null</null> if the result with that session id
+     * cannot be retrieved
+     */
+    IInvocationResult getResult(int sessionId);
+
+}
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/IModuleListener.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/IModuleListener.java
new file mode 100644
index 0000000..213d293
--- /dev/null
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/IModuleListener.java
@@ -0,0 +1,27 @@
+/*
+ * 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.result;
+
+import com.android.tradefed.result.ITestInvocationListener;
+
+/**
+ * Listener for Compatibility tests.
+ * <p>
+ * This listener wraps around the normal listener to convert from module name to module id.
+ */
+public interface IModuleListener extends ITestInvocationListener {
+
+}
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/InvocationResultRepo.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/InvocationResultRepo.java
new file mode 100644
index 0000000..f714ba6
--- /dev/null
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/InvocationResultRepo.java
@@ -0,0 +1,63 @@
+/*
+ * 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.result;
+
+import com.android.compatibility.common.util.IInvocationResult;
+import com.android.compatibility.common.util.XmlResultHandler;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * An implementation of {@link IInvocationResultRepo}.
+ */
+public class InvocationResultRepo implements IInvocationResultRepo {
+
+    /**
+     * Ordered list of result directories. the index of each file is its session id.
+     */
+    private List<IInvocationResult> mResults;
+
+    /**
+     * Create a {@link InvocationResultRepo} from a directory of results
+     *
+     * @param testResultDir the parent directory of results
+     */
+    public InvocationResultRepo(File testResultDir) {
+        mResults = XmlResultHandler.getResults(testResultDir);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public List<IInvocationResult> getResults() {
+        return new ArrayList<IInvocationResult>(mResults);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public IInvocationResult getResult(int sessionId) {
+        if (sessionId < 0 || sessionId >= mResults.size()) {
+            return null;
+        }
+        return mResults.get(sessionId);
+    }
+
+}
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/ModuleListener.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/ModuleListener.java
new file mode 100644
index 0000000..5b2a3fe
--- /dev/null
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/ModuleListener.java
@@ -0,0 +1,158 @@
+package com.android.compatibility.common.tradefed.result;
+
+import com.android.compatibility.common.tradefed.testtype.IModuleDef;
+import com.android.ddmlib.testrunner.TestIdentifier;
+import com.android.tradefed.build.IBuildInfo;
+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.result.TestSummary;
+
+import java.util.Map;
+
+/**
+ * Listener for Compatibility test info.
+ * <p>
+ * This listener wraps around the normal listener to convert from module name to module id.
+ */
+public class ModuleListener implements IModuleListener {
+
+    private IModuleDef mModule;
+    private ITestInvocationListener mListener;
+
+    /**
+     * @param module
+     * @param listener
+     */
+    public ModuleListener(IModuleDef module, ITestInvocationListener listener) {
+        mModule = module;
+        mListener = listener;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void invocationStarted(IBuildInfo buildInfo) {
+        CLog.d("ModuleListener.invocationStarted(%s)", buildInfo.toString());
+        mListener.invocationStarted(buildInfo);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void testRunStarted(String name, int numTests) {
+        CLog.d("ModuleListener.testRunStarted(%s, %d)", name, numTests);
+        mListener.testRunStarted(mModule.getId(), numTests);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void testStarted(TestIdentifier test) {
+        CLog.d("ModuleListener.testStarted(%s)", test.toString());
+        mListener.testStarted(test);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void testEnded(TestIdentifier test, Map<String, String> metrics) {
+        CLog.d("ModuleListener.testEnded(%s, %s)", test.toString(), metrics.toString());
+        mListener.testEnded(test, metrics);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void testIgnored(TestIdentifier test) {
+        CLog.d("ModuleListener.testIgnored(%s)", test.toString());
+        mListener.testIgnored(test);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void testFailed(TestIdentifier test, String trace) {
+        CLog.d("ModuleListener.testFailed(%s, %s)", test.toString(), trace);
+        mListener.testFailed(test, trace);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void testAssumptionFailure(TestIdentifier test, String trace) {
+        CLog.d("ModuleListener.testAssumptionFailure(%s, %s)", test.toString(), trace);
+        mListener.testAssumptionFailure(test, trace);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void testRunStopped(long elapsedTime) {
+        CLog.d("ModuleListener.testRunStopped(%d)", elapsedTime);
+        mListener.testRunStopped(elapsedTime);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void testRunEnded(long elapsedTime, Map<String, String> metrics) {
+        CLog.d("ModuleListener.testRunEnded(%d, %s)", elapsedTime, metrics.toString());
+        mListener.testRunEnded(elapsedTime, metrics);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void testRunFailed(String id) {
+        CLog.d("ModuleListener.testRunFailed(%s)", id);
+        mListener.testRunFailed(mModule.getId());
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public TestSummary getSummary() {
+        return mListener.getSummary();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void invocationEnded(long elapsedTime) {
+        CLog.d("ModuleListener.invocationEnded(%d)", elapsedTime);
+        mListener.invocationEnded(elapsedTime);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void invocationFailed(Throwable cause) {
+        CLog.d("ModuleListener.invocationFailed(%s)", cause.toString());
+        mListener.invocationFailed(cause);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void testLog(String name, LogDataType type, InputStreamSource stream) {
+        CLog.d("ModuleListener.testLog(%s, %s, %s)", name, type.toString(), stream.toString());
+        mListener.testLog(name, type, stream);
+    }
+
+}
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
new file mode 100644
index 0000000..bd0fba2
--- /dev/null
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/ResultReporter.java
@@ -0,0 +1,495 @@
+package com.android.compatibility.common.tradefed.result;
+
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.compatibility.common.tradefed.testtype.CompatibilityTest;
+import com.android.compatibility.common.util.AbiUtils;
+import com.android.compatibility.common.util.ICaseResult;
+import com.android.compatibility.common.util.IInvocationResult;
+import com.android.compatibility.common.util.IModuleResult;
+import com.android.compatibility.common.util.ITestResult;
+import com.android.compatibility.common.util.InvocationResult;
+import com.android.compatibility.common.util.MetricsStore;
+import com.android.compatibility.common.util.ReportLog;
+import com.android.compatibility.common.util.ResultUploader;
+import com.android.compatibility.common.util.TestStatus;
+import com.android.compatibility.common.util.XmlResultHandler;
+import com.android.ddmlib.Log;
+import com.android.ddmlib.Log.LogLevel;
+import com.android.ddmlib.testrunner.TestIdentifier;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.build.IFolderBuildInfo;
+import com.android.tradefed.config.Option;
+import com.android.tradefed.config.Option.Importance;
+import com.android.tradefed.config.OptionClass;
+import com.android.tradefed.log.LogUtil.CLog;
+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;
+import com.android.tradefed.result.LogFile;
+import com.android.tradefed.result.LogFileSaver;
+import com.android.tradefed.result.TestSummary;
+import com.android.tradefed.util.FileUtil;
+import com.android.tradefed.util.StreamUtil;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Reporter for Compatibility test results.
+ */
+@OptionClass(alias="result-reporter")
+public class ResultReporter implements ILogSaverListener, ITestInvocationListener,
+       ITestSummaryListener {
+
+    private static final String RESULT_KEY = "COMPATIBILITY_TEST_RESULT";
+    private static final String DEVICE_INFO = "DEVICE_INFO_";
+    private static final String DEVICE_INFO_EXT = ".deviceinfo.json";
+    private static final String DEVICE_INFO_PACKAGE = "com.android.compatibility.common.deviceinfo";
+    private static final String[] RESULT_RESOURCES = {
+        "compatibility-result.css",
+        "compatibility-result.xsd",
+        "compatibility-result.xsl",
+        "logo.png",
+        "newrule-green.png"};
+
+    @Option(name = CompatibilityTest.RETRY_OPTION,
+            shortName = 'r',
+            description = "retry a previous session.",
+            importance = Importance.IF_UNSET)
+    private Integer mRetrySessionId = null;
+
+    @Option(name = "quiet-output", description = "Mute display of test results.")
+    private boolean mQuietOutput = false;
+
+    @Option(name = "result-server", description = "Server to publish test results.")
+    private String mResultServer;
+
+    @Option(name = "include-test-log-tags", description = "Include test log tags in report.")
+    private boolean mIncludeTestLogTags = false;
+
+    @Option(name = "use-log-saver", description = "Also saves generated result with log saver")
+    private boolean mUseLogSaver = false;
+
+    private String mDeviceSerial;
+
+    private boolean mInitialized;
+    private IInvocationResult mResult;
+    private File mResultDir = null;
+    private File mLogDir = null;
+    private long mStartTime;
+    private boolean mIsDeviceInfoRun;
+    private ResultUploader mUploader;
+    private String mReferenceUrl;
+    private IModuleResult mCurrentModuleResult;
+    private ICaseResult mCurrentCaseResult;
+    private ITestResult mCurrentResult;
+    private IFolderBuildInfo mBuild;
+    private CompatibilityBuildHelper mBuildHelper;
+    private ILogSaver mLogSaver;
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void invocationStarted(IBuildInfo buildInfo) {
+        mInitialized = false;
+        mBuild = (IFolderBuildInfo) buildInfo;
+        mBuildHelper = new CompatibilityBuildHelper(mBuild);
+        mDeviceSerial = buildInfo.getDeviceSerial();
+        if (mDeviceSerial == null) {
+            mDeviceSerial = "unknown_device";
+        }
+        long time = System.currentTimeMillis();
+        String dirSuffix = getDirSuffix(time);
+        if (mRetrySessionId != null) {
+            Log.d(mDeviceSerial, String.format("Retrying session %d", mRetrySessionId));
+            List<IInvocationResult> results = null;
+            try {
+                results = XmlResultHandler.getResults(mBuildHelper.getResultsDir());
+            } catch (FileNotFoundException e) {
+                e.printStackTrace();
+            }
+            if (results != null && mRetrySessionId >= 0 && mRetrySessionId < results.size()) {
+                mResult = results.get(mRetrySessionId);
+            } else {
+                throw new IllegalArgumentException(
+                        String.format("Could not find session %d",mRetrySessionId));
+            }
+            mStartTime = mResult.getStartTime();
+            mResultDir = mResult.getResultDir();
+        } else {
+            mStartTime = time;
+            try {
+                mResultDir = new File(mBuildHelper.getResultsDir(), dirSuffix);
+            } catch (FileNotFoundException e) {
+                e.printStackTrace();
+            }
+            if (mResultDir != null && mResultDir.mkdirs()) {
+                logResult("Created result dir %s", mResultDir.getAbsolutePath());
+            } else {
+                throw new IllegalArgumentException(String.format("Could not create result dir %s",
+                        mResultDir.getAbsolutePath()));
+            }
+            mResult = new InvocationResult(mStartTime, mResultDir);
+        }
+        mBuildHelper.setResultDir(mResultDir.getName());
+        mUploader = new ResultUploader(mResultServer, mBuildHelper.getSuiteName());
+        try {
+            mLogDir = new File(mBuildHelper.getLogsDir(), dirSuffix);
+        } catch (FileNotFoundException e) {
+            e.printStackTrace();
+        }
+        if (mLogDir != null && mLogDir.mkdirs()) {
+            logResult("Created log dir %s", mLogDir.getAbsolutePath());
+        } else {
+            throw new IllegalArgumentException(String.format("Could not create log dir %s",
+                    mLogDir.getAbsolutePath()));
+        }
+        mInitialized = true;
+    }
+
+    /**
+     * @return a {@link String} to use for directory suffixes created from the given time.
+     */
+    private String getDirSuffix(long time) {
+        return new SimpleDateFormat("yyyy.MM.dd_HH.mm.ss").format(new Date(time));
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void testRunStarted(String id, int numTests) {
+        logResult("Starting %s with %d tests", id, numTests);
+        mIsDeviceInfoRun = AbiUtils.parseTestName(id).equals(DEVICE_INFO_PACKAGE);
+        if (mIsDeviceInfoRun) {
+            return;
+        }
+        mCurrentModuleResult = mResult.getOrCreateModule(id);
+        mCurrentModuleResult.setDeviceSerial(mDeviceSerial);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void testStarted(TestIdentifier test) {
+        if (mIsDeviceInfoRun) {
+            return;
+        }
+        mCurrentCaseResult = mCurrentModuleResult.getOrCreateResult(test.getClassName());
+        mCurrentResult = mCurrentCaseResult.getOrCreateResult(test.getTestName());
+        // Reset the result
+        mCurrentResult.resetResult();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void testEnded(TestIdentifier test, Map<String, String> metrics) {
+        if (mIsDeviceInfoRun) {
+            return;
+        }
+        // device test can have performance results in test metrics
+        String perfResult = metrics.get(RESULT_KEY);
+        ReportLog report = null;
+        if (perfResult != null) {
+            try {
+                report = ReportLog.parse(perfResult);
+            } catch (XmlPullParserException | IOException e) {
+                e.printStackTrace();
+            }
+        } else {
+            // host test should be checked into MetricsStore.
+            report = MetricsStore.removeResult(
+                    mDeviceSerial, mCurrentModuleResult.getAbi(), test.toString());
+        }
+        mCurrentResult.passed(report);
+        logResult("%s passed", test);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void testIgnored(TestIdentifier test) {
+        logResult("%s ignored", test);
+        // ignore
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void testFailed(TestIdentifier test, String trace) {
+        if (mIsDeviceInfoRun) {
+            return;
+        }
+        mCurrentResult.failed(trace);
+        logResult("%s failed: %s", test, trace);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void testAssumptionFailure(TestIdentifier test, String trace) {
+        if (mIsDeviceInfoRun) {
+            return;
+        }
+        mCurrentResult.failed(trace);
+        logResult("%s failed assumption: %s", test, trace);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void testRunStopped(long elapsedTime) {
+        logResult("%s stopped after %dms", mCurrentModuleResult.getId(), elapsedTime);
+        // ignore
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void testRunEnded(long elapsedTime, Map<String, String> metrics) {
+        if (mIsDeviceInfoRun) {
+            for (Map.Entry<String, String> metricEntry : metrics.entrySet()) {
+                String key = metricEntry.getKey();
+                String value = metricEntry.getValue();
+                if (key.startsWith(DEVICE_INFO)) {
+                    // Device info needs to be in the report
+                    mResult.addDeviceInfo(key.substring(DEVICE_INFO.length()), value);
+                } else if (!value.endsWith(DEVICE_INFO_EXT)) {
+                    Log.logAndDisplay(LogLevel.INFO, mDeviceSerial,
+                            String.format("%s failed: %s", metricEntry.getKey(), value));
+                }
+            }
+        } else {
+            logResult("%s completed in %dms. %d passed, %d failed, %d non executed",
+                    mCurrentModuleResult.getId(),
+                    elapsedTime,
+                    mCurrentModuleResult.countResults(TestStatus.PASS),
+                    mCurrentModuleResult.countResults(TestStatus.FAIL),
+                    mCurrentModuleResult.countResults(TestStatus.NOT_EXECUTED));
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void testRunFailed(String id) {
+        logResult("%s failed to run", id);
+        // ignore
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public TestSummary getSummary() {
+        // ignore
+        return null;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void putSummary(List<TestSummary> summaries) {
+        if (summaries.size() > 0) {
+            mReferenceUrl = summaries.get(0).getSummary().getString();
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void invocationEnded(long elapsedTime) {
+        logResult("ResultReporter.invocationEnded(%d)", elapsedTime);
+        if (mInitialized) {
+            logResult("Invocation completed in %dms. %d passed, %d failed, %d non executed",
+                    elapsedTime,
+                    mResult.countResults(TestStatus.PASS),
+                    mResult.countResults(TestStatus.FAIL),
+                    mResult.countResults(TestStatus.NOT_EXECUTED));
+            try {
+                File resultFile = XmlResultHandler.writeResults(mBuildHelper.getSuiteName(),
+                        mBuildHelper.getSuiteVersion(), mBuildHelper.getSuitePlan(), mResult,
+                        mResultDir, mStartTime, elapsedTime + mStartTime);
+                copyDynamicConfigFiles(mBuildHelper.getDynamicConfigFiles(), mResultDir);
+                copyFormattingFiles(mResultDir);
+                zipResults(mResultDir);
+                if (mUseLogSaver) {
+                    FileInputStream fis = null;
+                    try {
+                        fis = new FileInputStream(resultFile);
+                        mLogSaver.saveLogData("log-result", LogDataType.XML, fis);
+                    } catch (IOException ioe) {
+                        Log.e(mDeviceSerial, "error saving XML with log saver");
+                        Log.e(mDeviceSerial, ioe);
+                    } finally {
+                        StreamUtil.close(fis);
+                    }
+                }
+                if (mResultServer != null && !mResultServer.trim().isEmpty()) {
+                    mUploader.uploadResult(resultFile, mReferenceUrl);
+                }
+            } catch (IOException | XmlPullParserException e) {
+                CLog.e(e);
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void invocationFailed(Throwable cause) {
+        logResult("ResultReporter.invocationFailed(%s)", cause);
+        mInitialized = false;
+        // Clean up
+        mResultDir.delete();
+        mResultDir = null;
+        mLogDir.delete();
+        mLogDir = null;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void testLog(String name, LogDataType type, InputStreamSource stream) {
+        try {
+            LogFileSaver saver = new LogFileSaver(mLogDir);
+            File logFile = saver.saveAndZipLogData(name, type, stream.createInputStream());
+            logResult("Saved logs for %s in %s", name, logFile.getAbsolutePath());
+        } catch (IOException e) {
+            logResult("Failed to write log for %s", name);
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void testLogSaved(String dataName, LogDataType dataType, InputStreamSource dataStream,
+            LogFile logFile) {
+        if (mIncludeTestLogTags && mCurrentResult != null
+                && dataName.startsWith(mCurrentResult.getFullName())) {
+            if (dataType == LogDataType.BUGREPORT) {
+                mCurrentResult.setBugReport(logFile.getUrl());
+            } else if (dataType == LogDataType.LOGCAT) {
+                mCurrentResult.setLog(logFile.getUrl());
+            } else if (dataType == LogDataType.PNG) {
+                mCurrentResult.setScreenshot(logFile.getUrl());
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void setLogSaver(ILogSaver saver) {
+        mLogSaver = saver;
+    }
+
+    /**
+     * Copy the xml formatting files stored in this jar to the results directory
+     *
+     * @param resultsDir
+     */
+    static void copyFormattingFiles(File resultsDir) {
+        for (String resultFileName : RESULT_RESOURCES) {
+            InputStream configStream = XmlResultHandler.class.getResourceAsStream(
+                    String.format("/report/%s", resultFileName));
+            if (configStream != null) {
+                File resultFile = new File(resultsDir, resultFileName);
+                try {
+                    FileUtil.writeToFile(configStream, resultFile);
+                } catch (IOException e) {
+                    Log.w(ResultReporter.class.getSimpleName(),
+                            String.format("Failed to write %s to file", resultFileName));
+                }
+            } else {
+                Log.w(ResultReporter.class.getSimpleName(),
+                        String.format("Failed to load %s from jar", resultFileName));
+            }
+        }
+    }
+
+    /**
+     * move the dynamic config files to the results directory
+     *
+     * @param configFiles
+     * @param resultsDir
+     */
+    static void copyDynamicConfigFiles(Map<String, File> configFiles, File resultsDir) {
+        if (configFiles.size() == 0) return;
+
+        File folder = new File(resultsDir, "config");
+        folder.mkdir();
+        for (String moduleName : configFiles.keySet()) {
+            File resultFile = new File(folder, moduleName+".dynamic");
+            try {
+                FileUtil.copyFile(configFiles.get(moduleName), resultFile);
+                FileUtil.deleteFile(configFiles.get(moduleName));
+            } catch (IOException e) {
+                Log.w(ResultReporter.class.getSimpleName(),
+                        String.format("Failed to copy config file for %s to file", moduleName));
+            }
+        }
+    }
+
+    /**
+     * Zip the contents of the given results directory.
+     *
+     * @param resultsDir
+     */
+    @SuppressWarnings("deprecation")
+    private static void zipResults(File resultsDir) {
+        try {
+            // create a file in parent directory, with same name as resultsDir
+            File zipResultFile = new File(resultsDir.getParent(), String.format("%s.zip",
+                    resultsDir.getName()));
+            FileUtil.createZip(resultsDir, zipResultFile);
+        } catch (IOException e) {
+            Log.w(ResultReporter.class.getSimpleName(),
+                    String.format("Failed to create zip for %s", resultsDir.getName()));
+        }
+    }
+
+    /**
+     * @return the mResult
+     */
+    public IInvocationResult getResult() {
+        return mResult;
+    }
+
+    private void logResult(String format, Object... args) {
+        if (mQuietOutput) {
+            CLog.i(format, args);
+        } else {
+            Log.logAndDisplay(LogLevel.INFO, mDeviceSerial, String.format(format, args));
+        }
+    }
+}
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/ApkInstaller.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/ApkInstaller.java
new file mode 100644
index 0000000..076f902
--- /dev/null
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/ApkInstaller.java
@@ -0,0 +1,60 @@
+/*
+ * 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.build.IFolderBuildInfo;
+import com.android.tradefed.config.OptionClass;
+import com.android.tradefed.targetprep.TargetSetupError;
+import com.android.tradefed.targetprep.TestAppInstallSetup;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+
+/**
+ * Installs specified APKs from Compatibility repository.
+ */
+@OptionClass(alias="apk-installer")
+public class ApkInstaller extends TestAppInstallSetup {
+
+    private CompatibilityBuildHelper mBuildHelper = null;
+
+    protected File getTestsDir(IFolderBuildInfo buildInfo) throws FileNotFoundException {
+        if (mBuildHelper == null) {
+            mBuildHelper = new CompatibilityBuildHelper(buildInfo);
+        }
+        return mBuildHelper.getTestsDir();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected File getLocalPathForFilename(IBuildInfo buildInfo, String apkFileName)
+            throws TargetSetupError {
+        File apkFile = null;
+        try {
+            apkFile = new File(getTestsDir((IFolderBuildInfo) buildInfo), apkFileName);
+            if (!apkFile.isFile()) {
+                throw new FileNotFoundException();
+            }
+        } catch (FileNotFoundException e) {
+            throw new TargetSetupError(String.format("%s not found", apkFileName), e);
+        }
+        return apkFile;
+    }
+}
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
new file mode 100644
index 0000000..3284db0
--- /dev/null
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/DynamicConfigPusher.java
@@ -0,0 +1,164 @@
+/*
+ * 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.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.build.IFolderBuildInfo;
+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.targetprep.BuildError;
+import com.android.tradefed.targetprep.ITargetCleaner;
+import com.android.tradefed.targetprep.TargetSetupError;
+import com.android.tradefed.util.FileUtil;
+import com.android.tradefed.util.StreamUtil;
+
+import org.json.JSONException;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.net.URL;
+
+/**
+ * Pushes dynamic config files from config repository
+ */
+@OptionClass(alias="dynamic-config-pusher")
+public class DynamicConfigPusher implements ITargetCleaner {
+    public enum TestTarget {
+        DEVICE,
+        HOST
+    }
+
+    private static final String LOG_TAG = DynamicConfigPusher.class.getSimpleName();
+
+    @Option(name = "cleanup", description = "Whether to clean up config files after test is done.")
+    private boolean mCleanup = true;
+
+    @Option(name="module-name", description = "Specify the module name")
+    private String mModuleName;
+
+    @Option(name = "target", description = "Specify the target, 'device' or 'host'")
+    private TestTarget mTarget;
+
+    @Option(name = "version-name", description = "Specify the version for override config")
+    private static String mVersion;
+
+
+    private String mFilePushed;
+
+    void setModuleName(String moduleName) {
+        mModuleName = moduleName;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void setUp(ITestDevice device, IBuildInfo buildInfo) throws TargetSetupError, BuildError,
+            DeviceNotAvailableException {
+
+        CompatibilityBuildHelper buildHelper =
+                new CompatibilityBuildHelper((IFolderBuildInfo) buildInfo);
+
+        File localConfigFile = null;
+        try {
+            localConfigFile = DynamicConfig.getConfigFile(buildHelper.getTestsDir(), mModuleName);
+        } catch (FileNotFoundException e) {
+            throw new TargetSetupError(
+                    "Cannot get local dynamic config file from test directory", e);
+        }
+
+        String apfeConfigInJson = null;
+        String OriginUrl = buildHelper.getDynamicConfigUrl();
+
+        if (OriginUrl != null) {
+            try {
+                String requestUrl = OriginUrl
+                        .replace("{module-name}", mModuleName).replace("{version-name}", 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");
+            }
+        } else {
+            LogUtil.printLog(Log.LogLevel.INFO, LOG_TAG,
+                    "Dynamic config override Url is not set");
+        }
+
+        File src = null;
+        try {
+            src = DynamicConfigHandler.getMergedDynamicConfigFile(
+                    localConfigFile, apfeConfigInJson, mModuleName);
+        } catch (IOException | XmlPullParserException | JSONException e) {
+            throw new TargetSetupError("Cannot get merged dynamic config file", e);
+        }
+
+        switch (mTarget) {
+            case DEVICE:
+                String deviceDest = DynamicConfig.CONFIG_FOLDER_ON_DEVICE + src.getName();
+                if (!device.pushFile(src, deviceDest)) {
+                    throw new TargetSetupError(String.format(
+                            "Failed to push local '%s' to remote '%s'",
+                            src.getAbsolutePath(), deviceDest));
+                } else {
+                    mFilePushed = deviceDest;
+                    buildHelper.addDynamicConfigFile(mModuleName, src);
+                }
+                break;
+
+            case HOST:
+                File storageDir = new File(DynamicConfig.CONFIG_FOLDER_ON_HOST);
+                if (!storageDir.exists()) storageDir.mkdir();
+                File hostDest = new File(DynamicConfig.CONFIG_FOLDER_ON_HOST + src.getName());
+                try {
+                    FileUtil.copyFile(src, hostDest);
+                } catch (IOException e) {
+                    throw new TargetSetupError(String.format("Failed to copy file from %s to %s",
+                            src.getAbsolutePath(), hostDest.getAbsolutePath()), e);
+                }
+                mFilePushed = hostDest.getAbsolutePath();
+                buildHelper.addDynamicConfigFile(mModuleName, src);
+                break;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void tearDown(ITestDevice device, IBuildInfo buildInfo, Throwable e)
+            throws DeviceNotAvailableException {
+        switch (mTarget) {
+            case DEVICE:
+                if (!(e instanceof DeviceNotAvailableException)
+                        && mCleanup && mFilePushed != null) {
+                    device.executeShellCommand("rm -r " + mFilePushed);
+                }
+                break;
+            case HOST:
+                new File(mFilePushed).delete();
+        }
+    }
+}
\ No newline at end of file
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
new file mode 100644
index 0000000..ce19c8d
--- /dev/null
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/FilePusher.java
@@ -0,0 +1,54 @@
+/*
+ * 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.build.IFolderBuildInfo;
+import com.android.tradefed.config.OptionClass;
+import com.android.tradefed.targetprep.PushFilePreparer;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+
+/**
+ * Pushes specified testing artifacts from Compatibility repository.
+ */
+@OptionClass(alias="file-pusher")
+public class FilePusher extends PushFilePreparer {
+
+    private CompatibilityBuildHelper mBuildHelper = null;
+
+    protected File getTestsDir(IFolderBuildInfo buildInfo) throws FileNotFoundException {
+        if (mBuildHelper == null) {
+            mBuildHelper = new CompatibilityBuildHelper(buildInfo);
+        }
+        return mBuildHelper.getTestsDir();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public File resolveRelativeFilePath(IBuildInfo buildInfo, String fileName) {
+        try {
+            return new File(getTestsDir((IFolderBuildInfo) buildInfo), fileName);
+        } catch (FileNotFoundException e) {
+            e.printStackTrace();
+        }
+        return null;
+    }
+}
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
new file mode 100644
index 0000000..ec11601
--- /dev/null
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/ResultFilePuller.java
@@ -0,0 +1,119 @@
+/*
+ * 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.compatibility.common.util.DynamicConfig;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.build.IFolderBuildInfo;
+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 com.android.tradefed.util.FileUtil;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+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 {
+
+    private static final String LOG_TAG = ResultFilePuller.class.getSimpleName();
+
+    @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((IFolderBuildInfo) buildInfo);
+        try {
+            File resultDir = buildHelper.getResultDir();
+            if (mDestDir != null) {
+                resultDir = new File(resultDir, mDestDir);
+            }
+            resultDir.mkdirs();
+            if (!resultDir.isDirectory()) {
+                CLog.e(LOG_TAG, String.format("% 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(LOG_TAG, String.format("Failed to run %s", command));
+            }
+        } catch (Exception e) {
+            CLog.e(LOG_TAG, e);
+        }
+    }
+}
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/Abi.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/Abi.java
new file mode 100644
index 0000000..fab990e
--- /dev/null
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/Abi.java
@@ -0,0 +1,59 @@
+/*
+ * 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.testtype;
+
+import com.android.tradefed.testtype.IAbi;
+
+/**
+ * A class representing an ABI.
+ *
+ * TODO(stuartscott): should be in TradeFed.
+ */
+public class Abi implements IAbi {
+
+    private final String mName;
+    private final String mBitness;
+
+    public Abi(String name, String bitness) {
+        mName = name;
+        mBitness = bitness;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public String getName() {
+        return mName;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public String getBitness() {
+        return mBitness;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public String toString() {
+        return mName;
+    }
+
+}
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
new file mode 100644
index 0000000..79e6885
--- /dev/null
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/CompatibilityTest.java
@@ -0,0 +1,362 @@
+/*
+ * 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.testtype;
+
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.compatibility.common.tradefed.result.IInvocationResultRepo;
+import com.android.compatibility.common.tradefed.result.InvocationResultRepo;
+import com.android.compatibility.common.util.AbiUtils;
+import com.android.compatibility.common.util.ICaseResult;
+import com.android.compatibility.common.util.IInvocationResult;
+import com.android.compatibility.common.util.IModuleResult;
+import com.android.compatibility.common.util.ITestResult;
+import com.android.compatibility.common.util.TestFilter;
+import com.android.compatibility.common.util.TestStatus;
+import com.android.ddmlib.Log.LogLevel;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.build.IFolderBuildInfo;
+import com.android.tradefed.config.Option;
+import com.android.tradefed.config.Option.Importance;
+import com.android.tradefed.config.OptionClass;
+import com.android.tradefed.config.OptionCopier;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.result.ITestInvocationListener;
+import com.android.tradefed.testtype.IAbi;
+import com.android.tradefed.testtype.IBuildReceiver;
+import com.android.tradefed.testtype.IDeviceTest;
+import com.android.tradefed.testtype.IRemoteTest;
+import com.android.tradefed.testtype.IShardableTest;
+import com.android.tradefed.util.AbiFormatter;
+
+import java.io.FileNotFoundException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * A Test for running Compatibility Suites
+ */
+@OptionClass(alias="compatibility-test")
+public class CompatibilityTest implements IDeviceTest, IShardableTest, IBuildReceiver {
+
+    private static final String INCLUDE_FILTER_OPTION = "include-filter";
+    private static final String EXCLUDE_FILTER_OPTION = "exclude-filter";
+    private static final String PLAN_OPTION = "plan";
+    private static final String MODULE_OPTION = "module";
+    private static final String TEST_OPTION = "test";
+    public static final String RETRY_OPTION = "retry";
+    private static final String ABI_OPTION = "abi";
+    private static final String SHARD_OPTION = "shard";
+    private static final String URL = "dynamic-config-url";
+
+    private static final TestStatus[] RETRY_TEST_STATUS = new TestStatus[] {
+        TestStatus.FAIL,
+        TestStatus.NOT_EXECUTED
+    };
+
+    @Option(name = PLAN_OPTION,
+            description = "the test suite plan to run, such as \"everything\" or \"cts\"",
+            importance = Importance.ALWAYS)
+    private String mSuitePlan;
+
+    @Option(name = INCLUDE_FILTER_OPTION,
+            description = "the include module filters to apply.",
+            importance = Importance.ALWAYS)
+    private List<String> mIncludeFilters = new ArrayList<>();
+
+    @Option(name = EXCLUDE_FILTER_OPTION,
+            description = "the exclude module filters to apply.",
+            importance = Importance.ALWAYS)
+    private List<String> mExcludeFilters = new ArrayList<>();
+
+    @Option(name = MODULE_OPTION,
+            shortName = 'm',
+            description = "the test module to run.",
+            importance = Importance.IF_UNSET)
+    private String mModuleName = null;
+
+    @Option(name = TEST_OPTION,
+            shortName = 't',
+            description = "the test run.",
+            importance = Importance.IF_UNSET)
+    private String mTestName = null;
+
+    @Option(name = RETRY_OPTION,
+            shortName = 'r',
+            description = "retry a previous session.",
+            importance = Importance.IF_UNSET)
+    private Integer mRetrySessionId = null;
+
+    @Option(name = ABI_OPTION,
+            shortName = 'a',
+            description = "the abi to test.",
+            importance = Importance.IF_UNSET)
+    private String mAbiName = null;
+
+    @Option(name = SHARD_OPTION,
+            description = "split the modules up to run on multiple devices concurrently.")
+    private int mShards = 1;
+
+    @Option(name = URL,
+            description = "Specify the url for override config")
+    private String mURL;
+
+    @Option(name = "bugreport-on-failure", description =
+            "take a bugreport on every test failure. " +
+            "Warning: can potentially use a lot of disk space.")
+    private boolean mBugReportOnFailure = false;
+
+    @Option(name = "logcat-on-failure", description =
+            "take a logcat snapshot on every test failure.")
+    private boolean mLogcatOnFailure = false;
+
+    @Option(name = "screenshot-on-failure", description =
+            "take a screenshot on every test failure.")
+    private boolean mScreenshotOnFailure = false;
+
+    private int mShardAssignment;
+    private int mTotalShards;
+    private ITestDevice mDevice;
+    private IFolderBuildInfo mBuild;
+    private CompatibilityBuildHelper mBuildHelper;
+    private List<IModuleDef> mModules = new ArrayList<>();
+    private int mLastModuleIndex = 0;
+
+    /**
+     * Create a new {@link CompatibilityTest} that will run the default list of modules.
+     */
+    public CompatibilityTest() {
+        this(0 /*shardAssignment*/, 1 /*totalShards*/);
+    }
+
+    /**
+     * Create a new {@link CompatibilityTest} that will run a sublist of modules.
+     */
+    public CompatibilityTest(int shardAssignment, int totalShards) {
+        if (shardAssignment < 0) {
+            throw new IllegalArgumentException(
+                "shardAssignment cannot be negative. found:" + shardAssignment);
+        }
+        if (totalShards < 1) {
+            throw new IllegalArgumentException(
+                "shardAssignment must be at least 1. found:" + totalShards);
+        }
+        mShardAssignment = shardAssignment;
+        mTotalShards = totalShards;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public ITestDevice getDevice() {
+        return mDevice;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void setDevice(ITestDevice device) {
+        mDevice = device;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void setBuild(IBuildInfo buildInfo) {
+        mBuild = (IFolderBuildInfo) buildInfo;
+        mBuildHelper = new CompatibilityBuildHelper(mBuild);
+        mBuildHelper.init(mSuitePlan, mURL);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void run(ITestInvocationListener listener) throws DeviceNotAvailableException {
+        try {
+            Set<IAbi> abiSet = getAbis();
+            if (abiSet == null || abiSet.isEmpty()) {
+                if (mAbiName == null) {
+                    throw new IllegalArgumentException("Could not get device's ABIs");
+                } else {
+                    throw new IllegalArgumentException(String.format("Device %s does't support %s",
+                            mDevice.getSerialNumber(), mAbiName));
+                }
+            }
+            CLog.logAndDisplay(LogLevel.INFO, "ABIs: %s", abiSet);
+            setupTestModules(abiSet);
+
+            // TODO(stuartscott): Enable skipping of deviceinfo
+            // if (mSkipDeviceInfo) {
+            //   remove device info from modules
+            // }
+
+            listener = new FailureListener(listener, getDevice(), mBugReportOnFailure,
+                    mLogcatOnFailure, mScreenshotOnFailure);
+            int moduleCount = mModules.size();
+            CLog.logAndDisplay(LogLevel.INFO, "Start test run of %d module%s", moduleCount,
+                    (moduleCount > 1) ? "s" : "");
+
+            for (int i = mLastModuleIndex; i < moduleCount; i++) {
+                IModuleDef module = mModules.get(i);
+                module.setBuild(mBuild);
+                module.setDevice(mDevice);
+                module.run(listener);
+                // Track of the last complete test package index for resume
+                mLastModuleIndex = i;
+            }
+        } catch (DeviceNotAvailableException e) {
+            // Pass up
+            throw e;
+        } catch (RuntimeException e) {
+            CLog.logAndDisplay(LogLevel.ERROR, "Exception: %s", e.getMessage());
+            CLog.e(e);
+        } catch (Error e) {
+            CLog.logAndDisplay(LogLevel.ERROR, "Error: %s", e.getMessage());
+            CLog.e(e);
+        }
+    }
+
+    /**
+     * Set {@code mModules} to the list of test modules to run.
+     * @param abis
+     */
+    private void setupTestModules(Set<IAbi> abis) {
+        if (!mModules.isEmpty()) {
+            CLog.d("Resume tests using existing module list");
+            return;
+        }
+        if (mRetrySessionId != null) {
+            // We're retrying so clear the filters
+            mIncludeFilters.clear();
+            mExcludeFilters.clear();
+            mModuleName = null;
+            mTestName = null;
+            // Load the invocation result
+            IInvocationResultRepo repo;
+            IInvocationResult result = null;
+            try {
+                repo = new InvocationResultRepo(mBuildHelper.getResultsDir());
+                result = repo.getResult(mRetrySessionId);
+            } catch (FileNotFoundException e) {
+                e.printStackTrace();
+            }
+            if (result == null) {
+                throw new IllegalArgumentException(String.format(
+                        "Could not find session with id %d", mRetrySessionId));
+            }
+            // Append each test that failed or was not executed to the filters
+            for (IModuleResult module : result.getModules()) {
+                for (ICaseResult cr : module.getResults()) {
+                    for (TestStatus status : RETRY_TEST_STATUS) {
+                        for (ITestResult r : cr.getResults(status)) {
+                            // Create the filter for the test to be run.
+                            mIncludeFilters.add(new TestFilter(module.getAbi(), module.getName(),
+                                    r.getFullName()).toString());
+                        }
+                    }
+                }
+            }
+        }
+        if (mModuleName != null) {
+            mIncludeFilters.clear();
+            mIncludeFilters.add(new TestFilter(mAbiName, mModuleName, mTestName).toString());
+            if (mTestName != null) {
+                // We're filtering it down to the lowest level, no need to give excludes
+                mExcludeFilters.clear();
+            } else {
+                // If we dont specify a test name, we only want to run this module with any
+                // exclusions defined by the plan as long as they dont exclude the whole module.
+                List<String> excludeFilters = new ArrayList<>();
+                for (String excludeFilter : mExcludeFilters) {
+                    TestFilter filter = TestFilter.createFrom(excludeFilter);
+                    String name = filter.getName();
+                    // Add the filter if it applies to this module, and it has a test name
+                    if ((mModuleName.equals(name) || mModuleName.matches(name) ||
+                            name.matches(mModuleName)) && filter.getTest() != null) {
+                        excludeFilters.add(excludeFilter);
+                    }
+                }
+                mExcludeFilters = excludeFilters;
+            }
+        }
+        try {
+            // Collect ALL tests
+            IModuleRepo testRepo = new ModuleRepo(mBuildHelper.getTestsDir(), abis);
+            List<IModuleDef> modules = testRepo.getModules(mIncludeFilters, mExcludeFilters);
+
+            // Filter by shard
+            int numTestmodules = modules.size();
+            int totalShards = Math.min(mTotalShards, numTestmodules);
+
+            mModules.clear();
+            for (int i = mShardAssignment; i < numTestmodules; i += totalShards) {
+                mModules.add(modules.get(i));
+            }
+        } catch (FileNotFoundException e) {
+            CLog.e(e);
+        }
+    }
+
+    /**
+     * Gets the set of ABIs supported by both Compatibility and the device under test
+     * @return The set of ABIs to run the tests on
+     * @throws DeviceNotAvailableException
+     */
+    Set<IAbi> getAbis() throws DeviceNotAvailableException {
+        Set<IAbi> abis = new HashSet<>();
+        for (String abi : AbiFormatter.getSupportedAbis(mDevice, "")) {
+            // Only test against ABIs supported by Compatibility, and if the --abi option was given,
+            // it must match.
+            if (AbiUtils.isAbiSupportedByCompatibility(abi)
+                    && (mAbiName == null || mAbiName.equals(abi))) {
+                abis.add(new Abi(abi, AbiUtils.getBitness(abi)));
+            }
+        }
+        return abis;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public Collection<IRemoteTest> split() {
+        if (mShards <= 1) {
+            return null;
+        }
+
+        List<IRemoteTest> shardQueue = new LinkedList<>();
+        for (int shardAssignment = 0; shardAssignment < mShards; shardAssignment++) {
+            CompatibilityTest test = new CompatibilityTest(shardAssignment, mShards /* total */);
+            OptionCopier.copyOptionsNoThrow(this, test);
+            // Set the shard count because the copy option on the previous line copies
+            // over the mShard value
+            test.mShards = 0;
+            shardQueue.add(test);
+        }
+
+        return shardQueue;
+    }
+
+}
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/FailureListener.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/FailureListener.java
new file mode 100644
index 0000000..c3509d1
--- /dev/null
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/FailureListener.java
@@ -0,0 +1,81 @@
+/*
+ * 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.testtype;
+
+import com.android.ddmlib.testrunner.TestIdentifier;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.result.ITestInvocationListener;
+import com.android.tradefed.result.InputStreamSource;
+import com.android.tradefed.result.LogDataType;
+import com.android.tradefed.result.ResultForwarder;
+import com.android.tradefed.util.RunUtil;
+
+public class FailureListener extends ResultForwarder {
+
+    private static final int MAX_LOGCAT_BYTES = 500 * 1024; // 500K
+
+    private ITestDevice mDevice;
+    private boolean mBugReportOnFailure;
+    private boolean mLogcatOnFailure;
+    private boolean mScreenshotOnFailure;
+
+    public FailureListener(ITestInvocationListener listener, ITestDevice device,
+            boolean bugReportOnFailure, boolean logcatOnFailure, boolean screenshotOnFailure) {
+        super(listener);
+        mDevice = device;
+        mBugReportOnFailure = bugReportOnFailure;
+        mLogcatOnFailure = logcatOnFailure;
+        mScreenshotOnFailure = screenshotOnFailure;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void testFailed(TestIdentifier test, String trace) {
+        super.testFailed(test, trace);
+        CLog.i("FailureListener.testFailed %s %b %b %b", test.toString(), mBugReportOnFailure, mLogcatOnFailure, mScreenshotOnFailure);
+        if (mBugReportOnFailure) {
+           InputStreamSource bugSource = mDevice.getBugreport();
+           super.testLog(String.format("%s-bugreport", test.toString()), LogDataType.BUGREPORT,
+                   bugSource);
+           bugSource.cancel();
+        }
+        if (mLogcatOnFailure) {
+            // sleep 2s to ensure test failure stack trace makes it into logcat capture
+            RunUtil.getDefault().sleep(2 * 1000);
+            InputStreamSource logSource = mDevice.getLogcat(MAX_LOGCAT_BYTES);
+            super.testLog(String.format("%s-logcat", test.toString()), LogDataType.LOGCAT,
+                    logSource);
+            logSource.cancel();
+        }
+        if (mScreenshotOnFailure) {
+            try {
+                InputStreamSource screenSource = mDevice.getScreenshot();
+                super.testLog(String.format("%s-screenshot", test.toString()), LogDataType.PNG,
+                        screenSource);
+                screenSource.cancel();
+            } catch (DeviceNotAvailableException e) {
+                CLog.e(e);
+                CLog.e("Device %s became unavailable while capturing screenshot",
+                        mDevice.getSerialNumber());
+            }
+        }
+    }
+
+}
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
new file mode 100644
index 0000000..6555810
--- /dev/null
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/IModuleDef.java
@@ -0,0 +1,78 @@
+/*
+ * 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.testtype;
+
+import com.android.tradefed.targetprep.ITargetPreparer;
+import com.android.tradefed.testtype.IAbi;
+import com.android.tradefed.testtype.IBuildReceiver;
+import com.android.tradefed.testtype.IDeviceTest;
+import com.android.tradefed.testtype.IRemoteTest;
+
+import java.util.List;
+import java.util.regex.Pattern;
+
+/**
+ * Container for Compatibility test info.
+ */
+public interface IModuleDef extends Comparable<IModuleDef>, IBuildReceiver, IDeviceTest, IRemoteTest {
+
+    /**
+     * @return The name of this module.
+     */
+    String getName();
+
+    /**
+     * @return a {@link String} to uniquely identify this module.
+     */
+    String getId();
+
+    /**
+     * @return the abi of this test module.
+     */
+    IAbi getAbi();
+
+    /**
+     * @return a list of preparers used for setup or teardown of test cases in this module
+     */
+    List<ITargetPreparer> getPreparers();
+
+    /**
+     * @return a {@link List} of {@link IRemoteTest}s to run the test module.
+     */
+    List<IRemoteTest> getTests();
+
+    /**
+     * Adds a filter to include a specific test
+     *
+     * @param name the name of the test. Can be <package>, <package>.<class>,
+     * <package>.<class>#<method> or <native_name>
+     */
+    void addIncludeFilter(String name);
+
+    /**
+     * Adds a filter to exclude a specific test
+     *
+     * @param name the name of the test. Can be <package>, <package>.<class>,
+     * <package>.<class>#<method> or <native_name>
+     */
+    void addExcludeFilter(String name);
+
+    /**
+     * @return true iff this module's name matches the give regular expression pattern.
+     */
+    boolean nameMatches(Pattern pattern);
+
+}
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/IModuleRepo.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/IModuleRepo.java
new file mode 100644
index 0000000..40e944b
--- /dev/null
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/IModuleRepo.java
@@ -0,0 +1,65 @@
+/*
+ * 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.testtype;
+
+import com.android.compatibility.common.util.AbiUtils;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Interface for accessing tests from the Compatibility repository.
+ */
+public interface IModuleRepo {
+
+    /**
+     * Get a {@link IModuleDef} given an id.
+     *
+     * @param id the id of the module (created by {@link AbiUtils#createId(String, String)})
+     */
+    IModuleDef getModule(String id);
+
+    /**
+     * @return a {@link Map} of all modules in repo.
+     */
+    Map<String, IModuleDef> getModules();
+
+    /**
+     * @return a sorted {@link List} of {@link IModuleDef}s given the filters.
+     */
+    List<IModuleDef> getModules(List<String> includeFilters, List<String> excludeFilters);
+
+    /**
+     * @return a {@link Map} of all module in repo keyed by name.
+     */
+    Map<String, List<IModuleDef>> getModulesByName();
+
+    /**
+     * @return a sorted {@link List} of module names.
+     */
+    List<String> getModuleNames();
+
+    /**
+     * @return a {@link Set} of modules names that match the given regular expression.
+     */
+    Set<String> getModulesMatching(String regex);
+
+    /**
+     * @return a sorted {@link List} of module ids.
+     */
+    List<String> getModuleIds();
+}
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/ModuleDef.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/ModuleDef.java
new file mode 100644
index 0000000..7b6ec51
--- /dev/null
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/ModuleDef.java
@@ -0,0 +1,217 @@
+/*
+ * 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.testtype;
+
+import com.android.compatibility.common.tradefed.result.IModuleListener;
+import com.android.compatibility.common.tradefed.result.ModuleListener;
+import com.android.compatibility.common.util.AbiUtils;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.result.ITestInvocationListener;
+import com.android.tradefed.targetprep.BuildError;
+import com.android.tradefed.targetprep.ITargetCleaner;
+import com.android.tradefed.targetprep.ITargetPreparer;
+import com.android.tradefed.targetprep.TargetSetupError;
+import com.android.tradefed.testtype.IAbi;
+import com.android.tradefed.testtype.IAbiReceiver;
+import com.android.tradefed.testtype.IBuildReceiver;
+import com.android.tradefed.testtype.IDeviceTest;
+import com.android.tradefed.testtype.IRemoteTest;
+import com.android.tradefed.testtype.ITestFilterReceiver;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.regex.Pattern;
+
+/**
+ * Container for Compatibility test module info.
+ */
+public class ModuleDef implements IModuleDef {
+
+    private final String mId;
+    private final String mName;
+    private final IAbi mAbi;
+    private List<IRemoteTest> mTests = null;
+    private List<ITargetPreparer> mPreparers = null;
+    private IBuildInfo mBuild;
+    private ITestDevice mDevice;
+    private List<String> mIncludeFilters = new ArrayList<>();
+    private List<String> mExcludeFilters = new ArrayList<>();
+
+    public ModuleDef(String name, IAbi abi, List<IRemoteTest> tests,
+            List<ITargetPreparer> preparers) {
+        mId = AbiUtils.createId(abi.getName(), name);
+        mName = name;
+        mAbi = abi;
+        mTests = tests;
+        mPreparers = preparers;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public String getId() {
+        return mId;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public String getName() {
+        return mName;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public IAbi getAbi() {
+        return mAbi;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public List<IRemoteTest> getTests() {
+        return mTests;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public List<ITargetPreparer> getPreparers() {
+        return mPreparers;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void addIncludeFilter(String filter) {
+        mIncludeFilters.add(filter);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void addExcludeFilter(String filter) {
+        mExcludeFilters.add(filter);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int compareTo(IModuleDef moduleDef) {
+        return getName().compareTo(moduleDef.getName());
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public boolean nameMatches(Pattern pattern) {
+        return pattern.matcher(mName).matches();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void setBuild(IBuildInfo build) {
+        mBuild = build;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public ITestDevice getDevice() {
+        return mDevice;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void setDevice(ITestDevice device) {
+        mDevice = device;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void run(ITestInvocationListener listener) throws DeviceNotAvailableException {
+        IModuleListener moduleListener = new ModuleListener(this, listener);
+
+        List<ITargetCleaner> cleaners = new ArrayList<>();
+        // Setup
+        for (ITargetPreparer preparer : mPreparers) {
+            CLog.d("Preparer: %s", preparer.getClass().getSimpleName());
+            if (preparer instanceof IAbiReceiver) {
+                ((IAbiReceiver) preparer).setAbi(mAbi);
+            }
+            if (preparer instanceof ITargetCleaner) {
+                cleaners.add((ITargetCleaner) preparer);
+            }
+            try {
+                preparer.setUp(mDevice, mBuild);
+            } catch (BuildError e) {
+                // This should only happen for flashing new build
+                CLog.e("Unexpected BuildError from preparer: %s",
+                    preparer.getClass().getCanonicalName());
+            } catch (TargetSetupError e) {
+                // log preparer class then rethrow & let caller handle
+                CLog.e("TargetSetupError in preparer: %s",
+                    preparer.getClass().getCanonicalName());
+                throw new RuntimeException(e);
+            }
+        }
+        // Run tests
+        for (IRemoteTest test : mTests) {
+            CLog.d("Test: %s", test.getClass().getSimpleName());
+            if (test instanceof IAbiReceiver) {
+                ((IAbiReceiver) test).setAbi(mAbi);
+            }
+            if (test instanceof IBuildReceiver) {
+                ((IBuildReceiver) test).setBuild(mBuild);
+            }
+            if (test instanceof IDeviceTest) {
+                ((IDeviceTest) test).setDevice(mDevice);
+            }
+            if (test instanceof ITestFilterReceiver) {
+                ((ITestFilterReceiver) test).addAllIncludeFilters(mIncludeFilters);
+                ((ITestFilterReceiver) test).addAllExcludeFilters(mExcludeFilters);
+            }
+            test.run(moduleListener);
+        }
+        // Tear down - in reverse order
+        Collections.reverse(cleaners);
+        for (ITargetCleaner cleaner : cleaners) {
+            CLog.d("Cleaner: %s", cleaner.getClass().getSimpleName());
+            cleaner.tearDown(mDevice, mBuild, null);
+        }
+    }
+}
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
new file mode 100644
index 0000000..a9e8615
--- /dev/null
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/ModuleRepo.java
@@ -0,0 +1,253 @@
+/*
+ * 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.testtype;
+
+import com.android.compatibility.common.util.AbiUtils;
+import com.android.compatibility.common.util.TestFilter;
+import com.android.tradefed.config.Configuration;
+import com.android.tradefed.config.ConfigurationException;
+import com.android.tradefed.config.ConfigurationFactory;
+import com.android.tradefed.config.IConfigurationFactory;
+import com.android.tradefed.targetprep.ITargetPreparer;
+import com.android.tradefed.testtype.IAbi;
+import com.android.tradefed.testtype.IRemoteTest;
+
+import java.io.File;
+import java.io.FilenameFilter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Pattern;
+
+/**
+ * Retrieves Compatibility test module definitions from the repository.
+ */
+public class ModuleRepo implements IModuleRepo {
+
+    private static final String CONFIG_EXT = ".config";
+
+    /** mapping of module id to definition */
+    private final Map<String, IModuleDef> mModules;
+    private final Set<IAbi> mAbis;
+
+    /**
+     * Creates a {@link ModuleRepo}, initialized from provided build
+     */
+    public ModuleRepo(File testsDir, Set<IAbi> abis) {
+        this(new HashMap<String, IModuleDef>(), abis);
+        parse(testsDir);
+    }
+
+    /**
+     * Creates a {@link ModuleRepo}, initialized with the given modules
+     */
+    public ModuleRepo(Map<String, IModuleDef> modules, Set<IAbi> abis) {
+        mModules = modules;
+        mAbis = abis;
+    }
+
+    /**
+     * Builds mTestMap based on directory contents
+     */
+    private void parse(File dir) {
+        File[] configFiles = dir.listFiles(new ConfigFilter());
+        IConfigurationFactory configFactory = ConfigurationFactory.getInstance();
+        for (File configFile : configFiles) {
+            try {
+                // Invokes parser to process the test module config file
+                // Need to generate a different config for each ABI as we cannot guarantee the
+                // configs are idempotent. This however means we parse the same file multiple times.
+                for (IAbi abi : mAbis) {
+                    Configuration config = (Configuration) configFactory.createConfigurationFromArgs(
+                            new String[]{configFile.getAbsolutePath()});
+                    String name = configFile.getName().replace(CONFIG_EXT, "");
+                    List<IRemoteTest> tests = config.getTests();
+                    List<ITargetPreparer> preparers = config.getTargetPreparers();
+                    IModuleDef def = new ModuleDef(name, abi, tests, preparers);
+                    mModules.put(AbiUtils.createId(abi.getName(), name), def);
+                }
+            } catch (ConfigurationException e) {
+                throw new RuntimeException(String.format("error parsing config file: %s",
+                        configFile.getName()), e);
+            }
+        }
+    }
+
+    /**
+     * A {@link FilenameFilter} to find all the config files in a directory.
+     */
+    public static class ConfigFilter implements FilenameFilter {
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public boolean accept(File dir, String name) {
+            return name.endsWith(CONFIG_EXT);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public IModuleDef getModule(String id) {
+        return mModules.get(id);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public Map<String, IModuleDef> getModules() {
+        return mModules;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public Map<String, List<IModuleDef>> getModulesByName() {
+        Map<String, List<IModuleDef>> modules = new HashMap<>();
+        for (IModuleDef moduleDef : mModules.values()) {
+            String name = moduleDef.getName();
+            List<IModuleDef> defs = modules.get(name);
+            if (defs == null) {
+                defs = new ArrayList<IModuleDef>();
+                modules.put(name, defs);
+            }
+            defs.add(moduleDef);
+        }
+        return modules;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public List<String> getModuleIds() {
+        List<String> ids = new ArrayList<>(mModules.keySet());
+        Collections.sort(ids);
+        return ids;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public List<String> getModuleNames() {
+        Set<String> names = new HashSet<>();
+        for (IModuleDef moduleDef : mModules.values()) {
+            names.add(moduleDef.getName());
+        }
+        List<String> namesList = new ArrayList<>(names);
+        Collections.sort(namesList);
+        return namesList;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public Set<String> getModulesMatching(String regex) {
+        Set<String> names = new HashSet<>();
+        Pattern pattern = Pattern.compile(regex);
+        for (IModuleDef moduleDef : mModules.values()) {
+            if (moduleDef.nameMatches(pattern)) {
+                names.add(moduleDef.getName());
+            }
+        }
+        return names;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public List<IModuleDef> getModules(List<String> includeFilters, List<String> excludeFilters) {
+        Map<String, IModuleDef> moduleDefs = new HashMap<>();
+        Set<String> ids;
+        // Include all the inclusions
+        for (String filterString : includeFilters) {
+            TestFilter filter = TestFilter.createFrom(filterString);
+            String test = filter.getTest();
+            ids = getAllIds(filter);
+            for (String id : ids) {
+                IModuleDef module = getModule(id);
+                if (test != null) {
+                    // We're including a subset of tests
+                    module.addIncludeFilter(test);
+                }
+                moduleDefs.put(id, module);
+            }
+        }
+        // Exclude all the exclusions
+        for (String filterString : excludeFilters) {
+            TestFilter filter = TestFilter.createFrom(filterString);
+            String test = filter.getTest();
+            ids = getAllIds(filter);
+            // Iterate through all IDs
+            for (String id : ids) {
+                IModuleDef module = moduleDefs.get(id);
+                if (module != null) {
+                    if (test != null) {
+                        // Excluding a subset of tests, so keep module but give filter
+                        module.addExcludeFilter(test);
+                    } else {
+                        // Excluding all tests in the module so just remove the whole thing
+                        moduleDefs.remove(id);
+                    }
+                }
+            }
+        }
+        if (moduleDefs.isEmpty()) {
+            throw new IllegalStateException("Nothing to do. Use 'list modules' to see available"
+                    + " modules, and 'list results' to see available sessions to retry.");
+        }
+        // Note: run() relies on the fact that the list is reliably sorted for sharding purposes
+        List<IModuleDef> sortedModuleDefs = new ArrayList<>(moduleDefs.values());
+        Collections.sort(sortedModuleDefs);
+        return sortedModuleDefs;
+    }
+
+    /**
+     * Returns all IDs matching the given filter.
+     */
+    private Set<String> getAllIds(TestFilter filter) {
+        String abi = filter.getAbi();
+        String name = filter.getName();
+        Set<String> filteredNames = getModulesMatching(name);
+        if (filteredNames.isEmpty()) {
+            throw new IllegalArgumentException(String.format(
+                    "Not modules matching %s. Use 'list modules' to see available modules.",
+                    filter.getName()));
+        }
+        Set<String> ids = new HashSet<>();
+        for (String module : filteredNames) {
+            if (abi != null) {
+                ids.add(AbiUtils.createId(abi, module));
+            } else {
+                // ABI not specified, test on all ABIs
+                for (IAbi a : mAbis) ids.add(AbiUtils.createId(a.getName(), module));
+            }
+        }
+        return ids;
+    }
+}
diff --git a/common/host-side/tradefed/tests/Android.mk b/common/host-side/tradefed/tests/Android.mk
new file mode 100644
index 0000000..b07fa1e
--- /dev/null
+++ b/common/host-side/tradefed/tests/Android.mk
@@ -0,0 +1,43 @@
+# 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)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, ../src)
+LOCAL_JAVA_RESOURCE_DIRS := ../res
+
+LOCAL_SUITE_BUILD_NUMBER := 2
+LOCAL_SUITE_NAME := TESTS
+LOCAL_SUITE_FULLNAME := "Compatibility Tests"
+LOCAL_SUITE_VERSION := 1
+
+LOCAL_MODULE := compatibility-mock-tradefed
+
+include $(BUILD_COMPATIBILITY_SUITE)
+
+# Make the tests
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_MODULE := compatibility-tradefed-tests
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_JAVA_LIBRARIES := tradefed-prebuilt compatibility-mock-tradefed junit compatibility-host-util
+
+include $(BUILD_HOST_JAVA_LIBRARY)
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
new file mode 100644
index 0000000..588936a
--- /dev/null
+++ b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/UnitTests.java
@@ -0,0 +1,48 @@
+/*
+ * 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;
+
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelperTest;
+import com.android.compatibility.common.tradefed.command.CompatibilityConsoleTest;
+import com.android.compatibility.common.tradefed.result.ResultReporterTest;
+import com.android.compatibility.common.tradefed.testtype.CompatibilityTestTest;
+import com.android.compatibility.common.tradefed.testtype.ModuleDefTest;
+import com.android.compatibility.common.tradefed.testtype.ModuleRepoTest;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * 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.
+ */
+public class UnitTests extends TestSuite {
+
+    public UnitTests() {
+        super();
+        addTestSuite(CompatibilityBuildHelperTest.class);
+        addTestSuite(CompatibilityConsoleTest.class);
+        addTestSuite(ResultReporterTest.class);
+        addTestSuite(CompatibilityTestTest.class);
+        addTestSuite(ModuleDefTest.class);
+        addTestSuite(ModuleRepoTest.class);
+    }
+
+    public static Test suite() {
+        return new UnitTests();
+    }
+}
\ No newline at end of file
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
new file mode 100644
index 0000000..99847f5
--- /dev/null
+++ b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/build/CompatibilityBuildHelperTest.java
@@ -0,0 +1,145 @@
+/*
+ * 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.build;
+
+import com.android.tradefed.build.IFolderBuildInfo;
+import com.android.tradefed.util.FileUtil;
+
+import junit.framework.TestCase;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+
+public class CompatibilityBuildHelperTest extends TestCase {
+
+    private static final String ROOT_PROPERTY = "TESTS_ROOT";
+    private static final String BUILD_NUMBER = "2";
+    private static final String SUITE_NAME = "TESTS";
+    private static final String SUITE_FULL_NAME = "Compatibility Tests";
+    private static final String SUITE_VERSION = "1";
+    private static final String SUITE_PLAN = "cts";
+    private static final String DYNAMIC_CONFIG_URL = "";
+    private static final String ROOT_DIR_NAME = "root";
+    private static final String BASE_DIR_NAME = "android-tests";
+    private static final String TESTCASES = "testcases";
+
+    private File mRoot = null;
+    private File mBase = null;
+    private File mTests = null;
+    private IFolderBuildInfo mBuild;
+    private CompatibilityBuildHelper mHelper;
+
+    @Override
+    public void setUp() throws Exception {
+        mRoot = FileUtil.createTempDir(ROOT_DIR_NAME);
+        CompatibilityBuildProvider provider = new CompatibilityBuildProvider();
+        mBuild = (IFolderBuildInfo) provider.getBuild();
+        mHelper = new CompatibilityBuildHelper(mBuild);
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+        setProperty(null);
+        FileUtil.recursiveDelete(mRoot);
+        mRoot = null;
+        mBase = null;
+        mTests = null;
+    }
+
+    private void createDirStructure() {
+        mBase = new File(mRoot, BASE_DIR_NAME);
+        mBase.mkdirs();
+        mTests = new File(mBase, TESTCASES);
+        mTests.mkdirs();
+    }
+
+    public void testSuiteInfoLoad() throws Exception {
+        setProperty(mRoot.getAbsolutePath());
+        mHelper.init(SUITE_PLAN, DYNAMIC_CONFIG_URL);
+        assertEquals("Incorrect suite build number", BUILD_NUMBER, mHelper.getSuiteBuild());
+        assertEquals("Incorrect suite name", SUITE_NAME, mHelper.getSuiteName());
+        assertEquals("Incorrect suite full name", SUITE_FULL_NAME, mHelper.getSuiteFullName());
+        assertEquals("Incorrect suite version", SUITE_VERSION, mHelper.getSuiteVersion());
+    }
+
+    public void testProperty() throws Exception {
+        setProperty(null);
+        CompatibilityBuildProvider provider = new CompatibilityBuildProvider();
+        IFolderBuildInfo info = (IFolderBuildInfo) provider.getBuild();
+        CompatibilityBuildHelper helper = new CompatibilityBuildHelper(info);
+        try {
+            // Should fail with root unset
+            helper.init(SUITE_PLAN, DYNAMIC_CONFIG_URL);
+            fail("Expected fail for unset root property");
+        } catch (IllegalArgumentException e) {
+            /* expected */
+        }
+        setProperty(mRoot.getAbsolutePath());
+        // Shouldn't fail with root set
+        helper.init(SUITE_PLAN, DYNAMIC_CONFIG_URL);
+    }
+
+    public void testValidation() throws Exception {
+        setProperty(mRoot.getAbsolutePath());
+        mHelper.init(SUITE_PLAN, DYNAMIC_CONFIG_URL);
+        try {
+            mHelper.getDir();
+            fail("Build helper validation succeeded on an invalid installation");
+        } catch (FileNotFoundException e) {
+            // Expected
+        }
+        createDirStructure();
+        try {
+            mHelper.getTestsDir();
+        } catch (IllegalArgumentException e) {
+            e.printStackTrace();
+            fail("Build helper validation failed on a valid installation");
+        }
+    }
+
+    public void testDirs() throws Exception {
+        setProperty(mRoot.getAbsolutePath());
+        mHelper.init(SUITE_PLAN, DYNAMIC_CONFIG_URL);
+        createDirStructure();
+        assertNotNull(mRoot);
+        assertNotNull(mBuild);
+        assertNotNull(mHelper.getRootDir());
+        assertEquals("Incorrect root dir", mRoot.getAbsolutePath(),
+                mHelper.getRootDir().getAbsolutePath());
+        assertEquals("Incorrect base dir", mBase.getAbsolutePath(),
+                mHelper.getDir().getAbsolutePath());
+        assertEquals("Incorrect logs dir", new File(mBase, "logs").getAbsolutePath(),
+                mHelper.getLogsDir().getAbsolutePath());
+        assertEquals("Incorrect tests dir", mTests.getAbsolutePath(),
+                mHelper.getTestsDir().getAbsolutePath());
+        assertEquals("Incorrect results dir", new File(mBase, "results").getAbsolutePath(),
+                mHelper.getResultsDir().getAbsolutePath());
+    }
+
+    /**
+     * Sets the *_ROOT property of the build's installation location.
+     *
+     * @param value the value to set, or null to clear the property.
+     */
+    public static void setProperty(String value) {
+        if (value == null) {
+            System.clearProperty(ROOT_PROPERTY);
+        } else {
+            System.setProperty(ROOT_PROPERTY, value);
+        }
+    }
+}
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/command/CompatibilityConsoleTest.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/command/CompatibilityConsoleTest.java
new file mode 100644
index 0000000..55c1651
--- /dev/null
+++ b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/command/CompatibilityConsoleTest.java
@@ -0,0 +1,44 @@
+/*
+ * 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.command;
+
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelperTest;
+
+import junit.framework.TestCase;
+
+public class CompatibilityConsoleTest extends TestCase {
+
+    @Override
+    public void setUp() throws Exception {
+        CompatibilityBuildHelperTest.setProperty("/tmp/foobar");
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+        CompatibilityBuildHelperTest.setProperty(null);
+    }
+
+    public void testHelpExists() throws Exception {
+        CompatibilityConsole console = new CompatibilityConsole() {};
+        assertFalse("No help", console.getGenericHelpString(null).isEmpty());
+    }
+
+    public void testPromptExists() throws Exception {
+        CompatibilityConsole console = new CompatibilityConsole() {};
+        assertFalse("No prompt", console.getConsolePrompt().isEmpty());
+    }
+}
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
new file mode 100644
index 0000000..d782f8e
--- /dev/null
+++ b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/result/ResultReporterTest.java
@@ -0,0 +1,168 @@
+/*
+ * 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.result;
+
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.compatibility.common.util.AbiUtils;
+import com.android.compatibility.common.util.ICaseResult;
+import com.android.compatibility.common.util.IInvocationResult;
+import com.android.compatibility.common.util.IModuleResult;
+import com.android.compatibility.common.util.ITestResult;
+import com.android.compatibility.common.util.TestStatus;
+import com.android.ddmlib.testrunner.TestIdentifier;
+import com.android.tradefed.build.FolderBuildInfo;
+import com.android.tradefed.build.IFolderBuildInfo;
+import com.android.tradefed.config.OptionSetter;
+import com.android.tradefed.util.FileUtil;
+
+import junit.framework.TestCase;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.util.HashMap;
+import java.util.List;
+
+public class ResultReporterTest extends TestCase {
+
+    private static final String ROOT_PROPERTY = "TESTS_ROOT";
+    private static final String BUILD_NUMBER = "2";
+    private static final String SUITE_PLAN = "cts";
+    private static final String DYNAMIC_CONFIG_URL = "";
+    private static final String ROOT_DIR_NAME = "root";
+    private static final String BASE_DIR_NAME = "android-tests";
+    private static final String TESTCASES = "testcases";
+    private static final String NAME = "ModuleName";
+    private static final String ABI = "mips64";
+    private static final String ID = AbiUtils.createId(ABI, NAME);
+    private static final String CLASS = "android.test.FoorBar";
+    private static final String METHOD_1 = "testBlah1";
+    private static final String METHOD_2 = "testBlah2";
+    private static final String METHOD_3 = "testBlah3";
+    private static final String TEST_1 = String.format("%s#%s", CLASS, METHOD_1);
+    private static final String TEST_2 = String.format("%s#%s", CLASS, METHOD_2);
+    private static final String TEST_3 = String.format("%s#%s", CLASS, METHOD_3);
+    private static final String STACK_TRACE = "Something small is not alright\n " +
+            "at four.big.insects.Marley.sing(Marley.java:10)";
+    private static final String RESULT_DIR = "result123";
+    private static final String[] FORMATTING_FILES = {
+        "compatibility-result.css",
+        "compatibility-result.xsd",
+        "compatibility-result.xsl",
+        "logo.png",
+        "newrule-green.png"};
+
+    private ResultReporter mReporter;
+    private IFolderBuildInfo mBuildInfo;
+    private CompatibilityBuildHelper mBuildHelper;
+
+    private File mRoot = null;
+    private File mBase = null;
+    private File mTests = null;
+
+    @Override
+    public void setUp() throws Exception {
+        mReporter = new ResultReporter();
+        OptionSetter setter = new OptionSetter(mReporter);
+        setter.setOptionValue("quiet-output", "true");
+        mRoot = FileUtil.createTempDir(ROOT_DIR_NAME);
+        mBase = new File(mRoot, BASE_DIR_NAME);
+        mBase.mkdirs();
+        mTests = new File(mBase, TESTCASES);
+        mTests.mkdirs();
+        System.setProperty(ROOT_PROPERTY, mRoot.getAbsolutePath());
+        mBuildInfo = new FolderBuildInfo(BUILD_NUMBER, "", "");
+        mBuildHelper = new CompatibilityBuildHelper(mBuildInfo);
+        mBuildHelper.init(SUITE_PLAN, DYNAMIC_CONFIG_URL);
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+        mReporter = null;
+        FileUtil.recursiveDelete(mRoot);
+    }
+
+    public void testSetup() throws Exception {
+        mReporter.invocationStarted(mBuildInfo);
+        // Should have created a directory for the logs
+        File[] children = mBuildHelper.getLogsDir().listFiles();
+        assertTrue("Didn't create logs dir", children.length == 1 && children[0].isDirectory());
+        // Should have created a directory for the results
+        children = mBuildHelper.getResultsDir().listFiles();
+        assertTrue("Didn't create results dir", children.length == 1 && children[0].isDirectory());
+        mReporter.invocationEnded(10);
+        // Should have created a zip file
+        children = mBuildHelper.getResultsDir().listFiles(new FileFilter() {
+            @Override
+            public boolean accept(File pathname) {
+                return pathname.getName().endsWith(".zip");
+            }
+        });
+        assertTrue("Didn't create results zip",
+                children.length == 1 && children[0].isFile() && children[0].length() > 0);
+    }
+
+    public void testResultReporting() throws Exception {
+        mReporter.invocationStarted(mBuildInfo);
+        mReporter.testRunStarted(ID, 2);
+        TestIdentifier test1 = new TestIdentifier(CLASS, METHOD_1);
+        mReporter.testStarted(test1);
+        mReporter.testEnded(test1, new HashMap<String, String>());
+        TestIdentifier test2 = new TestIdentifier(CLASS, METHOD_2);
+        mReporter.testStarted(test2);
+        mReporter.testFailed(test2, STACK_TRACE);
+        TestIdentifier test3 = new TestIdentifier(CLASS, METHOD_3);
+        mReporter.testStarted(test3);
+        mReporter.testFailed(test3, STACK_TRACE);
+        mReporter.testRunEnded(10, new HashMap<String, String>());
+        mReporter.invocationEnded(10);
+        IInvocationResult result = mReporter.getResult();
+        assertEquals("Expected 1 pass", 1, result.countResults(TestStatus.PASS));
+        assertEquals("Expected 2 failures", 2, result.countResults(TestStatus.FAIL));
+        List<IModuleResult> modules = result.getModules();
+        assertEquals("Expected 1 module", 1, modules.size());
+        IModuleResult module = modules.get(0);
+        assertEquals("Incorrect ID", ID, module.getId());
+        List<ICaseResult> caseResults = module.getResults();
+        assertEquals("Expected 1 test case", 1, caseResults.size());
+        ICaseResult caseResult = caseResults.get(0);
+        List<ITestResult> testResults = caseResult.getResults();
+        assertEquals("Expected 3 tests", 3, testResults.size());
+        ITestResult result1 = caseResult.getResult(METHOD_1);
+        assertNotNull(String.format("Expected result for %s", TEST_1), result1);
+        assertEquals(String.format("Expected pass for %s", TEST_1), TestStatus.PASS,
+                result1.getResultStatus());
+        ITestResult result2 = caseResult.getResult(METHOD_2);
+        assertNotNull(String.format("Expected result for %s", TEST_2), result2);
+        assertEquals(String.format("Expected fail for %s", TEST_2), TestStatus.FAIL,
+                result2.getResultStatus());
+        ITestResult result3 = caseResult.getResult(METHOD_3);
+        assertNotNull(String.format("Expected result for %s", TEST_3), result3);
+        assertEquals(String.format("Expected fail for %s", TEST_3), TestStatus.FAIL,
+                result3.getResultStatus());
+    }
+
+    public void testCopyFormattingFiles() throws Exception {
+        File resultDir = new File(mBuildHelper.getResultsDir(), RESULT_DIR);
+        resultDir.mkdirs();
+        ResultReporter.copyFormattingFiles(resultDir);
+        for (String filename : FORMATTING_FILES) {
+            File file = new File(resultDir, filename);
+            assertTrue(String.format("%s (%s) was not created", filename, file.getAbsolutePath()),
+                    file.exists() && file.isFile() && file.length() > 0);
+        }
+    }
+}
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/TradefedTest.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/CompatibilityTestTest.java
similarity index 66%
copy from common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/TradefedTest.java
copy to common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/CompatibilityTestTest.java
index ab19369..ccab426 100644
--- a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/TradefedTest.java
+++ b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/CompatibilityTestTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2014 The Android Open Source Project
+ * 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.
@@ -14,12 +14,18 @@
  * limitations under the License.
  */
 
-package com.android.compatibility.common.tradefed;
+package com.android.compatibility.common.tradefed.testtype;
 
 import junit.framework.TestCase;
 
-public class TradefedTest extends TestCase {
+public class CompatibilityTestTest extends TestCase {
 
-    // TODO(stuartscott): Add tests when there is something to test.
+    @Override
+    public void setUp() throws Exception {
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+    }
 
 }
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
new file mode 100644
index 0000000..899ac2c
--- /dev/null
+++ b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/ModuleDefTest.java
@@ -0,0 +1,189 @@
+/*
+ * 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.testtype;
+
+import com.android.compatibility.common.util.AbiUtils;
+import com.android.ddmlib.testrunner.TestIdentifier;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.result.ITestInvocationListener;
+import com.android.tradefed.result.InputStreamSource;
+import com.android.tradefed.result.LogDataType;
+import com.android.tradefed.result.TestSummary;
+import com.android.tradefed.targetprep.ITargetPreparer;
+import com.android.tradefed.testtype.IAbi;
+import com.android.tradefed.testtype.IRemoteTest;
+import com.android.tradefed.testtype.ITestFilterReceiver;
+
+import junit.framework.TestCase;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Pattern;
+
+public class ModuleDefTest extends TestCase {
+
+    private static final String NAME = "ModuleName";
+    private static final String ABI = "mips64";
+    private static final String ID = AbiUtils.createId(ABI, NAME);
+    private static final String CLASS = "android.test.FoorBar";
+    private static final String METHOD_1 = "testBlah1";
+    private static final String TEST_1 = String.format("%s#%s", CLASS, METHOD_1);
+
+    public void testAccessors() throws Exception {
+        IAbi abi = new Abi(ABI, "");
+        IModuleDef def = new ModuleDef(NAME, abi, new ArrayList<IRemoteTest>(),
+                new ArrayList<ITargetPreparer>());
+        assertEquals("Incorrect ID", ID, def.getId());
+        assertEquals("Incorrect ABI", ABI, def.getAbi().getName());
+        assertEquals("Incorrect Name", NAME, def.getName());
+        assertNotNull("Expected tests", def.getTests());
+        assertNotNull("Expected preparers", def.getPreparers());
+    }
+
+    public void testNameMatching() throws Exception {
+        IAbi abi = new Abi(ABI, "");
+        ModuleDef def = new ModuleDef(NAME, abi, new ArrayList<IRemoteTest>(),
+                new ArrayList<ITargetPreparer>());
+        assertTrue("Expected equality", def.nameMatches(Pattern.compile(NAME)));
+        assertTrue("Expected regex equality", def.nameMatches(Pattern.compile(".*")));
+        assertFalse("Expected no match to ID", def.nameMatches(Pattern.compile(ID)));
+        assertFalse("Expected no match to empty", def.nameMatches(Pattern.compile("")));
+    }
+
+    public void testAddFilters() throws Exception {
+        IAbi abi = new Abi(ABI, "");
+        List<IRemoteTest> tests = new ArrayList<>();
+        MockRemoteTest mockTest = new MockRemoteTest();
+        tests.add(mockTest);
+        ModuleDef def = new ModuleDef(NAME, abi, tests, new ArrayList<ITargetPreparer>());
+        def.addIncludeFilter(CLASS);
+        def.addExcludeFilter(TEST_1);
+        MockListener mockListener = new MockListener();
+        def.run(mockListener);
+        assertEquals("Expected one include filter", 1, mockTest.mIncludeFilters.size());
+        assertEquals("Expected one exclude filter", 1, mockTest.mExcludeFilters.size());
+        assertEquals("Incorrect include filter", CLASS, mockTest.mIncludeFilters.get(0));
+        assertEquals("Incorrect exclude filter", TEST_1, mockTest.mExcludeFilters.get(0));
+    }
+
+    private class MockRemoteTest implements IRemoteTest, ITestFilterReceiver {
+
+        private final List<String> mIncludeFilters = new ArrayList<>();
+        private final List<String> mExcludeFilters = new ArrayList<>();
+
+        @Override
+        public void addIncludeFilter(String filter) {
+            mIncludeFilters.add(filter);
+        }
+
+        @Override
+        public void addAllIncludeFilters(List<String> filters) {
+            mIncludeFilters.addAll(filters);
+        }
+
+        @Override
+        public void addExcludeFilter(String filter) {
+            mExcludeFilters.add(filter);
+        }
+
+        @Override
+        public void addAllExcludeFilters(List<String> filters) {
+            mExcludeFilters.addAll(filters);
+        }
+
+        @Override
+        public void run(ITestInvocationListener listener) throws DeviceNotAvailableException {
+            // Do nothing
+        }
+
+    }
+
+    private class MockListener implements ITestInvocationListener {
+
+        @Override
+        public void invocationStarted(IBuildInfo buildInfo) {
+            // Do nothing
+        }
+
+        @Override
+        public void testRunStarted(String name, int numTests) {
+            // Do nothing
+        }
+
+        @Override
+        public void testStarted(TestIdentifier test) {
+            // Do nothing
+        }
+
+        @Override
+        public void testEnded(TestIdentifier test, Map<String, String> metrics) {
+            // Do nothing
+        }
+
+        @Override
+        public void testIgnored(TestIdentifier test) {
+            // Do nothing
+        }
+
+        @Override
+        public void testFailed(TestIdentifier test, String trace) {
+            // Do nothing
+        }
+
+        @Override
+        public void testAssumptionFailure(TestIdentifier test, String trace) {
+            // Do nothing
+        }
+
+        @Override
+        public void testRunStopped(long elapsedTime) {
+            // Do nothing
+        }
+
+        @Override
+        public void testRunEnded(long elapsedTime, Map<String, String> metrics) {
+            // Do nothing
+        }
+
+        @Override
+        public void testRunFailed(String id) {
+            // Do nothing
+        }
+
+        @Override
+        public TestSummary getSummary() {
+            return null;
+        }
+
+        @Override
+        public void invocationEnded(long elapsedTime) {
+            // Do nothing
+        }
+
+        @Override
+        public void invocationFailed(Throwable cause) {
+            // Do nothing
+        }
+
+        @Override
+        public void testLog(String name, LogDataType type, InputStreamSource stream) {
+            // Do nothing
+        }
+    }
+}
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/TradefedTest.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/ModuleRepoTest.java
similarity index 67%
copy from common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/TradefedTest.java
copy to common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/ModuleRepoTest.java
index ab19369..bff1072 100644
--- a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/TradefedTest.java
+++ b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/ModuleRepoTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2014 The Android Open Source Project
+ * 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.
@@ -14,12 +14,18 @@
  * limitations under the License.
  */
 
-package com.android.compatibility.common.tradefed;
+package com.android.compatibility.common.tradefed.testtype;
 
 import junit.framework.TestCase;
 
-public class TradefedTest extends TestCase {
+public class ModuleRepoTest extends TestCase {
 
-    // TODO(stuartscott): Add tests when there is something to test.
+    @Override
+    public void setUp() throws Exception {
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+    }
 
 }
diff --git a/tests/tests/acceleration/Android.mk b/common/host-side/util/Android.mk
similarity index 61%
copy from tests/tests/acceleration/Android.mk
copy to common/host-side/util/Android.mk
index d417371..8747cf8 100644
--- a/tests/tests/acceleration/Android.mk
+++ b/common/host-side/util/Android.mk
@@ -1,10 +1,10 @@
-# Copyright (C) 2011 The Android Open Source Project
+# 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
+# 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,
@@ -12,22 +12,20 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-LOCAL_PATH:= $(call my-dir)
+LOCAL_PATH := $(call my-dir)
 
 include $(CLEAR_VARS)
 
-LOCAL_MODULE_TAGS := optional
-
-LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-
-LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner
-
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
-LOCAL_PACKAGE_NAME := CtsAccelerationTestCases
+LOCAL_STATIC_JAVA_LIBRARIES := compatibility-common-util-hostsidelib
 
-LOCAL_INSTRUMENTATION_FOR := CtsAccelerationTestStubs
+LOCAL_JAVA_LIBRARIES := json-prebuilt
 
-LOCAL_SDK_VERSION := current
+LOCAL_MODULE := compatibility-host-util
 
-include $(BUILD_CTS_PACKAGE)
+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/util/src/com/android/compatibility/common/util/DynamicConfigHandler.java b/common/host-side/util/src/com/android/compatibility/common/util/DynamicConfigHandler.java
new file mode 100644
index 0000000..b42faca
--- /dev/null
+++ b/common/host-side/util/src/com/android/compatibility/common/util/DynamicConfigHandler.java
@@ -0,0 +1,127 @@
+/*
+ * 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.util;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.json.JSONTokener;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlPullParserFactory;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class DynamicConfigHandler {
+
+    private static final String LOG_TAG = DynamicConfigHandler.class.getSimpleName();
+
+    // xml constant
+    private static final String NS = null; //representing null namespace
+    private static final String ENCODING = "UTF-8";
+
+    public static File getMergedDynamicConfigFile(File localConfigFile, String apfeConfigJson,
+            String moduleName) throws IOException, XmlPullParserException, JSONException {
+
+        DynamicConfig.Params localConfig = DynamicConfig.genParamsFromFile(localConfigFile);
+        DynamicConfig.Params apfeOverride = parseJsonToParam(apfeConfigJson);
+
+        localConfig.mDynamicParams.putAll(apfeOverride.mDynamicParams);
+        localConfig.mDynamicArrayParams.putAll(apfeOverride.mDynamicArrayParams);
+
+        File mergedConfigFile = storeMergedConfigFile(localConfig, moduleName);
+        return mergedConfigFile;
+    }
+
+    private static DynamicConfig.Params parseJsonToParam(String apfeConfigJson)
+            throws JSONException {
+        if (apfeConfigJson == null) return new DynamicConfig.Params();
+
+        Map<String, String> configMap = new HashMap<>();
+        Map<String, List<String>> configListMap = new HashMap<>();
+
+        JSONObject rootObj  = new JSONObject(new JSONTokener(apfeConfigJson));
+        if (rootObj.has("config")) {
+            JSONArray configArr = rootObj.getJSONArray("config");
+            for (int i = 0; i < configArr.length(); i++) {
+                JSONObject config = configArr.getJSONObject(i);
+                configMap.put(config.getString("key"), config.getString("value"));
+            }
+        }
+        if (rootObj.has("configList")) {
+            JSONArray configListArr = rootObj.getJSONArray("configList");
+            for (int i = 0; i < configListArr.length(); i++) {
+                JSONObject configList = configListArr.getJSONObject(i);
+                String key = configList.getString("key");
+                List<String> values = new ArrayList<>();
+                JSONArray configListValuesArr = configList.getJSONArray("value");
+                for (int j = 0; j < configListValuesArr.length(); j++) {
+                    values.add(configListValuesArr.getString(j));
+                }
+                configListMap.put(key, values);
+            }
+        }
+
+        DynamicConfig.Params param = new DynamicConfig.Params();
+        param.mDynamicParams = configMap;
+        param.mDynamicArrayParams = configListMap;
+        return param;
+    }
+
+    private static File storeMergedConfigFile(DynamicConfig.Params p, String moduleName)
+            throws XmlPullParserException, IOException {
+        XmlSerializer serializer = XmlPullParserFactory.newInstance().newSerializer();
+
+        File parentFolder = new File(DynamicConfig.CONFIG_FOLDER_ON_HOST);
+        if (!parentFolder.exists()) parentFolder.mkdir();
+        File folder = new File(DynamicConfig.MERGED_CONFIG_FILE_FOLDER);
+        if (!folder.exists()) folder.mkdir();
+        File mergedConfigFile = new File(folder, moduleName+".dynamic");
+        OutputStream stream = new FileOutputStream(mergedConfigFile);
+        serializer.setOutput(stream, ENCODING);
+        serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
+        serializer.startDocument(ENCODING, false);
+
+        serializer.startTag(NS, DynamicConfig.DYNAMIC_CONFIG_TAG);
+        for (String key : p.mDynamicParams.keySet()) {
+            serializer.startTag(NS, DynamicConfig.CONFIG_TAG);
+            serializer.attribute(NS, DynamicConfig.KEY_ATTR, key);
+            serializer.text(p.mDynamicParams.get(key));
+            serializer.endTag(NS, DynamicConfig.CONFIG_TAG);
+        }
+        for (String key : p.mDynamicArrayParams.keySet()) {
+            serializer.startTag(NS, DynamicConfig.CONFIG_LIST_TAG);
+            serializer.attribute(NS, DynamicConfig.KEY_ATTR, key);
+            for (String item: p.mDynamicArrayParams.get(key)) {
+                serializer.startTag(NS, DynamicConfig.ITEM_TAG);
+                serializer.text(item);
+                serializer.endTag(NS, DynamicConfig.ITEM_TAG);
+            }
+            serializer.endTag(NS, DynamicConfig.CONFIG_LIST_TAG);
+        }
+        serializer.endTag(NS, DynamicConfig.DYNAMIC_CONFIG_TAG);
+        serializer.endDocument();
+        return mergedConfigFile;
+    }
+}
diff --git a/common/host-side/util/src/com/android/compatibility/common/util/DynamicConfigHostSide.java b/common/host-side/util/src/com/android/compatibility/common/util/DynamicConfigHostSide.java
new file mode 100644
index 0000000..ac69034
--- /dev/null
+++ b/common/host-side/util/src/com/android/compatibility/common/util/DynamicConfigHostSide.java
@@ -0,0 +1,34 @@
+/*
+ * 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.util;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * Load dynamic config for device side test cases
+ */
+public class DynamicConfigHostSide extends DynamicConfig {
+    private static String LOG_TAG = DynamicConfigHostSide.class.getSimpleName();
+
+    public DynamicConfigHostSide(String moduleName) throws IOException, XmlPullParserException {
+        File configFile = getConfigFile(new File(CONFIG_FOLDER_ON_HOST), moduleName);
+        initConfigFromXml(configFile);
+    }
+}
diff --git a/common/util/src/com/android/compatibility/common/util/MetricsReportLog.java b/common/host-side/util/src/com/android/compatibility/common/util/MetricsReportLog.java
similarity index 100%
rename from common/util/src/com/android/compatibility/common/util/MetricsReportLog.java
rename to common/host-side/util/src/com/android/compatibility/common/util/MetricsReportLog.java
diff --git a/common/util/src/com/android/compatibility/common/util/MetricsStore.java b/common/host-side/util/src/com/android/compatibility/common/util/MetricsStore.java
similarity index 98%
rename from common/util/src/com/android/compatibility/common/util/MetricsStore.java
rename to common/host-side/util/src/com/android/compatibility/common/util/MetricsStore.java
index 9eeb94a..efe7182 100644
--- a/common/util/src/com/android/compatibility/common/util/MetricsStore.java
+++ b/common/host-side/util/src/com/android/compatibility/common/util/MetricsStore.java
@@ -28,6 +28,8 @@
     private static final ConcurrentHashMap<String, ReportLog> mMap =
             new ConcurrentHashMap<String, ReportLog>();
 
+    private MetricsStore() {}
+
     /**
      * Stores a result. Existing result with the same key will be replaced.
      * Note that key is generated in the form of device_serial#class#method name.
diff --git a/tests/tests/acceleration/Android.mk b/common/host-side/util/tests/Android.mk
similarity index 61%
copy from tests/tests/acceleration/Android.mk
copy to common/host-side/util/tests/Android.mk
index d417371..a0e9a3b 100644
--- a/tests/tests/acceleration/Android.mk
+++ b/common/host-side/util/tests/Android.mk
@@ -1,10 +1,10 @@
-# Copyright (C) 2011 The Android Open Source Project
+# 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
+# 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,
@@ -12,22 +12,16 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-LOCAL_PATH:= $(call my-dir)
+LOCAL_PATH := $(call my-dir)
 
 include $(CLEAR_VARS)
 
-LOCAL_MODULE_TAGS := optional
-
-LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-
-LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner
-
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
-LOCAL_PACKAGE_NAME := CtsAccelerationTestCases
+LOCAL_JAVA_LIBRARIES := compatibility-host-util junit json-prebuilt
 
-LOCAL_INSTRUMENTATION_FOR := CtsAccelerationTestStubs
+LOCAL_MODULE := compatibility-host-util-tests
 
-LOCAL_SDK_VERSION := current
+LOCAL_MODULE_TAGS := optional
 
-include $(BUILD_CTS_PACKAGE)
+include $(BUILD_HOST_JAVA_LIBRARY)
\ No newline at end of file
diff --git a/common/host-side/util/tests/src/com/android/compatibility/common/util/DynamicConfigHandlerTest.java b/common/host-side/util/tests/src/com/android/compatibility/common/util/DynamicConfigHandlerTest.java
new file mode 100644
index 0000000..e2001fc
--- /dev/null
+++ b/common/host-side/util/tests/src/com/android/compatibility/common/util/DynamicConfigHandlerTest.java
@@ -0,0 +1,133 @@
+/*
+ * 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.util;
+
+import junit.framework.TestCase;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+/**
+ * Unit tests for {@link DynamicConfigHandler}
+ */
+public class DynamicConfigHandlerTest extends TestCase {
+
+    private static final String localConfig =
+            "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
+            "<DynamicConfig>\n" +
+            "    <Config key=\"test-config-1\">test config 1</Config>\n" +
+            "    <Config key=\"test-config-2\">test config 2</Config>\n" +
+            "    <Config key=\"override-config-2\">test config 3</Config>\n" +
+            "    <ConfigList key=\"config-list\">\n" +
+            "        <Item>config0</Item>\n" +
+            "        <Item>config1</Item>\n" +
+            "        <Item>config2</Item>\n" +
+            "        <Item>config3</Item>\n" +
+            "        <Item>config4</Item>\n" +
+            "    </ConfigList>\n" +
+            "    <ConfigList key=\"override-config-list-2\">\n" +
+            "        <Item>A</Item>\n" +
+            "        <Item>B</Item>\n" +
+            "        <Item>C</Item>\n" +
+            "        <Item>D</Item>\n" +
+            "        <Item>E</Item>\n" +
+            "    </ConfigList>\n" +
+            "</DynamicConfig>\n";
+
+    private static final String overrideJson =
+            "{\n" +
+            "  \"config\": [\n" +
+            "    {\n" +
+            "      \"key\": \"version\",\n" +
+            "      \"value\": \"1.0\"\n" +
+            "    },\n" +
+            "    {\n" +
+            "      \"key\": \"suite\",\n" +
+            "      \"value\": \"CTS_V2\"\n" +
+            "    },\n" +
+            "    {\n" +
+            "      \"key\": \"override-config-1\",\n" +
+            "      \"value\": \"override-config-val-1\"\n" +
+            "    },\n" +
+            "    {\n" +
+            "      \"key\": \"override-config-2\",\n" +
+            "      \"value\": \"override-config-val-2\"\n" +
+            "    }\n" +
+            "  ],\n" +
+            "  \"configList\": [\n" +
+            "    {\n" +
+            "      \"key\": \"override-config-list-1\",\n" +
+            "      \"value\": [\n" +
+            "        \"override-config-list-val-1-1\",\n" +
+            "        \"override-config-list-val-1-2\"\n" +
+            "      ]\n" +
+            "    },\n" +
+            "    {\n" +
+            "      \"key\": \"override-config-list-2\",\n" +
+            "      \"value\": [\n" +
+            "        \"override-config-list-val-2-1\"\n" +
+            "      ]\n" +
+            "    },\n" +
+            "    {\n" +
+            "      \"key\": \"override-config-list-3\",\n" +
+            "      \"value\": []\n" +
+            "    }\n" +
+            "  ]\n" +
+            "}";
+
+    public void testDynamicConfigHandler() throws Exception {
+        String module = "test1";
+        File localConfigFile = createFileFromStr(localConfig, module);
+
+        File mergedFile = DynamicConfigHandler
+                .getMergedDynamicConfigFile(localConfigFile, overrideJson, module);
+
+        DynamicConfig.Params params = DynamicConfig.genParamsFromFile(mergedFile);
+
+        assertEquals("override-config-val-1", params.mDynamicParams.get("override-config-1"));
+        assertTrue(params.mDynamicArrayParams.get("override-config-list-1")
+                .contains("override-config-list-val-1-1"));
+        assertTrue(params.mDynamicArrayParams.get("override-config-list-1")
+                .contains("override-config-list-val-1-2"));
+        assertTrue(params.mDynamicArrayParams.get("override-config-list-3").size() == 0);
+
+        assertEquals("test config 1", params.mDynamicParams.get("test-config-1"));
+        assertTrue(params.mDynamicArrayParams.get("config-list").contains("config2"));
+
+        assertEquals("override-config-val-2", params.mDynamicParams.get("override-config-2"));
+        assertEquals(1, params.mDynamicArrayParams.get("override-config-list-2").size());
+        assertTrue(params.mDynamicArrayParams.get("override-config-list-2")
+                .contains("override-config-list-val-2-1"));
+    }
+
+
+    private File createFileFromStr(String configStr, String module) throws IOException {
+        File file = File.createTempFile(module, "dynamic");
+        FileOutputStream stream = null;
+        try {
+            stream = new FileOutputStream(file);
+            stream.write(configStr.getBytes());
+            stream.flush();
+        } finally {
+            if (stream != null) {
+                stream.close();
+            }
+        }
+        return file;
+    }
+}
diff --git a/common/host-side/util/tests/src/com/android/compatibility/common/util/HostUnitTests.java b/common/host-side/util/tests/src/com/android/compatibility/common/util/HostUnitTests.java
new file mode 100644
index 0000000..8cf1e3f
--- /dev/null
+++ b/common/host-side/util/tests/src/com/android/compatibility/common/util/HostUnitTests.java
@@ -0,0 +1,37 @@
+/*
+ * 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.util;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * A test suite for all host util unit tests.
+ * <p/>
+ * All tests listed here should be self-contained, and do not require any external dependencies.
+ */
+public class HostUnitTests extends TestSuite {
+
+    public HostUnitTests() {
+        super();
+        addTestSuite(DynamicConfigHandlerTest.class);
+        addTestSuite(MetricsStoreTest.class);
+    }
+
+    public static Test suite() {
+        return new HostUnitTests();
+    }
+}
\ No newline at end of file
diff --git a/common/util/tests/src/com/android/compatibility/common/util/MetricsStoreTest.java b/common/host-side/util/tests/src/com/android/compatibility/common/util/MetricsStoreTest.java
similarity index 100%
rename from common/util/tests/src/com/android/compatibility/common/util/MetricsStoreTest.java
rename to common/host-side/util/tests/src/com/android/compatibility/common/util/MetricsStoreTest.java
diff --git a/common/host-side/xml-plan-generator/Android.mk b/common/host-side/xml-plan-generator/Android.mk
deleted file mode 100644
index 53718e5..0000000
--- a/common/host-side/xml-plan-generator/Android.mk
+++ /dev/null
@@ -1,49 +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.
-
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_JAVA_LIBRARIES := compatibility-common-util-hostsidelib_v2
-
-LOCAL_STATIC_JAVA_LIBRARIES := vogarexpectlib
-
-LOCAL_JAR_MANIFEST := MANIFEST.mf
-
-LOCAL_CLASSPATH := $(HOST_JDK_TOOLS_JAR)
-
-LOCAL_MODULE := compatibility-xml-plan-generator_v2
-
-LOCAL_MODULE_TAGS := optional
-
-include $(BUILD_HOST_JAVA_LIBRARY)
-
-###############################################################################
-# Build the tests
-###############################################################################
-
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := $(call all-java-files-under, tests/src)
-
-LOCAL_JAVA_LIBRARIES := compatibility-tradefed_v2 compatibility-xml-plan-generator_v2 junit
-
-LOCAL_MODULE := compatibility-xml-plan-generator-tests_v2
-
-LOCAL_MODULE_TAGS := optional
-
-include $(BUILD_HOST_JAVA_LIBRARY)
diff --git a/common/host-side/xml-plan-generator/MANIFEST.mf b/common/host-side/xml-plan-generator/MANIFEST.mf
deleted file mode 100644
index 95aee0d..0000000
--- a/common/host-side/xml-plan-generator/MANIFEST.mf
+++ /dev/null
@@ -1,3 +0,0 @@
-Manifest-Version: 1.0
-Main-Class: com.android.compatibility.common.xmlgenerator.XmlPlanGenerator
-Class-Path: compatibility-common-util-hostsidelib_v2.jar
diff --git a/common/host-side/xml-plan-generator/src/com/android/compatibility/common/xmlgenerator/Test.java b/common/host-side/xml-plan-generator/src/com/android/compatibility/common/xmlgenerator/Test.java
deleted file mode 100644
index d3e1d88..0000000
--- a/common/host-side/xml-plan-generator/src/com/android/compatibility/common/xmlgenerator/Test.java
+++ /dev/null
@@ -1,30 +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.
- */
-
-package com.android.compatibility.common.xmlgenerator;
-
-public class Test {
-
-    private final String mName;
-
-    public Test(String name) {
-        mName = name;
-    }
-
-    public String getName() {
-        return mName;
-    }
-}
diff --git a/common/host-side/xml-plan-generator/src/com/android/compatibility/common/xmlgenerator/TestCase.java b/common/host-side/xml-plan-generator/src/com/android/compatibility/common/xmlgenerator/TestCase.java
deleted file mode 100644
index 65b4aa3..0000000
--- a/common/host-side/xml-plan-generator/src/com/android/compatibility/common/xmlgenerator/TestCase.java
+++ /dev/null
@@ -1,41 +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.
- */
-
-package com.android.compatibility.common.xmlgenerator;
-
-import java.util.ArrayList;
-
-public class TestCase {
-
-    private final String mName;
-    private final ArrayList<Test> mTests = new ArrayList<Test>();
-
-    public TestCase(String name) {
-        mName = name;
-    }
-
-    public void addTest(Test test) {
-        mTests.add(test);
-    }
-
-    public String getName() {
-        return mName;
-    }
-
-    public ArrayList<Test> getTests() {
-        return mTests;
-    }
-}
diff --git a/common/host-side/xml-plan-generator/src/com/android/compatibility/common/xmlgenerator/TestListParser.java b/common/host-side/xml-plan-generator/src/com/android/compatibility/common/xmlgenerator/TestListParser.java
deleted file mode 100644
index 6880440..0000000
--- a/common/host-side/xml-plan-generator/src/com/android/compatibility/common/xmlgenerator/TestListParser.java
+++ /dev/null
@@ -1,96 +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.
- */
-
-package com.android.compatibility.common.xmlgenerator;
-
-import java.io.InputStream;
-import java.util.HashMap;
-import java.util.Scanner;
-
-/**
- * Parser of test lists in the form;
- *
- * suite:android.sample
- * case:SampleTest
- * test:testA
- * test:testB
- * suite:android.sample.ui
- * case:SampleUiTest
- * test:testA
- * test:testB
- */
-public class TestListParser {
-
-    private TestListParser() {}
-
-    public static HashMap<String, TestSuite> parse(InputStream input) {
-        final HashMap<String, TestSuite> suites = new HashMap<String, TestSuite>();
-        TestSuite currentSuite = null;
-        TestCase currentCase = null;
-        Scanner in = null;
-        try {
-            in = new Scanner(input);
-            while (in.hasNextLine()) {
-                final String line = in.nextLine();
-                final String[] parts = line.split(":");
-                if (parts.length != 2) {
-                    throw new RuntimeException("Invalid Format: " + line);
-                }
-                final String key = parts[0];
-                final String value = parts[1];
-                if (currentSuite == null) {
-                    if (!"suite".equals(key)) {
-                        throw new RuntimeException("TestSuite Expected");
-                    }
-                    final String[] names = value.split("\\.");
-                    for (int i = 0; i < names.length; i++) {
-                        final String name = names[i];
-                        if (currentSuite != null) {
-                            if (currentSuite.hasTestSuite(name)) {
-                                currentSuite = currentSuite.getTestSuite(name);
-                            } else {
-                                final TestSuite newSuite = new TestSuite(name);
-                                currentSuite.addTestSuite(newSuite);
-                                currentSuite = newSuite;
-                            }
-                        } else if (suites.containsKey(name)) {
-                            currentSuite = suites.get(name);
-                        } else {
-                            currentSuite = new TestSuite(name);
-                            suites.put(name, currentSuite);
-                        }
-                    }
-                } else if (currentCase == null) {
-                    if (!"case".equals(key)) {
-                        throw new RuntimeException("TestCase Expected");
-                    }
-                    currentCase = new TestCase(value);
-                    currentSuite.addTestCase(currentCase);
-                } else {
-                    if (!"test".equals(key)) {
-                        throw new RuntimeException("Test Expected");
-                    }
-                    currentCase.addTest(new Test(value));
-                }
-            }
-        } finally {
-            if (in != null) {
-                in.close();
-            }
-        }
-        return suites;
-    }
-}
diff --git a/common/host-side/xml-plan-generator/src/com/android/compatibility/common/xmlgenerator/TestSuite.java b/common/host-side/xml-plan-generator/src/com/android/compatibility/common/xmlgenerator/TestSuite.java
deleted file mode 100644
index db4fd07c..0000000
--- a/common/host-side/xml-plan-generator/src/com/android/compatibility/common/xmlgenerator/TestSuite.java
+++ /dev/null
@@ -1,59 +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.
- */
-
-package com.android.compatibility.common.xmlgenerator;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-
-public class TestSuite {
-
-    private final String mName;
-    private final HashMap<String, TestSuite> mTestSuites = new HashMap<String, TestSuite>();
-    private final ArrayList<TestCase> mTestCases = new ArrayList<TestCase>();
-
-    public TestSuite(String name) {
-        mName = name;
-    }
-
-    public boolean hasTestSuite(String name) {
-        return mTestSuites.containsKey(name);
-    }
-
-    public TestSuite getTestSuite(String name) {
-        return mTestSuites.get(name);
-    }
-
-    public void addTestSuite(TestSuite testSuite) {
-        mTestSuites.put(testSuite.getName(), testSuite);
-    }
-
-    public void addTestCase(TestCase testCase) {
-        mTestCases.add(testCase);
-    }
-
-    public String getName() {
-        return mName;
-    }
-
-    public HashMap<String, TestSuite> getTestSuites() {
-        return mTestSuites;
-    }
-
-    public ArrayList<TestCase> getTestCases() {
-        return mTestCases;
-    }
-}
diff --git a/common/host-side/xml-plan-generator/src/com/android/compatibility/common/xmlgenerator/XmlPlanGenerator.java b/common/host-side/xml-plan-generator/src/com/android/compatibility/common/xmlgenerator/XmlPlanGenerator.java
deleted file mode 100644
index efb53d5..0000000
--- a/common/host-side/xml-plan-generator/src/com/android/compatibility/common/xmlgenerator/XmlPlanGenerator.java
+++ /dev/null
@@ -1,217 +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.
- */
-
-package com.android.compatibility.common.xmlgenerator;
-
-import com.android.compatibility.common.util.KeyValueArgsParser;
-
-import org.w3c.dom.Document;
-import org.w3c.dom.Element;
-import org.w3c.dom.NodeList;
-
-import vogar.ExpectationStore;
-import vogar.ModeId;
-import vogar.Result;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-
-import javax.xml.parsers.DocumentBuilderFactory;
-
-/**
- * Passes the scanner output and outputs an xml description of the tests.
- */
-public class XmlPlanGenerator {
-
-    private final ExpectationStore mExpectations;
-    private final String mAppNameSpace;
-    private final String mAppPackageName;
-    private final String mName;
-    private final String mRunner;
-    private final String mTargetBinaryName;
-    private final String mTargetNameSpace;
-    private final String mJarPath;
-    private final String mTestType;
-    private final String mOutput;
-
-    private XmlPlanGenerator(ExpectationStore expectations, String appNameSpace,
-            String appPackageName, String name, String runner, String targetBinaryName,
-            String targetNameSpace, String jarPath, String testType, String output) {
-        mExpectations = expectations;
-        mAppNameSpace = appNameSpace;
-        mAppPackageName = appPackageName;
-        mName = name;
-        mRunner = runner;
-        mTargetBinaryName = targetBinaryName;
-        mTargetNameSpace = targetNameSpace;
-        mJarPath = jarPath;
-        mTestType = testType;
-        mOutput = output;
-    }
-
-    private void writePackageXml() throws IOException {
-        OutputStream out = System.out;
-        if (mOutput != null) {
-            out = new FileOutputStream(mOutput);
-        }
-        PrintWriter writer = null;
-        try {
-            writer = new PrintWriter(out);
-            writer.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
-            writeTestPackage(writer);
-        } finally {
-            if (writer != null) {
-                writer.close();
-            }
-        }
-    }
-
-    private void writeTestPackage(PrintWriter writer) {
-        writer.append("<TestPackage");
-        if (mAppNameSpace != null) {
-            writer.append(" appNameSpace=\"").append(mAppNameSpace).append("\"");
-        }
-
-        writer.append(" appPackageName=\"").append(mAppPackageName).append("\"");
-        writer.append(" name=\"").append(mName).append("\"");
-
-        if (mRunner != null) {
-            writer.append(" runner=\"").append(mRunner).append("\"");
-        }
-
-        if (mAppNameSpace != null && mTargetNameSpace != null
-                && !mAppNameSpace.equals(mTargetNameSpace)) {
-            writer.append(" targetBinaryName=\"").append(mTargetBinaryName).append("\"");
-            writer.append(" targetNameSpace=\"").append(mTargetNameSpace).append("\"");
-        }
-
-        if (mTestType != null && !mTestType.isEmpty()) {
-            writer.append(" testType=\"").append(mTestType).append("\"");
-        }
-
-        if (mJarPath != null) {
-            writer.append(" jarPath=\"").append(mJarPath).append("\"");
-        }
-
-        writer.println(" version=\"1.0\">");
-
-        final HashMap<String, TestSuite> suites = TestListParser.parse(System.in);
-        if (suites.isEmpty()) {
-            throw new RuntimeException("No TestSuites Found");
-        }
-        writeTestSuites(writer, suites, "");
-        writer.println("</TestPackage>");
-    }
-
-    private void writeTestSuites(PrintWriter writer, HashMap<String, TestSuite> suites, String name) {
-        for (String suiteName : suites.keySet()) {
-            final TestSuite suite = suites.get(suiteName);
-            writer.append("<TestSuite name=\"").append(suiteName).println("\">");
-            final String fullname = name + suiteName + ".";
-            writeTestSuites(writer, suite.getTestSuites(), fullname);
-            writeTestCases(writer, suite.getTestCases(), fullname);
-            writer.println("</TestSuite>");
-        }
-    }
-
-    private void writeTestCases(PrintWriter writer, ArrayList<TestCase> cases, String name) {
-        for (TestCase testCase : cases) {
-            final String caseName = testCase.getName();
-            writer.append("<TestCase name=\"").append(caseName).println("\">");
-            final String fullname = name + caseName;
-            writeTests(writer, testCase.getTests(), fullname);
-            writer.println("</TestCase>");
-        }
-    }
-
-    private void writeTests(PrintWriter writer, ArrayList<Test> tests, String name) {
-        if (tests.isEmpty()) {
-            throw new RuntimeException("No Tests Found");
-        }
-        for (Test test : tests) {
-            final String testName = test.getName();
-            writer.append("<Test name=\"").append(testName).append("\"");
-            final String fullname = name + "#" + testName;
-            if (isKnownFailure(mExpectations, fullname)) {
-                writer.append(" expectation=\"failure\"");
-            }
-            writer.println(" />");
-        }
-    }
-
-    public static boolean isKnownFailure(ExpectationStore store, String fullname) {
-        return store != null && store.get(fullname).getResult() != Result.SUCCESS;
-    }
-
-    public static void main(String[] args) throws Exception {
-        final HashMap<String, String> argsMap = KeyValueArgsParser.parse(args);
-        final String packageName = argsMap.get("-p");
-        final String name = argsMap.get("-n");
-        final String testType = argsMap.get("-t");
-        final String jarPath = argsMap.get("-j");
-        final String instrumentation = argsMap.get("-i");
-        final String manifest = argsMap.get("-m");
-        final String expectations = argsMap.get("-e");
-        final String output = argsMap.get("-o");
-        String appNameSpace = argsMap.get("-a");
-        String targetNameSpace = argsMap.get("-r");
-        if (packageName == null || name == null) {
-            usage(args);
-        }
-        String runner = null;
-        if (manifest != null) {
-            Document m = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(manifest);
-            Element elem = m.getDocumentElement();
-            appNameSpace = elem.getAttribute("package");
-            runner = getElementAttribute(elem, "instrumentation", "android:name");
-            targetNameSpace = getElementAttribute(elem, "instrumentation", "android:targetPackage");
-        }
-
-        final HashSet<File> expectationFiles = new HashSet<File>();
-        if (expectations != null) {
-            expectationFiles.add(new File(expectations));
-        }
-        final ExpectationStore store = ExpectationStore.parse(expectationFiles, ModeId.DEVICE);
-        XmlPlanGenerator generator = new XmlPlanGenerator(store, appNameSpace, packageName, name,
-            runner, instrumentation, targetNameSpace, jarPath, testType, output);
-        generator.writePackageXml();
-    }
-
-    private static String getElementAttribute(Element parent, String elem, String name) {
-        NodeList nodeList = parent.getElementsByTagName(elem);
-        if (nodeList.getLength() > 0) {
-             Element element = (Element) nodeList.item(0);
-             return element.getAttribute(name);
-        }
-        return null;
-    }
-
-    private static void usage(String[] args) {
-        System.err.println("Arguments: " + Arrays.toString(args));
-        System.err.println("Usage: compatibility-xml-plan-generator -p PACKAGE_NAME -n NAME" +
-            "[-t TEST_TYPE] [-j JAR_PATH] [-i INSTRUMENTATION] [-m MANIFEST] [-e EXPECTATIONS]" +
-            "[-o OUTPUT]");
-        System.exit(1);
-    }
-}
diff --git a/common/host-side/xml-plan-generator/tests/src/com/android/compatibility/common/xmlgenerator/XmlPlanGeneratorTest.java b/common/host-side/xml-plan-generator/tests/src/com/android/compatibility/common/xmlgenerator/XmlPlanGeneratorTest.java
deleted file mode 100644
index 082af17..0000000
--- a/common/host-side/xml-plan-generator/tests/src/com/android/compatibility/common/xmlgenerator/XmlPlanGeneratorTest.java
+++ /dev/null
@@ -1,128 +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.
- */
-
-package com.android.compatibility.common.xmlgenerator;
-
-import junit.framework.TestCase;
-
-import java.io.ByteArrayInputStream;
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Scanner;
-
-public class XmlPlanGeneratorTest extends TestCase {
-
-    private static final String JAR = "out/host/linux-x86/framework/compatibility-xml-plan-generator_v2.jar";
-    private static final String PACKAGE_NAME = "com.android.test";
-    private static final String NAME = "ValidTest";
-    private static final String VALID_RESULT =
-        "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
-        "<TestPackage appPackageName=\"com.android.test\" name=\"ValidTest\" version=\"1.0\">" +
-        "<TestSuite name=\"com\">" +
-        "<TestSuite name=\"android\">" +
-        "<TestSuite name=\"test\">" +
-        "<TestCase name=\"ValidTest\">" +
-        "<Test name=\"testA\" />" +
-        "</TestCase>" +
-        "</TestSuite>" +
-        "</TestSuite>" +
-        "</TestSuite>" +
-        "</TestPackage>";
-
-    private static final String VALID =
-        "suite:com.android.test\n" +
-        "case:ValidTest\n" +
-        "test:testA\n";
-
-    private static final String INVALID_A = "";
-
-    private static final String INVALID_B =
-        "suite:com.android.test\n" +
-        "case:InvalidTest\n";
-
-    private static final String INVALID_C =
-        "uh oh";
-
-    private static final String INVALID_D =
-        "test:testA\n" +
-        "case:InvalidTest\n" +
-        "suite:com.android.test\n";
-
-    private static final String INVALID_E =
-        "suite:com.android.test\n" +
-        "test:testA\n" +
-        "case:InvalidTest\n";
-
-    public void testValid() throws Exception {
-        assertEquals(VALID_RESULT, runGenerator(VALID));
-    }
-
-    public void testInvalidA() throws Exception {
-        assertNull(runGenerator(INVALID_A));
-    }
-
-    public void testInvalidB() throws Exception {
-        assertNull(runGenerator(INVALID_B));
-    }
-
-    public void testTestListParserInvalidFormat() throws Exception {
-        runTestListParser(INVALID_C);
-    }
-
-    public void testTestListParserSuiteExpected() throws Exception {
-        runTestListParser(INVALID_D);
-    }
-
-    public void testTestListParserCaseExpected() throws Exception {
-        runTestListParser(INVALID_E);
-    }
-
-    private static String runGenerator(String input) throws Exception {
-        ArrayList<String> args = new ArrayList<String>();
-        args.add("java");
-        args.add("-jar");
-        args.add(JAR);
-        args.add("-p");
-        args.add(PACKAGE_NAME);
-        args.add("-n");
-        args.add(NAME);
-
-        final Process p = new ProcessBuilder(args).start();
-        final PrintWriter out = new PrintWriter(p.getOutputStream());
-        out.print(input);
-        out.flush();
-        out.close();
-        final StringBuilder output = new StringBuilder();
-        final Scanner in = new Scanner(p.getInputStream());
-        while (in.hasNextLine()) {
-            output.append(in.nextLine());
-        }
-        int ret = p.waitFor();
-        if (ret == 0) {
-            return output.toString();
-        }
-        return null;
-    }
-
-    private static void runTestListParser(String input) throws Exception {
-        try {
-            final ByteArrayInputStream in = new ByteArrayInputStream(input.getBytes());
-            final HashMap<String, TestSuite> suites = TestListParser.parse(in);
-            fail();
-        } catch (RuntimeException e) {}
-    }
-}
diff --git a/common/util/Android.mk b/common/util/Android.mk
index 84ced65..2d4220f 100644
--- a/common/util/Android.mk
+++ b/common/util/Android.mk
@@ -24,7 +24,7 @@
 
 LOCAL_MODULE_TAGS := optional
 
-LOCAL_MODULE := compatibility-common-util-devicesidelib_v2
+LOCAL_MODULE := compatibility-common-util-devicesidelib
 
 LOCAL_SDK_VERSION := current
 
@@ -40,27 +40,10 @@
 
 LOCAL_MODULE_TAGS := optional
 
-LOCAL_MODULE := compatibility-common-util-hostsidelib_v2
+LOCAL_MODULE := compatibility-common-util-hostsidelib
 
 LOCAL_STATIC_JAVA_LIBRARIES := kxml2-2.3.0
 
 include $(BUILD_HOST_JAVA_LIBRARY)
 
-###############################################################################
-# Build the tests
-###############################################################################
-
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := $(call all-java-files-under, tests/src)
-
-LOCAL_STATIC_JAVA_LIBRARIES := \
-                        junit \
-                        kxml2-2.3.0 \
-                        compatibility-common-util-hostsidelib_v2
-
-LOCAL_MODULE := compatibility-common-util-tests_v2
-
-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/util/run_unit_tests.sh b/common/util/run_unit_tests.sh
deleted file mode 100755
index 04a6745..0000000
--- a/common/util/run_unit_tests.sh
+++ /dev/null
@@ -1,49 +0,0 @@
-#!/bin/bash
-
-# Copyright (C) 2012 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 the cts common unit tests
-
-checkFile() {
-    if [ ! -f "$1" ]; then
-        echo "Unable to locate $1"
-        exit
-    fi;
-}
-
-# check if in Android build env
-if [ ! -z ${ANDROID_BUILD_TOP} ]; then
-    HOST=`uname`
-    if [ "$HOST" == "Linux" ]; then
-        OS="linux-x86"
-    elif [ "$HOST" == "Darwin" ]; then
-        OS="darwin-x86"
-    else
-        echo "Unrecognized OS"
-        exit
-    fi;
-fi;
-
-JAR_DIR=${ANDROID_BUILD_TOP}/out/host/$OS/framework
-JARS="tradefed-prebuilt.jar compatibility-common-util-hostsidelib_v2.jar compatibility-common-util-tests_v2.jar"
-
-for JAR in $JARS; do
-    checkFile ${JAR_DIR}/${JAR}
-    JAR_PATH=${JAR_PATH}:${JAR_DIR}/${JAR}
-done
-
-java $RDBG_FLAG \
-  -cp ${JAR_PATH} com.android.tradefed.command.Console run singleCommand host -n --class com.android.compatibility.common.util.UnitTests "$@"
-
diff --git a/libs/commonutil/src/com/android/cts/util/AbiUtils.java b/common/util/src/com/android/compatibility/common/util/AbiUtils.java
similarity index 82%
rename from libs/commonutil/src/com/android/cts/util/AbiUtils.java
rename to common/util/src/com/android/compatibility/common/util/AbiUtils.java
index 42336f3..ef42a00 100644
--- a/libs/commonutil/src/com/android/cts/util/AbiUtils.java
+++ b/common/util/src/com/android/compatibility/common/util/AbiUtils.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.cts.util;
+package com.android.compatibility.common.util;
 
 import java.util.HashMap;
 import java.util.HashSet;
@@ -51,9 +51,9 @@
     private static final Set<String> MIPS_ABIS = new HashSet<String>();
 
     /**
-     * The set of ABI names which CTS supports.
+     * The set of ABI names which Compatibility supports.
      */
-    private static final Set<String> ABIS_SUPPORTED_BY_CTS = new HashSet<String>();
+    private static final Set<String> ABIS_SUPPORTED_BY_COMPATIBILITY = new HashSet<String>();
 
     /**
      * The map of architecture to ABI.
@@ -84,9 +84,9 @@
         ARCH_TO_ABIS.put("mips", MIPS_ABIS);
         ARCH_TO_ABIS.put("mips64", MIPS_ABIS);
 
-        ABIS_SUPPORTED_BY_CTS.addAll(ARM_ABIS);
-        ABIS_SUPPORTED_BY_CTS.addAll(INTEL_ABIS);
-        ABIS_SUPPORTED_BY_CTS.addAll(MIPS_ABIS);
+        ABIS_SUPPORTED_BY_COMPATIBILITY.addAll(ARM_ABIS);
+        ABIS_SUPPORTED_BY_COMPATIBILITY.addAll(INTEL_ABIS);
+        ABIS_SUPPORTED_BY_COMPATIBILITY.addAll(MIPS_ABIS);
     }
 
     /**
@@ -101,25 +101,25 @@
      */
     public static Set<String> getAbisForArch(String arch) {
         if (arch == null || arch.isEmpty() || !ARCH_TO_ABIS.containsKey(arch)) {
-            return getAbisSupportedByCts();
+            return getAbisSupportedByCompatibility();
         }
         return new HashSet<String>(ARCH_TO_ABIS.get(arch));
     }
 
     /**
-     * Returns the set of ABIs supported by CTS.
+     * Returns the set of ABIs supported by Compatibility.
      * @return a new Set containing the supported ABIs.
      */
-    public static Set<String> getAbisSupportedByCts() {
-        return new HashSet<String>(ABIS_SUPPORTED_BY_CTS);
+    public static Set<String> getAbisSupportedByCompatibility() {
+        return new HashSet<String>(ABIS_SUPPORTED_BY_COMPATIBILITY);
     }
 
     /**
      * @param abi The ABI name to test.
-     * @return true if the given ABI is supported by CTS.
+     * @return true if the given ABI is supported by Compatibility.
      */
-    public static boolean isAbiSupportedByCts(String abi) {
-        return ABIS_SUPPORTED_BY_CTS.contains(abi);
+    public static boolean isAbiSupportedByCompatibility(String abi) {
+        return ABIS_SUPPORTED_BY_COMPATIBILITY.contains(abi);
     }
 
     /**
@@ -128,7 +128,7 @@
      * @return a string which can be add to a command sent to ADB.
      */
     public static String createAbiFlag(String abi) {
-        if (abi == null || abi.isEmpty() || !isAbiSupportedByCts(abi)) {
+        if (abi == null || abi.isEmpty() || !isAbiSupportedByCompatibility(abi)) {
             return "";
         }
         return String.format("--abi %s ", abi);
@@ -181,7 +181,7 @@
     }
 
     /**
-     * @param abilistString A comma separated string containing abis.
+     * @param unsupportedAbiDescription A comma separated string containing abis.
      * @return A List of Strings containing valid ABIs.
      */
     public static Set<String> parseAbiList(String unsupportedAbiDescription) {
@@ -190,7 +190,7 @@
         if (descSegments.length == 2) {
             for (String abi : descSegments[1].split(",")) {
                 String trimmedAbi = abi.trim();
-                if (isAbiSupportedByCts(trimmedAbi)) {
+                if (isAbiSupportedByCompatibility(trimmedAbi)) {
                     abiSet.add(trimmedAbi);
                 }
             }
diff --git a/common/util/src/com/android/compatibility/common/util/CaseResult.java b/common/util/src/com/android/compatibility/common/util/CaseResult.java
new file mode 100644
index 0000000..e16ad1f
--- /dev/null
+++ b/common/util/src/com/android/compatibility/common/util/CaseResult.java
@@ -0,0 +1,115 @@
+/*
+ * 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.util;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Data structure for a Compatibility test case result.
+ */
+public class CaseResult implements ICaseResult {
+
+    private String mName;
+
+    private Map<String, ITestResult> mResults = new HashMap<>();
+
+    /**
+     * Creates a {@link CaseResult} for the given name, eg &lt;package-name&gt;.&lt;class-name&gt;
+     */
+    public CaseResult(String name) {
+        mName = name;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public String getName() {
+        return mName;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public ITestResult getOrCreateResult(String testName) {
+        ITestResult result = mResults.get(testName);
+        if (result == null) {
+            result = new TestResult(this, testName);
+            mResults.put(testName, result);
+        }
+        return result;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public ITestResult getResult(String testName) {
+        return mResults.get(testName);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public List<ITestResult> getResults(TestStatus status) {
+        List<ITestResult> results = new ArrayList<>();
+        for (ITestResult result : mResults.values()) {
+            if (result.getResultStatus() == status) {
+                results.add(result);
+            }
+        }
+        return results;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public List<ITestResult> getResults() {
+        ArrayList<ITestResult> results = new ArrayList<>(mResults.values());
+        Collections.sort(results);
+        return results;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int countResults(TestStatus status) {
+        int total = 0;
+        for (ITestResult result : mResults.values()) {
+            if (result.getResultStatus() == status) {
+                total++;
+            }
+        }
+        return total;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int compareTo(ICaseResult another) {
+        return getName().compareTo(another.getName());
+    }
+
+}
\ No newline at end of file
diff --git a/common/util/src/com/android/compatibility/common/util/DynamicConfig.java b/common/util/src/com/android/compatibility/common/util/DynamicConfig.java
new file mode 100644
index 0000000..a0b1d5e
--- /dev/null
+++ b/common/util/src/com/android/compatibility/common/util/DynamicConfig.java
@@ -0,0 +1,117 @@
+/*
+ * 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.util;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlPullParserFactory;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Load dynamic config for test cases
+ */
+public class DynamicConfig {
+    public final static String MODULE_NAME = "module-name";
+
+    //XML constant
+    public static final String NS = null;
+    public static final String DYNAMIC_CONFIG_TAG = "DynamicConfig";
+    public static final String CONFIG_TAG = "Config";
+    public static final String CONFIG_LIST_TAG = "ConfigList";
+    public static final String ITEM_TAG = "Item";
+    public static final String KEY_ATTR = "key";
+
+    public final static String CONFIG_FOLDER_ON_DEVICE = "/sdcard/dynamic-config-files/";
+    public final static String CONFIG_FOLDER_ON_HOST =
+            System.getProperty("java.io.tmpdir") + "/dynamic-config-files/";
+    public final static String MERGED_CONFIG_FILE_FOLDER =
+            System.getProperty("java.io.tmpdir") + "/dynamic-config-files/merged";
+
+
+    protected Params params;
+
+    protected void initConfigFromXml(File file) throws XmlPullParserException, IOException {
+        params = genParamsFromFile(file);
+    }
+
+    public String getConfig(String key) {
+        return params.mDynamicParams.get(key);
+    }
+
+    public List<String> getConfigList(String key) {
+        return params.mDynamicArrayParams.get(key);
+    }
+
+    public static File getConfigFile(File configFolder, String moduleName)
+            throws FileNotFoundException {
+        File config =  new File(configFolder, String.format("%s.dynamic", moduleName));
+        if (!config.exists()) {
+            throw new FileNotFoundException(String.format("Cannot find %s.dynamic", moduleName));
+        }
+        return config;
+    }
+
+    public static class Params {
+        public Map<String, String> mDynamicParams = new HashMap<>();
+        public Map<String, List<String>> mDynamicArrayParams = new HashMap<>();
+    }
+
+    public static Params genParamsFromFile(File file) throws XmlPullParserException, IOException {
+        Params param = new Params();
+
+        XmlPullParser parser = XmlPullParserFactory.newInstance().newPullParser();
+        parser.setInput(new InputStreamReader(new FileInputStream(file)));
+
+        parser.nextTag();
+        parser.require(XmlPullParser.START_TAG, NS, DYNAMIC_CONFIG_TAG);
+
+        while (parser.nextTag() == XmlPullParser.START_TAG) {
+            if (parser.getName().equals(CONFIG_TAG)) {
+                String key = parser.getAttributeValue(NS, KEY_ATTR);
+                String value = parser.nextText();
+                parser.require(XmlPullParser.END_TAG, NS, CONFIG_TAG);
+                if (key != null && !key.isEmpty()) {
+                    param.mDynamicParams.put(key, value);
+                }
+            } else {
+                List<String> arrayValue = new ArrayList<>();
+                parser.require(XmlPullParser.START_TAG, NS, CONFIG_LIST_TAG);
+                String key = parser.getAttributeValue(NS, KEY_ATTR);
+                while (parser.nextTag() == XmlPullParser.START_TAG) {
+                    parser.require(XmlPullParser.START_TAG, NS, ITEM_TAG);
+                    arrayValue.add(parser.nextText());
+                    parser.require(XmlPullParser.END_TAG, NS, ITEM_TAG);
+                }
+                parser.require(XmlPullParser.END_TAG, NS, CONFIG_LIST_TAG);
+                if (key != null && !key.isEmpty()) {
+                    param.mDynamicArrayParams.put(key, arrayValue);
+                }
+            }
+        }
+        parser.require(XmlPullParser.END_TAG, NS, DYNAMIC_CONFIG_TAG);
+        return param;
+    }
+}
diff --git a/common/util/src/com/android/compatibility/common/util/ICaseResult.java b/common/util/src/com/android/compatibility/common/util/ICaseResult.java
new file mode 100644
index 0000000..99e646a
--- /dev/null
+++ b/common/util/src/com/android/compatibility/common/util/ICaseResult.java
@@ -0,0 +1,58 @@
+/*
+ * 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.util;
+
+import java.util.List;
+
+/**
+ * Data structure for a Compatibility test case result.
+ */
+public interface ICaseResult extends Comparable<ICaseResult> {
+
+    String getName();
+
+    /**
+     * Gets a {@link ITestResult} for the given test, creating it if it doesn't exist.
+     *
+     * @param testName the name of the test eg &lt;method-name&gt;
+     * @return the {@link ITestResult} or <code>null</code>
+     */
+    ITestResult getOrCreateResult(String testName);
+
+    /**
+     * Gets the {@link ITestResult} for given test.
+     *
+     * @param testName the name of the test eg &lt;method-name&gt;
+     * @return the {@link ITestResult} or <code>null</code>
+     */
+    ITestResult getResult(String testName);
+
+    /**
+     * Gets all results sorted by name.
+     */
+    List<ITestResult> getResults();
+
+    /**
+     * Gets all results which have the given status.
+     */
+    List<ITestResult> getResults(TestStatus status);
+
+    /**
+     * Counts the number of results which have the given status.
+     */
+    int countResults(TestStatus status);
+
+}
diff --git a/common/util/src/com/android/compatibility/common/util/IInvocationResult.java b/common/util/src/com/android/compatibility/common/util/IInvocationResult.java
new file mode 100644
index 0000000..e70ae38
--- /dev/null
+++ b/common/util/src/com/android/compatibility/common/util/IInvocationResult.java
@@ -0,0 +1,82 @@
+/*
+ * 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.util;
+
+import java.io.File;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Interface for a the result of a single Compatibility invocation.
+ */
+public interface IInvocationResult {
+
+    /**
+     * @return the starting timestamp.
+     */
+    long getStartTime();
+
+    /**
+     * @param time the starting timestamp
+     */
+    void setStartTime(long time);
+
+    /**
+     * Count the number of results with given status.
+     */
+    int countResults(TestStatus result);
+
+    /**
+     * @param plan the plan associated with this result.
+     */
+    void setTestPlan(String plan);
+
+    /**
+     * @return the test plan associated with this result.
+     */
+    String getTestPlan();
+
+    /**
+     * @return the device serials associated with result.
+     */
+    Set<String> getDeviceSerials();
+
+    /**
+     * @return the {@link IModuleResult} for the given id, creating one if it doesn't exist
+     */
+    IModuleResult getOrCreateModule(String id);
+
+    /**
+     * @return the {@link IModuleResult}s sorted by id.
+     */
+    List<IModuleResult> getModules();
+
+    /**
+     * @return the directory containing this result.
+     */
+    File getResultDir();
+
+    /**
+     * Adds the given device info to the result.
+     */
+    void addDeviceInfo(String key, String value);
+
+    /**
+     * Gets the {@link Map} of device info collected.
+     */
+    Map<String, String> getDeviceInfo();
+}
diff --git a/common/util/src/com/android/compatibility/common/util/IModuleResult.java b/common/util/src/com/android/compatibility/common/util/IModuleResult.java
new file mode 100644
index 0000000..ea6df67
--- /dev/null
+++ b/common/util/src/com/android/compatibility/common/util/IModuleResult.java
@@ -0,0 +1,61 @@
+/*
+ * 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.util;
+
+import java.util.List;
+
+/**
+ * Data structure for a Compatibility test module result.
+ */
+public interface IModuleResult extends Comparable<IModuleResult> {
+
+    void setDeviceSerial(String deviceSerial);
+
+    String getDeviceSerial();
+
+    String getId();
+
+    String getName();
+
+    String getAbi();
+
+    /**
+     * Gets a {@link ICaseResult} for the given testcase, creating it if it doesn't exist.
+     *
+     * @param caseName the name of the testcase eg &lt;package-name&gt;&lt;class-name&gt;
+     * @return the {@link ICaseResult} or <code>null</code>
+     */
+    ICaseResult getOrCreateResult(String caseName);
+
+    /**
+     * Gets the {@link ICaseResult} result for given testcase.
+     *
+     * @param caseName the name of the testcase eg &lt;package-name&gt;&lt;class-name&gt;
+     * @return the {@link ITestResult} or <code>null</code>
+     */
+    ICaseResult getResult(String caseName);
+
+    /**
+     * Gets all results sorted by name.
+     */
+    List<ICaseResult> getResults();
+
+    /**
+     * Counts the number of results which have the given status.
+     */
+    int countResults(TestStatus status);
+
+}
diff --git a/common/util/src/com/android/compatibility/common/util/ITestResult.java b/common/util/src/com/android/compatibility/common/util/ITestResult.java
new file mode 100644
index 0000000..e57824b
--- /dev/null
+++ b/common/util/src/com/android/compatibility/common/util/ITestResult.java
@@ -0,0 +1,144 @@
+/*
+ * 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.util;
+
+/**
+ * Represents a single test result.
+ */
+public interface ITestResult extends Comparable<ITestResult> {
+
+    /**
+     * @return The name of this test result.
+     */
+    String getName();
+
+    /**
+     * @return The full name of this test result, ie
+     * &lt;package-name&gt;.&lt;class-name&gt;#&lt;method-name&gt;
+     */
+    String getFullName();
+
+    /**
+     * @return The {@link TestStatus} of this result.
+     */
+    TestStatus getResultStatus();
+
+    /**
+     * Sets the {@link TestStatus} of the result and updates the end time of the test.
+     *
+     * @param status The {@link TestStatus} of this result.
+     */
+    void setResultStatus(TestStatus status);
+
+    /**
+     * @return The failure message to display
+     */
+    String getMessage();
+
+    /**
+     * @param message The message to display which describes the failure
+     */
+    void setMessage(String message);
+
+    /**
+     * @param time the start time of the test.
+     */
+    void setStartTime(long time);
+
+    /**
+     * @return The time the test started
+     */
+    long getStartTime();
+
+    /**
+     * @param time the end time of the test.
+     */
+    void setEndTime(long time);
+
+    /**
+     * @return The time the test ended
+     */
+    long getEndTime();
+
+    /**
+     * @return The stack trace generated by the failure
+     */
+    String getStackTrace();
+
+    /**
+     * @param stackTrace the stack trace generated by the failure.
+     */
+    void setStackTrace(String stackTrace);
+
+    /**
+     * @return the metrics report.
+     */
+    ReportLog getReportLog();
+
+    /**
+     * @param report the metrics report.
+     */
+    void setReportLog(ReportLog report);
+
+    /**
+     * @return the path of the bug report generated of the failure.
+     */
+    String getBugReport();
+
+    /**
+     * @param path the path of the bug report generated of the failure.
+     */
+    void setBugReport(String path);
+
+    /**
+     * @return the path of the log file generated of the failure.
+     */
+    String getLog();
+
+    /**
+     * @param path the path of the log file generated of the failure.
+     */
+    void setLog(String path);
+
+    /**
+     * @return the path of the screenshot file generated of the failure.
+     */
+    String getScreenshot();
+
+    /**
+     * @param path the path of the screenshot file generated of the failure.
+     */
+    void setScreenshot(String path);
+
+    /**
+     * Report the test as a failure.
+     *
+     * @param trace the stacktrace of the failure.
+     */
+    void failed(String trace);
+
+    /**
+     * Report that the test has completed.
+     *
+     * @param report A report generated by the test, or null.
+     */
+    void passed(ReportLog report);
+
+    /**
+     * Reset the state of this {@link ITestResult}.
+     */
+    void resetResult();
+}
diff --git a/common/util/src/com/android/compatibility/common/util/InvocationResult.java b/common/util/src/com/android/compatibility/common/util/InvocationResult.java
new file mode 100644
index 0000000..0c44da7
--- /dev/null
+++ b/common/util/src/com/android/compatibility/common/util/InvocationResult.java
@@ -0,0 +1,158 @@
+/*
+ * 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.util;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Data structure for the detailed Compatibility test results.
+ */
+public class InvocationResult implements IInvocationResult {
+
+    private long mTimestamp;
+    private Map<String, IModuleResult> mModuleResults = new LinkedHashMap<>();
+    private Map<String, String> mDeviceInfo = new HashMap();
+    private String mTestPlan;
+    private File mResultDir;
+
+    /**
+     * @param resultDir
+     */
+    public InvocationResult(File resultDir) {
+        this(System.currentTimeMillis(), resultDir);
+    }
+
+    /**
+     * @param timestamp
+     * @param resultDir
+     */
+    public InvocationResult(long timestamp, File resultDir) {
+        setStartTime(timestamp);
+        mResultDir = resultDir;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public List<IModuleResult> getModules() {
+        ArrayList<IModuleResult> modules = new ArrayList<>(mModuleResults.values());
+        Collections.sort(modules);
+        return modules;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int countResults(TestStatus result) {
+        int total = 0;
+        for (IModuleResult m : mModuleResults.values()) {
+            total += m.countResults(result);
+        }
+        return total;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public IModuleResult getOrCreateModule(String id) {
+        IModuleResult moduleResult = mModuleResults.get(id);
+        if (moduleResult == null) {
+            moduleResult = new ModuleResult(id);
+            mModuleResults.put(id, moduleResult);
+        }
+        return moduleResult;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void addDeviceInfo(String key, String value) {
+        mDeviceInfo.put(key, value);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public Map<String, String> getDeviceInfo() {
+        return mDeviceInfo;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void setStartTime(long time) {
+        mTimestamp = time;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public long getStartTime() {
+        return mTimestamp;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void setTestPlan(String plan) {
+        mTestPlan = plan;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public String getTestPlan() {
+        return mTestPlan;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public Set<String> getDeviceSerials() {
+        Set<String> serials = new HashSet<>();
+        for (IModuleResult module : mModuleResults.values()) {
+            serials.add(module.getDeviceSerial());
+        }
+        return serials;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public File getResultDir() {
+        return mResultDir;
+    }
+
+}
diff --git a/common/util/src/com/android/compatibility/common/util/MeasureRun.java b/common/util/src/com/android/compatibility/common/util/MeasureRun.java
index d58b8d438..2b8905f 100644
--- a/common/util/src/com/android/compatibility/common/util/MeasureRun.java
+++ b/common/util/src/com/android/compatibility/common/util/MeasureRun.java
@@ -25,7 +25,7 @@
      */
     public void prepare(int i) throws Exception {
         // default empty implementation
-    };
+    }
 
     abstract public void run(int i) throws Exception;
 }
diff --git a/common/util/src/com/android/compatibility/common/util/MetricsXmlSerializer.java b/common/util/src/com/android/compatibility/common/util/MetricsXmlSerializer.java
index 0e2b004..0f4b597 100644
--- a/common/util/src/com/android/compatibility/common/util/MetricsXmlSerializer.java
+++ b/common/util/src/com/android/compatibility/common/util/MetricsXmlSerializer.java
@@ -21,6 +21,7 @@
 import java.io.IOException;
 import java.util.List;
 
+//TODO(stuartscott): Delete file for v2, ReportLog can serialize itself.
 /**
  * Serialize Metric data from {@link ReportLog} into compatibility report friendly XML
  */
@@ -36,26 +37,26 @@
         if (reportLog == null) {
             return;
         }
-        ReportLog.Result summary = reportLog.getSummary();
-        List<ReportLog.Result> detailedMetrics = reportLog.getDetailedMetrics();
+        ReportLog.Metric summary = reportLog.getSummary();
+        List<ReportLog.Metric> detailedMetrics = reportLog.getDetailedMetrics();
         // <Summary message="Average" scoreType="lower_better" unit="ms">195.2</Summary>
         if (summary != null) {
             mXmlSerializer.startTag(null, "Summary");
             mXmlSerializer.attribute(null, "message", summary.getMessage());
-            mXmlSerializer.attribute(null, "scoreType", summary.getType().getXmlString());
-            mXmlSerializer.attribute(null, "unit", summary.getUnit().getXmlString());
+            mXmlSerializer.attribute(null, "scoreType", summary.getType().toReportString());
+            mXmlSerializer.attribute(null, "unit", summary.getUnit().toReportString());
             mXmlSerializer.text(Double.toString(summary.getValues()[0]));
             mXmlSerializer.endTag(null, "Summary");
         }
 
         if (!detailedMetrics.isEmpty()) {
             mXmlSerializer.startTag(null, "Details");
-            for (ReportLog.Result result : detailedMetrics) {
+            for (ReportLog.Metric result : detailedMetrics) {
                 mXmlSerializer.startTag(null, "ValueArray");
-                mXmlSerializer.attribute(null, "source", result.getLocation());
+                mXmlSerializer.attribute(null, "source", result.getSource());
                 mXmlSerializer.attribute(null, "message", result.getMessage());
-                mXmlSerializer.attribute(null, "scoreType", result.getType().getXmlString());
-                mXmlSerializer.attribute(null, "unit", result.getUnit().getXmlString());
+                mXmlSerializer.attribute(null, "scoreType", result.getType().toReportString());
+                mXmlSerializer.attribute(null, "unit", result.getUnit().toReportString());
 
                 for (double value : result.getValues()) {
                     mXmlSerializer.startTag(null, "Value");
@@ -67,4 +68,4 @@
             mXmlSerializer.endTag(null, "Details");
         }
     }
-}
+}
\ No newline at end of file
diff --git a/common/util/src/com/android/compatibility/common/util/ModuleResult.java b/common/util/src/com/android/compatibility/common/util/ModuleResult.java
new file mode 100644
index 0000000..eb4b568
--- /dev/null
+++ b/common/util/src/com/android/compatibility/common/util/ModuleResult.java
@@ -0,0 +1,132 @@
+/*
+ * 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.util;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Data structure for a Compatibility test module result.
+ */
+public class ModuleResult implements IModuleResult {
+
+    private String mDeviceSerial;
+    private String mId;
+
+    private Map<String, ICaseResult> mResults = new HashMap<>();
+
+    /**
+     * Creates a {@link ModuleResult} for the given id, created with
+     * {@link AbiUtils#createId(String, String)}
+     */
+    public ModuleResult(String id) {
+        mId = id;
+    }
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void setDeviceSerial(String deviceSerial) {
+        mDeviceSerial = deviceSerial;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public String getDeviceSerial() {
+        return mDeviceSerial;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public String getId() {
+        return mId;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public String getName() {
+        return AbiUtils.parseTestName(mId);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public String getAbi() {
+        return AbiUtils.parseAbi(mId);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public ICaseResult getOrCreateResult(String caseName) {
+        ICaseResult result = mResults.get(caseName);
+        if (result == null) {
+            result = new CaseResult(caseName);
+            mResults.put(caseName, result);
+        }
+        return result;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public ICaseResult getResult(String caseName) {
+        return mResults.get(caseName);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public List<ICaseResult> getResults() {
+        ArrayList<ICaseResult> results = new ArrayList<>(mResults.values());
+        Collections.sort(results);
+        return results;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int countResults(TestStatus status) {
+        int total = 0;
+        for (ICaseResult result : mResults.values()) {
+            total += result.countResults(status);
+        }
+        return total;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int compareTo(IModuleResult another) {
+        return getId().compareTo(another.getId());
+    }
+
+}
diff --git a/common/util/src/com/android/compatibility/common/util/MultipartForm.java b/common/util/src/com/android/compatibility/common/util/MultipartForm.java
new file mode 100644
index 0000000..4ae7860
--- /dev/null
+++ b/common/util/src/com/android/compatibility/common/util/MultipartForm.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2011 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.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.util.HashMap;
+import java.util.Map;
+
+/** Builds a multipart form and submits it. */
+class MultipartForm {
+
+    private static final String FORM_DATA_BOUNDARY = "C75I55u3R3p0r73r";
+
+    /* package */ final String mServerUrl;
+    /* package */ final Map<String, String> mFormValues = new HashMap<String, String>();
+    /* package */ String mName;
+    /* package */ String mFileName;
+    /* package */ byte[] mData;
+
+    /**
+     * Creates a new multi-part form with the given serverUrl.
+     */
+    public MultipartForm(String serverUrl) {
+        mServerUrl = serverUrl;
+    }
+
+    /**
+     * Adds a key value attribute to the form.
+     *
+     * @param name the name of the attribute.
+     * @param value the attribute's value.
+     * @return the {@link MultipartForm} for easy chaining.
+     */
+    public MultipartForm addFormValue(String name, String value) {
+        mFormValues.put(name, value);
+        return this;
+    }
+
+    /**
+     * Adds the file as the payload of the form.
+     *
+     * @param name The name of attribute
+     * @param fileName The file's name
+     * @param data The file's data
+     * @return the {@link MultipartForm} for easy chaining.
+     */
+    public MultipartForm addFormFile(String name, String fileName, byte[] data) {
+        mName = name;
+        mFileName = fileName;
+        mData = data;
+        return this;
+    }
+
+    /**
+     * Submits the form to the server url.
+     *
+     * This will handle a redirection from the server.
+     *
+     * @throws IOException
+     */
+    public void submit() throws IOException {
+        String redirectUrl = submitForm(mServerUrl);
+        if (redirectUrl != null) {
+            submitForm(redirectUrl);
+        }
+    }
+
+    /**
+     * @param serverUrl to post the data to
+     * @return a url if the server redirected to another url
+     * @throws IOException
+     */
+    private String submitForm(String serverUrl) throws IOException {
+        HttpURLConnection connection = null;
+        try {
+            URL url = new URL(serverUrl);
+            connection = (HttpURLConnection) url.openConnection();
+            connection.setInstanceFollowRedirects(false);
+            connection.setRequestMethod("POST");
+            connection.setDoOutput(true);
+            connection.setRequestProperty("Content-Type",
+                    "multipart/form-data; boundary=" + FORM_DATA_BOUNDARY);
+
+            byte[] body = getContentBody();
+            connection.setRequestProperty("Content-Length", Integer.toString(body.length));
+
+            OutputStream output = connection.getOutputStream();
+            try {
+                output.write(body);
+            } finally {
+                output.close();
+            }
+
+            // Open the stream to get a response. Otherwise request will be cancelled.
+            InputStream input = connection.getInputStream();
+            input.close();
+
+            if (connection.getResponseCode() == 302) {
+                return connection.getHeaderField("Location");
+            }
+        } finally {
+            if (connection != null) {
+                connection.disconnect();
+            }
+        }
+
+        return null;
+    }
+
+    /* package */ byte[] getContentBody() throws IOException {
+        ByteArrayOutputStream byteOutput = new ByteArrayOutputStream();
+        PrintWriter writer = new PrintWriter(new OutputStreamWriter(byteOutput));
+        writer.println();
+
+        for (Map.Entry<String, String> formValue : mFormValues.entrySet()) {
+            writeFormField(writer, formValue.getKey(), formValue.getValue());
+        }
+
+        if (mData != null) {
+            writeFormFileHeader(writer, mName, mFileName);
+            writer.flush(); // Must flush here before writing to the byte stream!
+            byteOutput.write(mData);
+            writer.println();
+        }
+        writer.append("--").append(FORM_DATA_BOUNDARY).println("--");
+        writer.flush();
+        writer.close();
+        return byteOutput.toByteArray();
+    }
+
+    private void writeFormField(PrintWriter writer, String name, String value) {
+        writer.append("--").println(FORM_DATA_BOUNDARY);
+        writer.append("Content-Disposition: form-data; name=\"").append(name).println("\"");
+        writer.println();
+        writer.println(value);
+    }
+
+    private void writeFormFileHeader(PrintWriter writer, String name, String fileName) {
+        writer.append("--").println(FORM_DATA_BOUNDARY);
+        writer.append("Content-Disposition: form-data; name=\"").append(name);
+        writer.append("\"; filename=\"").append(fileName).println("\"");
+        writer.println("Content-Type: application/x-gzip");
+        writer.println("Content-Transfer-Encoding: binary");
+        writer.println();
+    }
+}
diff --git a/common/util/src/com/android/compatibility/common/util/ReportLog.java b/common/util/src/com/android/compatibility/common/util/ReportLog.java
index 7209ac8..31320c1 100644
--- a/common/util/src/com/android/compatibility/common/util/ReportLog.java
+++ b/common/util/src/com/android/compatibility/common/util/ReportLog.java
@@ -16,66 +16,71 @@
 
 package com.android.compatibility.common.util;
 
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlPullParserFactory;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
 import java.io.Serializable;
 import java.util.ArrayList;
 import java.util.List;
-import java.util.StringTokenizer;
-import java.util.regex.Pattern;
 
 /**
  * Utility class to add results to the report.
  */
 public class ReportLog implements Serializable {
 
-    private static final String LOG_SEPARATOR = "+++";
-    private static final String SUMMARY_SEPARATOR = "++++";
-    private static final String LOG_ELEM_SEPARATOR = "|";
-    private static final String EMPTY_CHAR = " ";
-    private Result mSummary;
-    private final List<Result> mDetails = new ArrayList<Result>();
+    private static final String ENCODING = "UTF-8";
+    private static final String TYPE = "org.kxml2.io.KXmlParser,org.kxml2.io.KXmlSerializer";
 
-    static class Result implements Serializable {
-        private String mLocation;
+    // XML constants
+    private static final String DETAIL_TAG = "Detail";
+    private static final String METRIC_TAG = "Metric";
+    private static final String MESSAGE_ATTR = "message";
+    private static final String SCORETYPE_ATTR = "score-type";
+    private static final String SCOREUNIT_ATTR = "score-unit";
+    private static final String SOURCE_ATTR = "source";
+    private static final String SUMMARY_TAG = "Summary";
+    private static final String VALUE_TAG = "Value";
+
+    private Metric mSummary;
+    private final List<Metric> mDetails = new ArrayList<>();
+
+    public static class Metric implements Serializable {
+        private String mSource;
         private String mMessage;
         private double[] mValues;
         private ResultType mType;
         private ResultUnit mUnit;
-        private Double mTarget;
 
-
-        private Result(String location, String message, double[] values,
-                ResultType type, ResultUnit unit) {
-            this(location, message, values, null /*target*/, type, unit);
+        Metric(String source, String message, double value, ResultType type, ResultUnit unit) {
+            this(source, message, new double[] { value }, type, unit);
         }
 
         /**
-         * Creates a result object to be included in the report. Each object has a message
+         * Creates a metric array to be included in the report. Each object has a message
          * describing its values and enums to interpret them. In addition, each result also includes
          * class, method and line number information about the test which added this result which is
          * collected by looking at the stack trace.
          *
          * @param message A string describing the values
          * @param values An array of the values
-         * @param target Nullable. The target value.
          * @param type Represents how to interpret the values (eg. A lower score is better)
          * @param unit Represents the unit in which the values are (eg. Milliseconds)
          */
-        private Result(String location, String message, double[] values,
-                Double target, ResultType type, ResultUnit unit) {
-            mLocation = location;
+        Metric(String source, String message, double[] values, ResultType type, ResultUnit unit) {
+            mSource = source;
             mMessage = message;
             mValues = values;
             mType = type;
             mUnit = unit;
-            mTarget = target;
         }
 
-        public double getTarget() {
-            return mTarget;
-        }
-
-        public String getLocation() {
-            return mLocation;
+        public String getSource() {
+            return mSource;
         }
 
         public String getMessage() {
@@ -94,137 +99,189 @@
             return mUnit;
         }
 
-        /**
-         * Format:
-         * location|message|target|type|unit|value[s], target can be " " if there is no target set.
-         * log for array = classMethodName:line_number|message|unit|type|space separated values
-         */
-        String toEncodedString() {
-            StringBuilder builder = new StringBuilder()
-                    .append(mLocation)
-                    .append(LOG_ELEM_SEPARATOR)
-                    .append(mMessage)
-                    .append(LOG_ELEM_SEPARATOR)
-                    .append(mTarget != null ? mTarget : EMPTY_CHAR)
-                    .append(LOG_ELEM_SEPARATOR)
-                    .append(mType.name())
-                    .append(LOG_ELEM_SEPARATOR)
-                    .append(mUnit.name())
-                    .append(LOG_ELEM_SEPARATOR);
-            for (double value : mValues) {
-                builder.append(value).append(" ");
+        void serialize(XmlSerializer serializer)
+                throws IllegalArgumentException, IllegalStateException, IOException {
+            serializer.startTag(null, METRIC_TAG);
+            serializer.attribute(null, SOURCE_ATTR, getSource());
+            serializer.attribute(null, MESSAGE_ATTR, getMessage());
+            serializer.attribute(null, SCORETYPE_ATTR, getType().toReportString());
+            serializer.attribute(null, SCOREUNIT_ATTR, getUnit().toReportString());
+            for (double d : getValues()) {
+                serializer.startTag(null, VALUE_TAG);
+                serializer.text(Double.toString(d));
+                serializer.endTag(null, VALUE_TAG);
             }
-            return builder.toString();
+            serializer.endTag(null, METRIC_TAG);
         }
 
-        static Result fromEncodedString(String encodedString) {
-            String[] elems = encodedString.split(Pattern.quote(LOG_ELEM_SEPARATOR));
-            if (elems.length < 5) {
-                return null;
+        static Metric parse(XmlPullParser parser)
+                throws XmlPullParserException, IOException {
+            parser.require(XmlPullParser.START_TAG, null, METRIC_TAG);
+            String source = parser.getAttributeValue(null, SOURCE_ATTR);
+            String message = parser.getAttributeValue(null, MESSAGE_ATTR);
+            ResultType type = ResultType.parseReportString(
+                    parser.getAttributeValue(null, SCORETYPE_ATTR));
+            ResultUnit unit = ResultUnit.parseReportString(
+                    parser.getAttributeValue(null, SCOREUNIT_ATTR));
+            List<String> valuesList = new ArrayList<>();
+            while (parser.nextTag() == XmlPullParser.START_TAG) {
+                parser.require(XmlPullParser.START_TAG, null, VALUE_TAG);
+                valuesList.add(parser.nextText());
+                parser.require(XmlPullParser.END_TAG, null, VALUE_TAG);
             }
-
-            String[] valueStrArray = elems[5].split(" ");
-            double[] valueArray = new double[valueStrArray.length];
-            for (int i = 0; i < valueStrArray.length; i++) {
-                valueArray[i] = Double.parseDouble(valueStrArray[i]);
+            int length = valuesList.size();
+            double[] values = new double[length];
+            for (int i = 0; i < length; i++) {
+                values[i] = Double.parseDouble(valuesList.get(i));
             }
-            return new Result(
-                    elems[0], /*location*/
-                    elems[1], /*message*/
-                    valueArray, /*values*/
-                    elems[2].equals(EMPTY_CHAR) ? null : Double.parseDouble(elems[2]), /*target*/
-                    ResultType.valueOf(elems[3]), /*type*/
-                    ResultUnit.valueOf(elems[4])  /*unit*/);
+            parser.require(XmlPullParser.END_TAG, null, METRIC_TAG);
+            return new Metric(source, message, values, type, unit);
         }
     }
 
     /**
-     * Adds an array of values to the report.
+     * @param elem
+     */
+    /* package */ void addMetric(Metric elem) {
+        mDetails.add(elem);
+    }
+
+    /**
+     * Adds an array of metrics to the report.
      */
     public void addValues(String message, double[] values, ResultType type, ResultUnit unit) {
-        mDetails.add(new Result(Stacktrace.getTestCallerClassMethodNameLineNumber(),
+        addMetric(new Metric(Stacktrace.getTestCallerClassMethodNameLineNumber(),
                 message, values, type, unit));
     }
 
     /**
-     * Adds an array of values to the report.
+     * Adds an array of metrics to the report.
      */
-    public void addValues(
-            String message, double[] values, ResultType type, ResultUnit unit, String location) {
-        mDetails.add(new Result(location, message, values, type, unit));
+    public void addValues(String source, String message, double[] values, ResultType type,
+            ResultUnit unit) {
+        addMetric(new Metric(source, message, values, type, unit));
     }
 
     /**
-     * Adds a value to the report.
+     * Adds a metric to the report.
      */
     public void addValue(String message, double value, ResultType type, ResultUnit unit) {
-        mDetails.add(new Result(Stacktrace.getTestCallerClassMethodNameLineNumber(), message,
-                new double[] {value}, type, unit));
+        addMetric(new Metric(Stacktrace.getTestCallerClassMethodNameLineNumber(), message,
+                value, type, unit));
     }
 
     /**
-     * Adds a value to the report.
+     * Adds a metric to the report.
      */
-    public void addValue(String message, double value, ResultType type,
-            ResultUnit unit, String location) {
-        mDetails.add(new Result(location, message, new double[] {value}, type, unit));
+    public void addValue(String source, String message, double value, ResultType type,
+            ResultUnit unit) {
+        addMetric(new Metric(source, message, value, type, unit));
+    }
+
+    /**
+     * @param elem
+     */
+    /* package */ void setSummary(Metric elem) {
+        mSummary = elem;
     }
 
     /**
      * Sets the summary of the report.
      */
     public void setSummary(String message, double value, ResultType type, ResultUnit unit) {
-        mSummary = new Result(Stacktrace.getTestCallerClassMethodNameLineNumber(),
-                message, new double[] {value}, type, unit);
+        setSummary(new Metric(Stacktrace.getTestCallerClassMethodNameLineNumber(),
+                message, value, type, unit));
     }
 
-    public Result getSummary() {
+    public Metric getSummary() {
         return mSummary;
     }
 
-    public List<Result> getDetailedMetrics() {
-        return new ArrayList<Result>(mDetails);
+    public List<Metric> getDetailedMetrics() {
+        return new ArrayList<Metric>(mDetails);
     }
 
     /**
-     * Parse a String encoded {@link com.android.compatibility.common.util.ReportLog}
+     * Serializes a given {@link ReportLog} to a String.
+     * @throws XmlPullParserException
+     * @throws IOException
+     * @throws IllegalStateException
+     * @throws IllegalArgumentException
      */
-    public static ReportLog fromEncodedString(String encodedString) {
-        ReportLog reportLog = new ReportLog();
-        StringTokenizer tok = new StringTokenizer(encodedString, SUMMARY_SEPARATOR);
-        if (tok.hasMoreTokens()) {
-            // Extract the summary
-            reportLog.mSummary = Result.fromEncodedString(tok.nextToken());
+    public static String serialize(ReportLog reportlog) throws XmlPullParserException,
+            IllegalArgumentException, IllegalStateException, IOException {
+        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+        XmlSerializer serializer = XmlPullParserFactory.newInstance(TYPE, null).newSerializer();
+        serializer.setOutput(byteArrayOutputStream, ENCODING);
+        serializer.startDocument(ENCODING, true);
+        serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
+        serialize(serializer, reportlog);
+        serializer.endDocument();
+        return byteArrayOutputStream.toString(ENCODING);
+    }
+
+    /**
+     * Serializes a given {@link ReportLog} to XML.
+     * @param serializer
+     * @param reportLog
+     * @throws IOException
+     */
+    public static void serialize(XmlSerializer serializer, ReportLog reportLog)
+            throws IOException {
+        if (reportLog == null) {
+            return;
         }
-        if (tok.hasMoreTokens()) {
-            // Extract the detailed results
-            StringTokenizer detailedTok = new StringTokenizer(tok.nextToken(), LOG_SEPARATOR);
-            while (detailedTok.hasMoreTokens()) {
-                reportLog.mDetails.add(Result.fromEncodedString(detailedTok.nextToken()));
+        Metric summary = reportLog.getSummary();
+        List<Metric> detailedMetrics = reportLog.getDetailedMetrics();
+        if (summary != null) {
+            serializer.startTag(null, SUMMARY_TAG);
+            summary.serialize(serializer);
+            serializer.endTag(null, SUMMARY_TAG);
+        }
+
+        if (!detailedMetrics.isEmpty()) {
+            serializer.startTag(null, DETAIL_TAG);
+            for (Metric elem : detailedMetrics) {
+                elem.serialize(serializer);
             }
+            serializer.endTag(null, DETAIL_TAG);
         }
-        return reportLog;
     }
 
     /**
-     * @return a String representation of this report or null if not collected
+     * Parses a {@link ReportLog} from the given string.
+     * @throws XmlPullParserException
+     * @throws IOException
      */
-    protected String toEncodedString() {
-        if ((mSummary == null) && mDetails.isEmpty()) {
-            // just return empty string
-            return null;
-        }
-        StringBuilder builder = new StringBuilder();
-        builder.append(mSummary.toEncodedString());
-        builder.append(SUMMARY_SEPARATOR);
-        for (Result result : mDetails) {
-            builder.append(result.toEncodedString());
-            builder.append(LOG_SEPARATOR);
-        }
-        // delete the last separator
-        if (builder.length() >= LOG_SEPARATOR.length()) {
-            builder.delete(builder.length() - LOG_SEPARATOR.length(), builder.length());
-        }
-        return builder.toString();
+    public static ReportLog parse(String result) throws XmlPullParserException, IOException {
+        XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
+        XmlPullParser parser = factory.newPullParser();
+        parser.setInput(new ByteArrayInputStream(result.getBytes(ENCODING)), ENCODING);
+        parser.nextTag();
+        return parse(parser);
     }
+
+    /**
+     * Parses a {@link ReportLog} from the given XML parser.
+     * @param parser
+     * @throws IOException
+     * @throws XmlPullParserException
+     */
+    public static ReportLog parse(XmlPullParser parser) throws XmlPullParserException, IOException {
+        parser.require(XmlPullParser.START_TAG, null, SUMMARY_TAG);
+        parser.nextTag();
+        ReportLog report = new ReportLog();
+        report.setSummary(Metric.parse(parser));
+        parser.nextTag();
+        parser.require(XmlPullParser.END_TAG, null, SUMMARY_TAG);
+        parser.nextTag();
+        if (parser.getName().equals(DETAIL_TAG)) {
+            while (parser.nextTag() == XmlPullParser.START_TAG) {
+                report.addMetric(Metric.parse(parser));
+            }
+            parser.require(XmlPullParser.END_TAG, null, DETAIL_TAG);
+        }
+        return report;
+    }
+
 }
diff --git a/common/util/src/com/android/compatibility/common/util/ResultType.java b/common/util/src/com/android/compatibility/common/util/ResultType.java
index 779c6d0..9d0a5fa 100644
--- a/common/util/src/com/android/compatibility/common/util/ResultType.java
+++ b/common/util/src/com/android/compatibility/common/util/ResultType.java
@@ -30,9 +30,16 @@
     WARNING;
 
     /**
-     * Return string used in the XML report
+     * @return a string to be used in the report.
      */
-    public String getXmlString() {
+    public String toReportString() {
         return name().toLowerCase();
     }
+
+    /**
+     * Returns a {@link ResultType} given a string from the report.
+     */
+    public static ResultType parseReportString(String value) {
+        return ResultType.valueOf(value.toUpperCase());
+    }
 }
diff --git a/common/util/src/com/android/compatibility/common/util/ResultUnit.java b/common/util/src/com/android/compatibility/common/util/ResultUnit.java
index f38ddae..5b082f9 100644
--- a/common/util/src/com/android/compatibility/common/util/ResultUnit.java
+++ b/common/util/src/com/android/compatibility/common/util/ResultUnit.java
@@ -40,10 +40,16 @@
     SCORE;
 
     /**
-     * Return string used in the XML report
+     * @return a string to be used in the report.
      */
-    public String getXmlString() {
+    public String toReportString() {
         return name().toLowerCase();
     }
-}
 
+    /**
+     * Returns a {@link ResultUnit} given a string from the report.
+     */
+    public static ResultUnit parseReportString(String value) {
+        return ResultUnit.valueOf(value.toUpperCase());
+    }
+}
diff --git a/common/util/src/com/android/compatibility/common/util/ResultUploader.java b/common/util/src/com/android/compatibility/common/util/ResultUploader.java
new file mode 100644
index 0000000..ff8d156
--- /dev/null
+++ b/common/util/src/com/android/compatibility/common/util/ResultUploader.java
@@ -0,0 +1,73 @@
+/*
+ * 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.util;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.zip.GZIPOutputStream;
+
+/**
+ * Uploads a result through a HTTP POST multipart/form-data request containing
+ * the test result XML.
+ */
+public class ResultUploader {
+
+    private static final int RESULT_XML_BYTES = 500 * 1024;
+
+    /* package */ MultipartForm mMultipartForm;
+
+    public ResultUploader(String serverUrl, String suiteName) {
+        mMultipartForm = new MultipartForm(serverUrl).addFormValue("suite", suiteName);
+    }
+
+    /**
+     * Uploads the given file to the server.
+     *
+     * @param reportFile The file to upload.
+     * @param referenceUrl A reference url to use.
+     * @throws IOException
+     */
+    public void uploadResult(File reportFile, String referenceUrl) throws IOException {
+        InputStream input = new FileInputStream(reportFile);
+        try {
+            byte[] data = getBytes(input);
+            mMultipartForm.addFormFile("result-xml", "test-result.xml.gz", data);
+            if (referenceUrl == null || referenceUrl.trim().isEmpty()) {
+                mMultipartForm.addFormValue("reference-url", referenceUrl);
+            }
+            mMultipartForm.submit();
+        } finally {
+            input.close();
+        }
+    }
+
+    private static byte[] getBytes(InputStream input) throws IOException {
+        ByteArrayOutputStream byteOutput = new ByteArrayOutputStream(RESULT_XML_BYTES);
+        GZIPOutputStream gzipOutput = new GZIPOutputStream(byteOutput);
+        byte[] buffer = new byte[1024];
+        int count;
+        while ((count = input.read(buffer)) > 0) {
+            gzipOutput.write(buffer, 0, count);
+        }
+        gzipOutput.close();
+        return byteOutput.toByteArray();
+    }
+
+}
diff --git a/common/util/src/com/android/compatibility/common/util/Stat.java b/common/util/src/com/android/compatibility/common/util/Stat.java
index 8bf9bf7..9748440 100644
--- a/common/util/src/com/android/compatibility/common/util/Stat.java
+++ b/common/util/src/com/android/compatibility/common/util/Stat.java
@@ -22,6 +22,10 @@
  * Utilities for doing statistics
  */
 public class Stat {
+    /**
+     * Private constructor for static class.
+     */
+    private Stat() {}
 
     /**
      * Collection of statistical propertirs like average, max, min, and stddev
@@ -48,10 +52,8 @@
         double average = data[0];
         double min = data[0];
         double max = data[0];
-        double eX2 = data[0] * data[0]; // will become E[X^2]
         for (int i = 1; i < data.length; i++) {
             average += data[i];
-            eX2 += data[i] * data[i];
             if (data[i] > max) {
                 max = data[i];
             }
@@ -60,9 +62,13 @@
             }
         }
         average /= data.length;
-        eX2 /= data.length;
-        // stddev = sqrt(E[X^2] - (E[X])^2)
-        double stddev = Math.sqrt(eX2 - average * average);
+        double sumOfSquares = 0.0;
+        for (int i = 0; i < data.length; i++) {
+            double diff = average - data[i];
+            sumOfSquares += diff * diff;
+        }
+        double variance = sumOfSquares / (data.length - 1);
+        double stddev = Math.sqrt(variance);
         return new StatResult(average, min, max, stddev, data.length);
     }
 
@@ -84,37 +90,16 @@
         double thresholdMin = median * (1.0 - rejectionThreshold);
         double thresholdMax = median * (1.0 + rejectionThreshold);
 
-        double average = 0.0;
-        double min = median;
-        double max = median;
-        double eX2 = 0.0; // will become E[X^2]
-        int validDataCounter = 0;
+        double[] validData = new double[data.length];
+        int index = 0;
         for (int i = 0; i < data.length; i++) {
             if ((data[i] > thresholdMin) && (data[i] < thresholdMax)) {
-                validDataCounter++;
-                average += data[i];
-                eX2 += data[i] * data[i];
-                if (data[i] > max) {
-                    max = data[i];
-                }
-                if (data[i] < min) {
-                    min = data[i];
-                }
+                validData[index] = data[i];
+                index++;
             }
             // TODO report rejected data
         }
-        double stddev;
-        if (validDataCounter > 0) {
-            average /= validDataCounter;
-            eX2 /= validDataCounter;
-            // stddev = sqrt(E[X^2] - (E[X])^2)
-            stddev = Math.sqrt(eX2 - average * average);
-        } else { // both median is showing too much diff
-            average = median;
-            stddev = 0; // don't care
-        }
-
-        return new StatResult(average, min, max, stddev, validDataCounter);
+        return getStat(Arrays.copyOf(validData, index));
     }
 
     /**
@@ -159,7 +144,6 @@
      * timeInSec with 0 value will be changed to small value to prevent divide by zero.
      * @param change total change of quality for the given duration timeInMSec.
      * @param timeInMSec
-     * @return
      */
     public static double calcRatePerSec(double change, double timeInMSec) {
         if (timeInMSec == 0) {
@@ -185,4 +169,14 @@
         return result;
     }
 
+    /**
+     * Get the value of the 95th percentile using nearest rank algorithm.
+     */
+    public static double get95PercentileValue(double[] values) {
+        Arrays.sort(values);
+        // zero-based array index
+        int index = (int) Math.round(values.length * 0.95 + .5) - 1;
+        return values[index];
+    }
+
 }
diff --git a/common/util/src/com/android/compatibility/common/util/TestFilter.java b/common/util/src/com/android/compatibility/common/util/TestFilter.java
new file mode 100644
index 0000000..78b68cf
--- /dev/null
+++ b/common/util/src/com/android/compatibility/common/util/TestFilter.java
@@ -0,0 +1,128 @@
+/*
+ * 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.util;
+
+/**
+ * Represents a filter for including and excluding tests.
+ */
+public class TestFilter {
+
+    private final String mAbi;
+    private final String mName;
+    private final String mTest;
+
+    /**
+     * Builds a new {@link TestFilter} from the given string. Filters can be in one of four forms,
+     * the instance will be initialized as;
+     * -"name"              -> abi = null, name = "name", test = null
+     * -"name" "test"       -> abi = null, name = "name", test = "test"
+     * -"abi" "name"        -> abi = "abi", name = "name", test = null
+     * -"abi" "name" test"  -> abi = "abi", name = "name", test = "test"
+     *
+     * @param filter the filter to parse
+     * @return the {@link TestFilter}
+     */
+    public static TestFilter createFrom(String filter) {
+        String[] parts = filter.split(" ");
+        String abi = null, name = null, test = null;
+        // Either:
+        // <name>
+        // <name> <test>
+        // <abi> <name>
+        // <abi> <name> <test>
+        if (parts.length == 1) {
+            name = parts[0];
+        } else if (parts.length == 2) {
+            if (AbiUtils.isAbiSupportedByCompatibility(parts[0])) {
+                abi = parts[0];
+                name = parts[1];
+            } else {
+                name = parts[0];
+                test = parts[1];
+            }
+        } else if (parts.length == 3){
+            abi = parts[0];
+            name = parts[1];
+            test = parts[2];
+        } else {
+            throw new IllegalArgumentException(String.format("Could not parse filter: %s", filter));
+        }
+        return new TestFilter(abi, name, test);
+    }
+
+    /**
+     * Creates a new {@link TestFilter} from the given parts.
+     *
+     * @param abi The ABI must be supported {@link AbiUtils#isAbiSupportedByCompatibility(String)}
+     * @param name The module's name
+     * @param test The test's identifier eg <package>.<class>#<method>
+     */
+    public TestFilter(String abi, String name, String test) {
+        mAbi = abi;
+        mName = name;
+        mTest = test;
+    }
+
+    /**
+     * Returns a String representation of this filter. This function is the inverse of
+     * {@link TestFilter#createFrom(String)}.
+     *
+     * For a valid filter f;
+     * <pre>
+     * {@code
+     * new TestFilter(f).toString().equals(f)
+     * }
+     * </pre>
+     */
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        if (mAbi != null) {
+            sb.append(mAbi);
+            sb.append(" ");
+        }
+        if (mName != null) {
+            sb.append(mName);
+        }
+        if (mTest != null) {
+            sb.append(" ");
+            sb.append(mTest);
+        }
+        return sb.toString();
+    }
+
+    /**
+     * @return the abi of this filter, or null if not specified.
+     */
+    public String getAbi() {
+        return mAbi;
+    }
+
+    /**
+     * @return the module name of this filter, or null if not specified.
+     */
+    public String getName() {
+        return mName;
+    }
+
+    /**
+     * @return the test identifier of this filter, or null if not specified.
+     */
+    public String getTest() {
+        return mTest;
+    }
+
+}
diff --git a/common/util/src/com/android/compatibility/common/util/TestResult.java b/common/util/src/com/android/compatibility/common/util/TestResult.java
new file mode 100644
index 0000000..551aaaa
--- /dev/null
+++ b/common/util/src/com/android/compatibility/common/util/TestResult.java
@@ -0,0 +1,268 @@
+/*
+ * 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.util;
+
+/**
+ * Represents a single test result.
+ */
+public class TestResult implements ITestResult {
+
+    private final ICaseResult mParent;
+    private final String mTestName;
+    private long mStartTime;
+    private long mEndTime;
+    private TestStatus mResult;
+    private String mMessage;
+    private String mStackTrace;
+    private ReportLog mReport;
+    private String mBugReport;
+    private String mLog;
+    private String mScreenshot;
+
+    /**
+     * Create a {@link TestResult} for the given test name.
+     */
+    public TestResult(ICaseResult parent, String name) {
+        mParent = parent;
+        mTestName = name;
+        resetResult();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public String getName() {
+        return mTestName;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public String getFullName() {
+        return String.format("%s#%s", mParent.getName(), getName());
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public TestStatus getResultStatus() {
+        return mResult;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void setResultStatus(TestStatus status) {
+        mResult = status;
+        mEndTime = System.currentTimeMillis();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public String getMessage() {
+        return mMessage;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void setMessage(String message) {
+        mMessage = message;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void setStartTime(long time) {
+        mStartTime = time;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public long getStartTime() {
+        return mStartTime;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void setEndTime(long time) {
+        mEndTime = time;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public long getEndTime() {
+        return mEndTime;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public String getStackTrace() {
+        return mStackTrace;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void setStackTrace(String stackTrace) {
+        mStackTrace = sanitizeStackTrace(stackTrace);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public ReportLog getReportLog() {
+        return mReport;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void setReportLog(ReportLog report) {
+        mReport = report;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public String getBugReport() {
+        return mBugReport;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void setBugReport(String path) {
+        mBugReport = path;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public String getLog() {
+        return mLog;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void setLog(String path) {
+        mLog = path;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public String getScreenshot() {
+        return mScreenshot;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void setScreenshot(String path) {
+        mScreenshot = path;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void failed(String trace) {
+        setResultStatus(TestStatus.FAIL);
+        int index = trace.indexOf('\n');
+        if (index < 0) {
+            // Trace is a single line, just set the message to be the same as the stacktrace.
+            setMessage(trace);
+        } else {
+            setMessage(trace.substring(0, index));
+        }
+        setStackTrace(trace);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void passed(ReportLog report) {
+        if (!getResultStatus().equals(TestStatus.FAIL)) {
+            setResultStatus(TestStatus.PASS);
+            if (report != null) {
+                setReportLog(report);
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void resetResult() {
+        setResultStatus(TestStatus.NOT_EXECUTED);
+        setStartTime(System.currentTimeMillis());
+        setMessage(null);
+        setStackTrace(null);
+        setReportLog(null);
+        setBugReport(null);
+        setLog(null);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int compareTo(ITestResult another) {
+        return getName().compareTo(another.getName());
+    }
+
+    /**
+     * Strip out any invalid XML characters that might cause the report to be unviewable.
+     * http://www.w3.org/TR/REC-xml/#dt-character
+     */
+    static String sanitizeStackTrace(String trace) {
+        if (trace != null) {
+            return trace.replaceAll("[^\\u0009\\u000A\\u000D\\u0020-\\uD7FF\\uE000-\\uFFFD]", "");
+        } else {
+            return null;
+        }
+    }
+
+}
diff --git a/common/util/src/com/android/compatibility/common/util/TestStatus.java b/common/util/src/com/android/compatibility/common/util/TestStatus.java
new file mode 100644
index 0000000..ef14547
--- /dev/null
+++ b/common/util/src/com/android/compatibility/common/util/TestStatus.java
@@ -0,0 +1,56 @@
+/*
+ * 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.util;
+
+/**
+ * An enum of possible test statuses.
+ */
+public enum TestStatus {
+    PASS("pass"),
+    FAIL("fail"),
+    NOT_EXECUTED("not-executed");
+
+    private String mValue;
+
+    private TestStatus(String storedValue) {
+        mValue = storedValue;
+    }
+
+    /**
+     * Get the String representation of this test status that should be stored in
+     * xml
+     */
+    public String getValue() {
+       return mValue;
+    }
+
+    /**
+     * Find the {@link TestStatus} corresponding to given string value
+     * <p/>
+     * Performs a case insensitive search
+     *
+     * @param value
+     * @return the {@link TestStatus} or <code>null</code> if it could not be found
+     */
+    static TestStatus getStatus(String  value) {
+        for (TestStatus status : TestStatus.values()) {
+            if (value.compareToIgnoreCase(status.getValue()) == 0) {
+                return status;
+            }
+        }
+        return null;
+    }
+}
diff --git a/common/util/src/com/android/compatibility/common/util/XmlResultHandler.java b/common/util/src/com/android/compatibility/common/util/XmlResultHandler.java
new file mode 100644
index 0000000..9f74e67
--- /dev/null
+++ b/common/util/src/com/android/compatibility/common/util/XmlResultHandler.java
@@ -0,0 +1,305 @@
+/*
+ * 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.util;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlPullParserFactory;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.Map.Entry;
+import java.util.List;
+
+/**
+ * Handles conversion of results to/from XML.
+ */
+public class XmlResultHandler {
+
+    private static final String ENCODING = "UTF-8";
+    private static final String TYPE = "org.kxml2.io.KXmlParser,org.kxml2.io.KXmlSerializer";
+    private static final SimpleDateFormat TIME_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+    private static final String NS = null;
+    private static final String RESULT_FILE_VERSION = "5.0";
+    /* package */ static final String TEST_RESULT_FILE_NAME = "test-result.xml";
+
+    // XML constants
+    private static final String ABI_ATTR = "abi";
+    private static final String BUGREPORT_TAG = "BugReport";
+    private static final String CASE_TAG = "TestCase";
+    private static final String DEVICE_ATTR = "device";
+    private static final String DEVICE_INFO_TAG = "DeviceInfo";
+    private static final String END_TIME_ATTR = "end";
+    private static final String FAILED_ATTR = "failed";
+    private static final String FAILURE_TAG = "Failure";
+    private static final String HOST_NAME_ATTR = "host-name";
+    private static final String JAVA_VENDOR_ATTR = "java-vendor";
+    private static final String JAVA_VERSION_ATTR = "java-version";
+    private static final String LOGCAT_TAG = "Logcat";
+    private static final String MESSAGE_ATTR = "message";
+    private static final String MODULE_TAG = "Module";
+    private static final String NAME_ATTR = "name";
+    private static final String NOT_EXECUTED_ATTR = "not-executed";
+    private static final String OS_ARCH_ATTR = "os-arch";
+    private static final String OS_NAME_ATTR = "os-name";
+    private static final String OS_VERSION_ATTR = "os-version";
+    private static final String PASS_ATTR = "pass";
+    private static final String REPORT_VERSION_ATTR = "report-version";
+    private static final String RESULT_ATTR = "result";
+    private static final String RESULT_TAG = "Result";
+    private static final String SCREENSHOT_TAG = "Screenshot";
+    private static final String STACK_TAG = "StackTrace";
+    private static final String START_TIME_ATTR = "start";
+    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 SUMMARY_TAG = "Summary";
+    private static final String TEST_TAG = "Test";
+
+    /**
+     * @param resultsDir
+     */
+    public static List<IInvocationResult> getResults(File resultsDir) {
+        List<IInvocationResult> results = new ArrayList<>();
+        File[] files = resultsDir.listFiles();
+        if (files == null || files.length == 0) {
+            // No results, just return the empty list
+            return results;
+        }
+        for (File resultDir : files) {
+            if (!resultDir.isDirectory()) {
+                continue;
+            }
+            try {
+                IInvocationResult invocation = new InvocationResult(resultDir);
+                XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
+                XmlPullParser parser = factory.newPullParser();
+                parser.setInput(new FileReader(new File(resultDir, TEST_RESULT_FILE_NAME)));
+                parser.nextTag();
+                parser.require(XmlPullParser.START_TAG, NS, RESULT_TAG);
+                invocation.setStartTime(parseTimeStamp(
+                        parser.getAttributeValue(NS, START_TIME_ATTR)));
+                invocation.setTestPlan(parser.getAttributeValue(NS, SUITE_PLAN_ATTR));
+                parser.nextTag();
+                parser.require(XmlPullParser.START_TAG, NS, DEVICE_INFO_TAG);
+                // TODO(stuartscott): may want to reload these incase the retry was done with
+                // --skip-device-info flag
+                parser.nextTag();
+                parser.require(XmlPullParser.END_TAG, NS, DEVICE_INFO_TAG);
+                parser.nextTag();
+                parser.require(XmlPullParser.START_TAG, NS, SUMMARY_TAG);
+                parser.nextTag();
+                parser.require(XmlPullParser.END_TAG, NS, SUMMARY_TAG);
+                while (parser.nextTag() == XmlPullParser.START_TAG) {
+                    parser.require(XmlPullParser.START_TAG, NS, MODULE_TAG);
+                    String name = parser.getAttributeValue(NS, NAME_ATTR);
+                    String abi = parser.getAttributeValue(NS, ABI_ATTR);
+                    String id = AbiUtils.createId(abi, name);
+                    IModuleResult module = invocation.getOrCreateModule(id);
+                    module.setDeviceSerial(parser.getAttributeValue(NS, DEVICE_ATTR));
+                    while (parser.nextTag() == XmlPullParser.START_TAG) {
+                        parser.require(XmlPullParser.START_TAG, NS, CASE_TAG);
+                        String caseName = parser.getAttributeValue(NS, NAME_ATTR);
+                        ICaseResult testCase = module.getOrCreateResult(caseName);
+                        while (parser.nextTag() == XmlPullParser.START_TAG) {
+                            parser.require(XmlPullParser.START_TAG, NS, TEST_TAG);
+                            String testName = parser.getAttributeValue(NS, NAME_ATTR);
+                            ITestResult test = testCase.getOrCreateResult(testName);
+                            String result = parser.getAttributeValue(NS, RESULT_ATTR);
+                            test.setResultStatus(TestStatus.getStatus(result));
+                            test.setStartTime(parseTimeStamp(
+                                    parser.getAttributeValue(NS, START_TIME_ATTR)));
+                            test.setEndTime(parseTimeStamp(
+                                    parser.getAttributeValue(NS, END_TIME_ATTR)));
+                            if (parser.nextTag() == XmlPullParser.START_TAG) {
+                                if (parser.getName().equals(FAILURE_TAG)) {
+                                    test.setMessage(parser.getAttributeValue(NS, MESSAGE_ATTR));
+                                    if (parser.nextTag() == XmlPullParser.START_TAG) {
+                                        parser.require(XmlPullParser.START_TAG, NS, STACK_TAG);
+                                        test.setStackTrace(parser.nextText());
+                                        parser.require(XmlPullParser.END_TAG, NS, STACK_TAG);
+                                        parser.nextTag();
+                                    }
+                                    parser.require(XmlPullParser.END_TAG, NS, FAILURE_TAG);
+                                    parser.nextTag();
+                                } else if (parser.getName().equals(BUGREPORT_TAG)) {
+                                    test.setBugReport(parser.nextText());
+                                    parser.nextTag();
+                                } else if (parser.getName().equals(LOGCAT_TAG)) {
+                                    test.setLog(parser.nextText());
+                                    parser.nextTag();
+                                } else if (parser.getName().equals(SCREENSHOT_TAG)) {
+                                    test.setScreenshot(parser.nextText());
+                                    parser.nextTag();
+                                } else {
+                                    test.setReportLog(ReportLog.parse(parser));
+                                    parser.nextTag();
+                                }
+                            }
+                            parser.require(XmlPullParser.END_TAG, NS, TEST_TAG);
+                        }
+                        parser.require(XmlPullParser.END_TAG, NS, CASE_TAG);
+                    }
+                    parser.require(XmlPullParser.END_TAG, NS, MODULE_TAG);
+                }
+                parser.require(XmlPullParser.END_TAG, NS, RESULT_TAG);
+                results.add(invocation);
+            } catch (XmlPullParserException e) {
+                e.printStackTrace();
+            } catch (FileNotFoundException e) {
+                e.printStackTrace();
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        }
+        return results;
+    }
+
+    /**
+     * @param result
+     * @param resultDir
+     * @param startTime
+     * @return The result file created.
+     * @throws IOException
+     * @throws XmlPullParserException
+     */
+    public static File writeResults(String suiteName, String suiteVersion, String suitePlan,
+            IInvocationResult result, File resultDir, long startTime, long endTime)
+                    throws IOException, XmlPullParserException {
+        int passed = result.countResults(TestStatus.PASS);
+        int failed = result.countResults(TestStatus.FAIL);
+        int notExecuted = result.countResults(TestStatus.NOT_EXECUTED);
+        File resultFile = new File(resultDir, TEST_RESULT_FILE_NAME);
+        OutputStream stream = new FileOutputStream(resultFile);
+        XmlSerializer serializer = XmlPullParserFactory.newInstance(TYPE, null).newSerializer();
+        serializer.setOutput(stream, ENCODING);
+        serializer.startDocument(ENCODING, false);
+        serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
+        serializer.processingInstruction(
+                "xml-stylesheet type=\"text/xsl\" href=\"compatibility-result.xsl\"");
+        serializer.startTag(NS, RESULT_TAG);
+        serializer.attribute(NS, START_TIME_ATTR, formatTimeStamp(startTime));
+        serializer.attribute(NS, END_TIME_ATTR, formatTimeStamp(endTime));
+        serializer.attribute(NS, SUITE_NAME_ATTR, suiteName);
+        serializer.attribute(NS, SUITE_VERSION_ATTR, suiteVersion);
+        serializer.attribute(NS, SUITE_PLAN_ATTR, suitePlan);
+        serializer.attribute(NS, REPORT_VERSION_ATTR, RESULT_FILE_VERSION);
+
+        String hostName = "";
+        try {
+            hostName = InetAddress.getLocalHost().getHostName();
+        } catch (UnknownHostException ignored) {}
+        serializer.attribute(NS, HOST_NAME_ATTR, hostName);
+        serializer.attribute(NS, OS_NAME_ATTR, System.getProperty("os.name"));
+        serializer.attribute(NS, OS_VERSION_ATTR, System.getProperty("os.version"));
+        serializer.attribute(NS, OS_ARCH_ATTR, System.getProperty("os.arch"));
+        serializer.attribute(NS, JAVA_VENDOR_ATTR, System.getProperty("java.vendor"));
+        serializer.attribute(NS, JAVA_VERSION_ATTR, System.getProperty("java.version"));
+
+        // Device Info
+        serializer.startTag(NS, DEVICE_INFO_TAG);
+        for (Entry<String, String> entry : result.getDeviceInfo().entrySet()) {
+            serializer.attribute(NS, entry.getKey(), entry.getValue());
+        }
+        serializer.endTag(NS, DEVICE_INFO_TAG);
+
+        // Summary
+        serializer.startTag(NS, SUMMARY_TAG);
+        serializer.attribute(NS, PASS_ATTR, Integer.toString(passed));
+        serializer.attribute(NS, FAILED_ATTR, Integer.toString(failed));
+        serializer.attribute(NS, NOT_EXECUTED_ATTR, Integer.toString(notExecuted));
+        serializer.endTag(NS, SUMMARY_TAG);
+
+        // Results
+        for (IModuleResult module : result.getModules()) {
+            serializer.startTag(NS, MODULE_TAG);
+            serializer.attribute(NS, NAME_ATTR, module.getName());
+            serializer.attribute(NS, ABI_ATTR, module.getAbi());
+            serializer.attribute(NS, DEVICE_ATTR, module.getDeviceSerial());
+            for (ICaseResult cr : module.getResults()) {
+                serializer.startTag(NS, CASE_TAG);
+                serializer.attribute(NS, NAME_ATTR, cr.getName());
+                for (ITestResult r : cr.getResults()) {
+                    serializer.startTag(NS, TEST_TAG);
+                    serializer.attribute(NS, RESULT_ATTR, r.getResultStatus().getValue());
+                    serializer.attribute(NS, NAME_ATTR, r.getName());
+                    serializer.attribute(NS, START_TIME_ATTR, formatTimeStamp(r.getStartTime()));
+                    serializer.attribute(NS, END_TIME_ATTR, formatTimeStamp(r.getEndTime()));
+                    String message = r.getMessage();
+                    if (message != null) {
+                        serializer.startTag(NS, FAILURE_TAG);
+                        serializer.attribute(NS, MESSAGE_ATTR, message);
+                        String stackTrace = r.getStackTrace();
+                        if (stackTrace != null) {
+                            serializer.startTag(NS, STACK_TAG);
+                            serializer.text(stackTrace);
+                            serializer.endTag(NS, STACK_TAG);
+                        }
+                        serializer.endTag(NS, FAILURE_TAG);
+                    }
+                    String bugreport = r.getBugReport();
+                    if (bugreport != null) {
+                        serializer.startTag(NS, BUGREPORT_TAG);
+                        serializer.text(bugreport);
+                        serializer.endTag(NS, BUGREPORT_TAG);
+                    }
+                    String logcat = r.getLog();
+                    if (logcat != null) {
+                        serializer.startTag(NS, LOGCAT_TAG);
+                        serializer.text(logcat);
+                        serializer.endTag(NS, LOGCAT_TAG);
+                    }
+                    String screenshot = r.getScreenshot();
+                    if (screenshot != null) {
+                        serializer.startTag(NS, SCREENSHOT_TAG);
+                        serializer.text(screenshot);
+                        serializer.endTag(NS, SCREENSHOT_TAG);
+                    }
+                    ReportLog.serialize(serializer, r.getReportLog());
+                    serializer.endTag(NS, TEST_TAG);
+                }
+                serializer.endTag(NS, CASE_TAG);
+            }
+            serializer.endTag(NS, MODULE_TAG);
+        }
+        serializer.endDocument();
+        return resultFile;
+    }
+
+    private static String formatTimeStamp(long epochTime) {
+        return TIME_FORMAT.format(new Date(epochTime));
+    }
+
+    private static long parseTimeStamp(String time) {
+        try {
+            return TIME_FORMAT.parse(time).getTime();
+        } catch (ParseException e) {
+            e.printStackTrace();
+        }
+        return 0L;
+    }
+}
diff --git a/tests/tests/acceleration/Android.mk b/common/util/tests/Android.mk
similarity index 64%
copy from tests/tests/acceleration/Android.mk
copy to common/util/tests/Android.mk
index d417371..0e0af50 100644
--- a/tests/tests/acceleration/Android.mk
+++ b/common/util/tests/Android.mk
@@ -1,10 +1,10 @@
-# Copyright (C) 2011 The Android Open Source Project
+# 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
+# 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,
@@ -16,18 +16,12 @@
 
 include $(CLEAR_VARS)
 
-LOCAL_MODULE_TAGS := optional
-
-LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-
-LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner
-
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
-LOCAL_PACKAGE_NAME := CtsAccelerationTestCases
+LOCAL_JAVA_LIBRARIES := junit kxml2-2.3.0 tradefed-prebuilt compatibility-common-util-hostsidelib
 
-LOCAL_INSTRUMENTATION_FOR := CtsAccelerationTestStubs
+LOCAL_MODULE := compatibility-common-util-tests
 
-LOCAL_SDK_VERSION := current
+LOCAL_MODULE_TAGS := optional
 
-include $(BUILD_CTS_PACKAGE)
+include $(BUILD_HOST_JAVA_LIBRARY)
diff --git a/common/util/tests/src/com/android/compatibility/common/util/AbiUtilsTest.java b/common/util/tests/src/com/android/compatibility/common/util/AbiUtilsTest.java
new file mode 100644
index 0000000..567da54
--- /dev/null
+++ b/common/util/tests/src/com/android/compatibility/common/util/AbiUtilsTest.java
@@ -0,0 +1,58 @@
+/*
+ * 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.util;
+
+import junit.framework.TestCase;
+
+/**
+ * Unit tests for {@link AbiUtils}
+ */
+public class AbiUtilsTest extends TestCase {
+
+    private static final String MODULE_NAME = "ModuleName";
+    private static final String ABI_NAME = "mips64";
+    private static final String ABI_FLAG = "--abi mips64 ";
+    private static final String ABI_ID = "mips64 ModuleName";
+
+    public void testCreateAbiFlag() {
+        String flag = AbiUtils.createAbiFlag(ABI_NAME);
+        assertEquals("Incorrect flag created", ABI_FLAG, flag);
+    }
+
+    public void testCreateId() {
+        String id = AbiUtils.createId(ABI_NAME, MODULE_NAME);
+        assertEquals("Incorrect id created", ABI_ID, id);
+    }
+
+    public void testParseId() {
+        String[] parts = AbiUtils.parseId(ABI_ID);
+        assertEquals("Wrong size array", 2, parts.length);
+        assertEquals("Wrong abi name", ABI_NAME, parts[0]);
+        assertEquals("Wrong module name", MODULE_NAME, parts[1]);
+    }
+
+    public void testParseName() {
+        String name = AbiUtils.parseTestName(ABI_ID);
+        assertEquals("Incorrect module name", MODULE_NAME, name);
+    }
+
+    public void testParseAbi() {
+        String abi = AbiUtils.parseAbi(ABI_ID);
+        assertEquals("Incorrect abi name", ABI_NAME, abi);
+    }
+
+}
diff --git a/common/util/tests/src/com/android/compatibility/common/util/CaseResultTest.java b/common/util/tests/src/com/android/compatibility/common/util/CaseResultTest.java
new file mode 100644
index 0000000..ae29597
--- /dev/null
+++ b/common/util/tests/src/com/android/compatibility/common/util/CaseResultTest.java
@@ -0,0 +1,79 @@
+/*
+ * 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.util;
+
+import junit.framework.TestCase;
+
+/**
+ * Unit tests for {@link CaseResult}
+ */
+public class CaseResultTest extends TestCase {
+
+    private static final String CLASS = "android.test.FoorBar";
+    private static final String METHOD_1 = "testBlah1";
+    private static final String METHOD_2 = "testBlah2";
+    private static final String METHOD_3 = "testBlah3";
+    private static final String MESSAGE = "Something small is not alright";
+    private static final String STACK_TRACE = "Something small is not alright\n " +
+            "at four.big.insects.Marley.sing(Marley.java:10)";
+    private CaseResult mResult;
+
+    @Override
+    public void setUp() throws Exception {
+        mResult = new CaseResult(CLASS);
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+        mResult = null;
+    }
+
+    public void testAccessors() throws Exception {
+        assertEquals("Incorrect case name", CLASS, mResult.getName());
+    }
+
+    public void testResultCreation() throws Exception {
+        ITestResult testResult = mResult.getOrCreateResult(METHOD_1);
+        // Should create one
+        assertEquals("Expected one result", 1, mResult.getResults().size());
+        assertTrue("Expected test result", mResult.getResults().contains(testResult));
+        // Should not create another one
+        ITestResult testResult2 = mResult.getOrCreateResult(METHOD_1);
+        assertEquals("Expected the same result", testResult, testResult2);
+        assertEquals("Expected one result", 1, mResult.getResults().size());
+    }
+
+    public void testResultReporting() throws Exception {
+        ITestResult testResult = mResult.getOrCreateResult(METHOD_1);
+        testResult.failed(STACK_TRACE);
+        assertEquals("Expected status to be set", TestStatus.FAIL, testResult.getResultStatus());
+        assertEquals("Expected message to be set", MESSAGE, testResult.getMessage());
+        assertEquals("Expected stack to be set", STACK_TRACE, testResult.getStackTrace());
+        testResult = mResult.getOrCreateResult(METHOD_2);
+        testResult.passed(null);
+        assertEquals("Expected status to be set", TestStatus.PASS, testResult.getResultStatus());
+        assertEquals("Expected two results", 2, mResult.getResults().size());
+    }
+
+    public void testCountResults() throws Exception {
+        mResult.getOrCreateResult(METHOD_1).failed(STACK_TRACE);
+        mResult.getOrCreateResult(METHOD_2).failed(STACK_TRACE);
+        mResult.getOrCreateResult(METHOD_3).passed(null);
+        assertEquals("Expected two failures", 2, mResult.countResults(TestStatus.FAIL));
+        assertEquals("Expected one pass", 1, mResult.countResults(TestStatus.PASS));
+    }
+}
\ No newline at end of file
diff --git a/common/util/tests/src/com/android/compatibility/common/util/DynamicConfigTest.java b/common/util/tests/src/com/android/compatibility/common/util/DynamicConfigTest.java
new file mode 100644
index 0000000..60369ec
--- /dev/null
+++ b/common/util/tests/src/com/android/compatibility/common/util/DynamicConfigTest.java
@@ -0,0 +1,105 @@
+/*
+ * 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.util;
+
+import junit.framework.TestCase;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+/**
+ * Unit tests for {@link DynamicConfig}
+ */
+public class DynamicConfigTest extends TestCase {
+    private static final String correctConfig =
+            "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
+            "<DynamicConfig>\n" +
+            "    <Config key=\"test-config-1\">test config 1</Config>\n" +
+            "    <Config key=\"test-config-2\">testconfig2</Config>\n" +
+            "    <ConfigList key=\"config-list\">\n" +
+            "        <Item>config0</Item>\n" +
+            "        <Item>config1</Item>\n" +
+            "        <Item>config2</Item>\n" +
+            "        <Item>config3</Item>\n" +
+            "        <Item>config4</Item>\n" +
+            "    </ConfigList>\n" +
+            "    <ConfigList key=\"config-list-2\">\n" +
+            "        <Item>A</Item>\n" +
+            "        <Item>B</Item>\n" +
+            "        <Item>C</Item>\n" +
+            "        <Item>D</Item>\n" +
+            "        <Item>E</Item>\n" +
+            "    </ConfigList>\n" +
+            "</DynamicConfig>\n";
+
+    private static final String configWrongNodeName =
+            "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
+            "<DynamicCsonfig>\n" +  //The node name DynamicConfig is intentionally mistyped
+            "    <Config key=\"test-config-1\">test config 1</Config>\n" +
+            "    <Config key=\"test-config-2\">testconfig2</Config>\n" +
+            "    <ConfigList key=\"config-list\">\n" +
+            "        <Item>Nevermore</Item>\n" +
+            "        <Item>Puck</Item>\n" +
+            "        <Item>Zeus</Item>\n" +
+            "        <Item>Earth Shaker</Item>\n" +
+            "        <Item>Vengeful Spirit</Item>\n" +
+            "    </ConfigList>\n" +
+            "    <ConfigList key=\"config-list-2\">\n" +
+            "        <Item>A</Item>\n" +
+            "        <Item>B</Item>\n" +
+            "        <Item>C</Item>\n" +
+            "        <Item>D</Item>\n" +
+            "        <Item>E</Item>\n" +
+            "    </ConfigList>\n" +
+            "</DynamicConfig>\n";
+
+    public void testCorrectConfig() throws Exception {
+        DynamicConfig config = new DynamicConfig();
+        File file = createFileFromStr(correctConfig);
+        config.initConfigFromXml(file);
+
+        assertEquals("Wrong Config", config.getConfig("test-config-1"), "test config 1");
+        assertEquals("Wrong Config", config.getConfig("test-config-2"), "testconfig2");
+        assertEquals("Wrong Config List", config.getConfigList("config-list").get(0), "config0");
+        assertEquals("Wrong Config List", config.getConfigList("config-list").get(2), "config2");
+        assertEquals("Wrong Config List", config.getConfigList("config-list-2").get(2), "C");
+    }
+
+    public void testConfigWithWrongNodeName() throws Exception {
+        DynamicConfig config = new DynamicConfig();
+        File file = createFileFromStr(configWrongNodeName);
+
+        try {
+            config.initConfigFromXml(file);
+            fail("Cannot detect error when config file has wrong node name");
+        } catch (XmlPullParserException e) {
+            //expected
+        }
+    }
+
+    private File createFileFromStr(String configStr) throws IOException {
+        File file = File.createTempFile("test", "dynamic");
+        FileOutputStream stream = new FileOutputStream(file);
+        stream.write(configStr.getBytes());
+        stream.flush();
+        return file;
+    }
+}
diff --git a/common/util/tests/src/com/android/compatibility/common/util/MetricsXmlSerializerTest.java b/common/util/tests/src/com/android/compatibility/common/util/MetricsXmlSerializerTest.java
index 05e69d8..c6f3ec1 100644
--- a/common/util/tests/src/com/android/compatibility/common/util/MetricsXmlSerializerTest.java
+++ b/common/util/tests/src/com/android/compatibility/common/util/MetricsXmlSerializerTest.java
@@ -24,6 +24,7 @@
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 
+//TODO(stuartscott): Delete file for v2, ReportLog can serialize itself.
 /**
  * Unit tests for {@link MetricsXmlSerializer}
  */
@@ -32,8 +33,7 @@
     static class LocalReportLog extends ReportLog {}
     private static final double[] VALUES = new double[] {1, 11, 21, 1211, 111221};
     private static final String HEADER = "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>";
-    private static final String EXPECTED_XML =
-            HEADER
+    private static final String EXPECTED_XML = HEADER
             + "<Summary message=\"Sample\" scoreType=\"higher_better\" unit=\"byte\">1.0</Summary>"
             + "<Details>"
                     + "<ValueArray source=\"com.android.compatibility.common.util."
@@ -89,4 +89,4 @@
 
         assertEquals(EXPECTED_XML, mByteArrayOutputStream.toString("utf-8"));
     }
-}
+}
\ No newline at end of file
diff --git a/common/util/tests/src/com/android/compatibility/common/util/ModuleResultTest.java b/common/util/tests/src/com/android/compatibility/common/util/ModuleResultTest.java
new file mode 100644
index 0000000..28b98fe
--- /dev/null
+++ b/common/util/tests/src/com/android/compatibility/common/util/ModuleResultTest.java
@@ -0,0 +1,75 @@
+/*
+ * 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.util;
+
+import junit.framework.TestCase;
+
+/**
+ * Unit tests for {@link ModuleResult}
+ */
+public class ModuleResultTest extends TestCase {
+
+    private static final String NAME = "ModuleName";
+    private static final String ABI = "mips64";
+    private static final String ID = AbiUtils.createId(ABI, NAME);
+    private static final String DEVICE = "device123";
+    private static final String CLASS = "android.test.FoorBar";
+    private static final String METHOD_1 = "testBlah1";
+    private static final String METHOD_2 = "testBlah2";
+    private static final String METHOD_3 = "testBlah3";
+    private static final String STACK_TRACE = "Something small is not alright\n " +
+            "at four.big.insects.Marley.sing(Marley.java:10)";
+    private ModuleResult mResult;
+
+    @Override
+    public void setUp() throws Exception {
+        mResult = new ModuleResult(ID);
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+        mResult = null;
+    }
+
+    public void testAccessors() throws Exception {
+        mResult.setDeviceSerial(DEVICE);
+        assertEquals("Incorrect device serial", DEVICE, mResult.getDeviceSerial());
+        assertEquals("Incorrect module ID", ID, mResult.getId());
+        assertEquals("Incorrect module ABI", ABI, mResult.getAbi());
+        assertEquals("Incorrect module name", NAME, mResult.getName());
+    }
+
+    public void testResultCreation() throws Exception {
+        ICaseResult caseResult = mResult.getOrCreateResult(CLASS);
+        // Should create one
+        assertEquals("Expected one result", 1, mResult.getResults().size());
+        assertTrue("Expected test result", mResult.getResults().contains(caseResult));
+        // Should not create another one
+        ICaseResult caseResult2 = mResult.getOrCreateResult(CLASS);
+        assertEquals("Expected the same result", caseResult, caseResult2);
+        assertEquals("Expected one result", 1, mResult.getResults().size());
+    }
+
+    public void testCountResults() throws Exception {
+        ICaseResult testCase = mResult.getOrCreateResult(CLASS);
+        testCase.getOrCreateResult(METHOD_1).failed(STACK_TRACE);
+        testCase.getOrCreateResult(METHOD_2).failed(STACK_TRACE);
+        testCase.getOrCreateResult(METHOD_3).passed(null);
+        assertEquals("Expected two failures", 2, mResult.countResults(TestStatus.FAIL));
+        assertEquals("Expected one pass", 1, mResult.countResults(TestStatus.PASS));
+    }
+}
\ No newline at end of file
diff --git a/common/util/tests/src/com/android/compatibility/common/util/MultipartFormTest.java b/common/util/tests/src/com/android/compatibility/common/util/MultipartFormTest.java
new file mode 100644
index 0000000..dd96308
--- /dev/null
+++ b/common/util/tests/src/com/android/compatibility/common/util/MultipartFormTest.java
@@ -0,0 +1,58 @@
+/*
+ * 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.util;
+
+import junit.framework.TestCase;
+
+import java.util.Arrays;
+
+/**
+ * Unit tests for {@link MultipartForm}
+ */
+public class MultipartFormTest extends TestCase {
+
+    private static final String SERVER_URL = "http://127.0.0.1:5555";
+    private static final byte[] ZIP_ARRAY = {
+            0xa, 0x2d, 0x2d, 0x43, 0x37, 0x35, 0x49, 0x35, 0x35, 0x75, 0x33, 0x52, 0x33, 0x70, 0x30,
+            0x72, 0x37, 0x33, 0x72, 0xa, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, 0x44, 0x69,
+            0x73, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x66, 0x6f, 0x72,
+            0x6d, 0x2d, 0x64, 0x61, 0x74, 0x61, 0x3b, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x3d, 0x22,
+            0x66, 0x6f, 0x6f, 0x22, 0xa, 0xa, 0x62, 0x61, 0x72, 0xa, 0x2d, 0x2d, 0x43, 0x37, 0x35,
+            0x49, 0x35, 0x35, 0x75, 0x33, 0x52, 0x33, 0x70, 0x30, 0x72, 0x37, 0x33, 0x72, 0xa, 0x43,
+            0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, 0x44, 0x69, 0x73, 0x70, 0x6f, 0x73, 0x69,
+            0x74, 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x66, 0x6f, 0x72, 0x6d, 0x2d, 0x64, 0x61, 0x74,
+            0x61, 0x3b, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x3d, 0x22, 0x62, 0x6c, 0x61, 0x68, 0x22,
+            0x3b, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x6e, 0x61, 0x6d, 0x65, 0x3d, 0x22, 0x62, 0x6c,
+            0x61, 0x68, 0x2e, 0x78, 0x6d, 0x6c, 0x2e, 0x67, 0x7a, 0x22, 0xa, 0x43, 0x6f, 0x6e, 0x74,
+            0x65, 0x6e, 0x74, 0x2d, 0x54, 0x79, 0x70, 0x65, 0x3a, 0x20, 0x61, 0x70, 0x70, 0x6c,
+            0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x78, 0x2d, 0x67, 0x7a, 0x69, 0x70, 0xa,
+            0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66,
+            0x65, 0x72, 0x2d, 0x45, 0x6e, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x3a, 0x20, 0x62,
+            0x69, 0x6e, 0x61, 0x72, 0x79, 0xa, 0xa, 0x62, 0x6c, 0x61, 0x68, 0xa, 0x2d, 0x2d, 0x43,
+            0x37, 0x35, 0x49, 0x35, 0x35, 0x75, 0x33, 0x52, 0x33, 0x70, 0x30, 0x72, 0x37, 0x33,
+            0x72, 0x2d, 0x2d, 0xa,
+    };
+
+    public void testContentBody() throws Exception {
+        MultipartForm form = new MultipartForm(SERVER_URL);
+        form.addFormValue("foo", "bar");
+        form.addFormFile("blah", "blah.xml.gz", "blah".getBytes());
+        byte[] data = form.getContentBody();
+        assertTrue("No data", data.length > 0);
+        assertTrue("Wrong data", Arrays.equals(ZIP_ARRAY, data));
+    }
+}
diff --git a/common/util/tests/src/com/android/compatibility/common/util/ReportLogTest.java b/common/util/tests/src/com/android/compatibility/common/util/ReportLogTest.java
index a5f3306..b41525b 100644
--- a/common/util/tests/src/com/android/compatibility/common/util/ReportLogTest.java
+++ b/common/util/tests/src/com/android/compatibility/common/util/ReportLogTest.java
@@ -18,51 +18,50 @@
 
 import junit.framework.TestCase;
 
-import java.util.Arrays;
-
 /**
  * Unit tests for {@link ReportLog}
  */
 public class ReportLogTest extends TestCase {
 
-    private static final double[] VALUES = new double[] {1, 11, 21, 1211, 111221};
+    private static final double[] VALUES = new double[] {.1, 124, 4736, 835.683, 98, 395};
+    private static final String HEADER = "<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>";
+    private static final String EXPECTED_XML =
+            HEADER + "\r\n" +
+            "<Summary>\r\n" +
+            "  <Metric source=\"com.android.compatibility.common.util.ReportLogTest#testSerialize:62\" message=\"Sample\" score-type=\"higher_better\" score-unit=\"byte\">\r\n" +
+            "    <Value>1.0</Value>\r\n" +
+            "  </Metric>\r\n" +
+            "</Summary>\r\n" +
+            "<Detail>\r\n" +
+            "  <Metric source=\"com.android.compatibility.common.util.ReportLogTest#testSerialize:63\" message=\"Details\" score-type=\"neutral\" score-unit=\"fps\">\r\n" +
+            "    <Value>0.1</Value>\r\n" +
+            "    <Value>124.0</Value>\r\n" +
+            "    <Value>4736.0</Value>\r\n" +
+            "    <Value>835.683</Value>\r\n" +
+            "    <Value>98.0</Value>\r\n" +
+            "    <Value>395.0</Value>\r\n" +
+            "  </Metric>\r\n" +
+            "</Detail>";
 
-    private static final String EXPECTED_ENCODED_REPORT_LOG =
-            "com.android.compatibility.common.util.ReportLogTest#testEncodeDecode:44|" +
-            "Sample Summary| |HIGHER_BETTER|BYTE|1.0 ++++" +
-            "com.android.compatibility.common.util.ReportLogTest#testEncodeDecode:45|" +
-            "Details| |NEUTRAL|FPS|1.0 11.0 21.0 1211.0 111221.0 ";
-    private ReportLog reportLog;
+    private ReportLog mReportLog;
 
     @Override
     protected void setUp() throws Exception {
-        this.reportLog = new ReportLog();
+        mReportLog = new ReportLog();
     }
 
-    public void testEncodeDecode() {
-
-        reportLog.setSummary("Sample Summary", 1.0, ResultType.HIGHER_BETTER, ResultUnit.BYTE);
-        reportLog.addValues("Details", VALUES, ResultType.NEUTRAL, ResultUnit.FPS);
-
-        String encodedReportLog = reportLog.toEncodedString();
-        assertEquals(EXPECTED_ENCODED_REPORT_LOG, encodedReportLog);
-
-        ReportLog decodedReportLog = ReportLog.fromEncodedString(encodedReportLog);
-        ReportLog.Result summary = reportLog.getSummary();
-        assertEquals("Sample Summary", summary.getMessage());
-        assertFalse(summary.getLocation().isEmpty());
-        assertEquals(ResultType.HIGHER_BETTER, summary.getType());
-        assertEquals(ResultUnit.BYTE, summary.getUnit());
-        assertTrue(Arrays.equals(new double[] {1.0}, summary.getValues()));
-
-        assertEquals(1, decodedReportLog.getDetailedMetrics().size());
-        ReportLog.Result detail = decodedReportLog.getDetailedMetrics().get(0);
-        assertEquals("Details", detail.getMessage());
-        assertFalse(detail.getLocation().isEmpty());
-        assertEquals(ResultType.NEUTRAL, detail.getType());
-        assertEquals(ResultUnit.FPS, detail.getUnit());
-        assertTrue(Arrays.equals(VALUES, detail.getValues()));
-
-        assertEquals(encodedReportLog, decodedReportLog.toEncodedString());
+    public void testSerialize_null() throws Exception {
+        assertEquals(HEADER, ReportLog.serialize(null));
     }
+
+    public void testSerialize_noData() throws Exception {
+        assertEquals(HEADER, ReportLog.serialize(mReportLog));
+    }
+
+    public void testSerialize() throws Exception {
+        mReportLog.setSummary("Sample", 1.0, ResultType.HIGHER_BETTER, ResultUnit.BYTE);
+        mReportLog.addValues("Details", VALUES, ResultType.NEUTRAL, ResultUnit.FPS);
+        assertEquals(EXPECTED_XML, ReportLog.serialize(mReportLog));
+    }
+
 }
diff --git a/common/util/tests/src/com/android/compatibility/common/util/StatTest.java b/common/util/tests/src/com/android/compatibility/common/util/StatTest.java
new file mode 100644
index 0000000..6e53d48
--- /dev/null
+++ b/common/util/tests/src/com/android/compatibility/common/util/StatTest.java
@@ -0,0 +1,85 @@
+/*
+ * 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.
+ */
+
+package com.android.compatibility.common.util;
+
+import com.android.compatibility.common.util.Stat;
+import junit.framework.TestCase;
+
+/**
+ * Unit tests for the {@link Stat} class.
+ */
+public class StatTest extends TestCase {
+
+    /**
+     * Test {@link Stat#get95PercentileValue(double[])}.
+     */
+    public void testGet95PercentileValue() {
+        double[] values = new double[100];
+        for (int i = 0; i < 100; i++) {
+            values[i] = i;
+        }
+        assertEquals(95, (int) Stat.get95PercentileValue(values));
+
+        values = new double[1000];
+        for (int i = 0; i < 1000; i++) {
+            values[i] = i;
+        }
+        assertEquals(950, (int) Stat.get95PercentileValue(values));
+
+        values = new double[100];
+        for (int i = 0; i < 100; i++) {
+            values[i] = i * i;
+        }
+        assertEquals(95 * 95, (int) Stat.get95PercentileValue(values));
+    }
+
+    /**
+     * Test {@link Stat#getAverage(double[])}.
+     */
+    public void testGetAverage() {
+        double[] values = new double[]{0, 1, 2, 3, 4};
+        double average = Stat.getAverage(values);
+        assertEquals(2.0, average, 0.00001);
+
+        values = new double[]{1, 2, 3, 4, 5};
+        average = Stat.getAverage(values);
+        assertEquals(3.0, average, 0.00001);
+
+        values = new double[]{0, 1, 4, 9, 16};
+        average = Stat.getAverage(values);
+        assertEquals(6.0, average, 0.00001);
+    }
+
+    /**
+     * Test standard deviation.
+     */
+    public void testGetStandardDeviation() {
+        double[] values = new double[]{0, 1, 2, 3, 4};
+        double stddev = Stat.getStat(values).mStddev;
+        assertEquals(Math.sqrt(2.5), stddev, 0.00001);
+
+        values = new double[]{1, 2, 3, 4, 5};
+        stddev = Stat.getStat(values).mStddev;
+        assertEquals(Math.sqrt(2.5), stddev, 0.00001);
+
+        values = new double[]{0, 2, 4, 6, 8};
+        stddev = Stat.getStat(values).mStddev;
+        assertEquals(Math.sqrt(10.0), stddev, 0.00001);
+    }
+
+
+}
diff --git a/common/util/tests/src/com/android/compatibility/common/util/TestFilterTest.java b/common/util/tests/src/com/android/compatibility/common/util/TestFilterTest.java
new file mode 100644
index 0000000..bc1091bb
--- /dev/null
+++ b/common/util/tests/src/com/android/compatibility/common/util/TestFilterTest.java
@@ -0,0 +1,82 @@
+/*
+ * 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.util;
+
+import junit.framework.TestCase;
+
+/**
+ * Unit tests for {@link TestFilter}
+ */
+public class TestFilterTest extends TestCase {
+
+    private static final String NAME = "ModuleName";
+    private static final String ABI = "mips64";
+    private static final String TEST = "com.android.foobar.Blah#testAllTheThings";
+    private static final String NAME_FILTER = String.format("%s", NAME);
+    private static final String ABI_NAME_FILTER = String.format("%s %s", ABI, NAME);
+    private static final String NAME_TEST_FILTER = String.format("%s %s", NAME, TEST);
+    private static final String FULL_FILTER = String.format("%s %s %s", ABI, NAME, TEST);
+
+    public void testParseNameFilter() {
+        TestFilter filter = TestFilter.createFrom(NAME_FILTER);
+        assertNull("Incorrect abi", filter.getAbi());
+        assertEquals("Incorrect name", NAME, filter.getName());
+        assertNull("Incorrect test", filter.getTest());
+    }
+
+    public void testParseAbiNameFilter() {
+        TestFilter filter = TestFilter.createFrom(ABI_NAME_FILTER);
+        assertEquals("Incorrect abi", ABI, filter.getAbi());
+        assertEquals("Incorrect name", NAME, filter.getName());
+        assertNull("Incorrect test", filter.getTest());
+    }
+
+    public void testParseNameTestFilter() {
+        TestFilter filter = TestFilter.createFrom(NAME_TEST_FILTER);
+        assertNull("Incorrect abi", filter.getAbi());
+        assertEquals("Incorrect name", NAME, filter.getName());
+        assertEquals("Incorrect test", TEST, filter.getTest());
+    }
+
+    public void testParseFullFilter() {
+        TestFilter filter = TestFilter.createFrom(FULL_FILTER);
+        assertEquals("Incorrect abi", ABI, filter.getAbi());
+        assertEquals("Incorrect name", NAME, filter.getName());
+        assertEquals("Incorrect test", TEST, filter.getTest());
+    }
+
+    public void testCreateNameFilter() {
+        TestFilter filter = new TestFilter(null, NAME, null);
+        assertEquals("Incorrect filter", NAME_FILTER, filter.toString());
+    }
+
+    public void testCreateAbiNameFilter() {
+        TestFilter filter = new TestFilter(ABI, NAME, null);
+        assertEquals("Incorrect filter", ABI_NAME_FILTER, filter.toString());
+    }
+
+    public void testCreateNameTestFilter() {
+        TestFilter filter = new TestFilter(null, NAME, TEST);
+        assertEquals("Incorrect filter", NAME_TEST_FILTER, filter.toString());
+    }
+
+    public void testCreateFullFilter() {
+        TestFilter filter = new TestFilter(ABI, NAME, TEST);
+        assertEquals("Incorrect filter", FULL_FILTER, filter.toString());
+    }
+
+}
diff --git a/common/util/tests/src/com/android/compatibility/common/util/TestResultTest.java b/common/util/tests/src/com/android/compatibility/common/util/TestResultTest.java
new file mode 100644
index 0000000..5e4431e
--- /dev/null
+++ b/common/util/tests/src/com/android/compatibility/common/util/TestResultTest.java
@@ -0,0 +1,48 @@
+/*
+ * 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.util;
+
+import junit.framework.TestCase;
+
+/**
+ * Unit tests for {@link TestResult}
+ */
+public class TestResultTest extends TestCase {
+
+    private static final String CLASS = "android.test.FoorBar";
+    private static final String METHOD_1 = "testBlah1";
+    private static final String TEST_1 = String.format("%s#%s", CLASS, METHOD_1);
+    private CaseResult mCase;
+    private TestResult mResult;
+
+    @Override
+    public void setUp() throws Exception {
+        mCase = new CaseResult(CLASS);
+        mResult = new TestResult(mCase, METHOD_1);
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+        mResult = null;
+    }
+
+    public void testAccessors() throws Exception {
+        assertEquals("Incorrect test name", METHOD_1, mResult.getName());
+        assertEquals("Incorrect full name", TEST_1, mResult.getFullName());
+    }
+
+}
\ No newline at end of file
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 348c680..1d367fe 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
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2014 The Android Open Source Project
+ * 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.
@@ -11,23 +11,36 @@
  * 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
+ * limitations under the License.
  */
-
 package com.android.compatibility.common.util;
 
+import junit.framework.Test;
 import junit.framework.TestSuite;
 
 /**
- * A {@link TestSuite} for the common.util package.
+ * A test suite for all util unit tests.
+ * <p/>
+ * All tests listed here should be self-contained, and do not require any external dependencies.
  */
 public class UnitTests extends TestSuite {
 
     public UnitTests() {
         super();
-
-        addTestSuite(MetricsStoreTest.class);
+        addTestSuite(AbiUtilsTest.class);
+        addTestSuite(CaseResultTest.class);
+        addTestSuite(DynamicConfigTest.class);
         addTestSuite(MetricsXmlSerializerTest.class);
+        addTestSuite(ModuleResultTest.class);
+        addTestSuite(MultipartFormTest.class);
         addTestSuite(ReportLogTest.class);
+        addTestSuite(StatTest.class);
+        addTestSuite(TestFilterTest.class);
+        addTestSuite(TestResultTest.class);
+        addTestSuite(XmlResultHandlerTest.class);
     }
-}
+
+    public static Test suite() {
+        return new UnitTests();
+    }
+}
\ No newline at end of file
diff --git a/common/util/tests/src/com/android/compatibility/common/util/XmlResultHandlerTest.java b/common/util/tests/src/com/android/compatibility/common/util/XmlResultHandlerTest.java
new file mode 100644
index 0000000..98a85cc
--- /dev/null
+++ b/common/util/tests/src/com/android/compatibility/common/util/XmlResultHandlerTest.java
@@ -0,0 +1,336 @@
+/*
+ * 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.util;
+
+import com.android.tradefed.util.FileUtil;
+
+import junit.framework.TestCase;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Unit tests for {@link XmlResultHandler}
+ */
+public class XmlResultHandlerTest extends TestCase {
+
+    private static final String SUITE_NAME = "CTS";
+    private static final String SUITE_VERSION = "5.0";
+    private static final String SUITE_PLAN = "cts";
+    private static final String REPORT_VERSION = "5.0";
+    private static final String OS_NAME = System.getProperty("os.name");
+    private static final String OS_VERSION = System.getProperty("os.version");
+    private static final String OS_ARCH = System.getProperty("os.arch");
+    private static final String JAVA_VENDOR = System.getProperty("java.vendor");
+    private static final String JAVA_VERSION = System.getProperty("java.version");
+    private static final String NAME_A = "ModuleA";
+    private static final String NAME_B = "ModuleB";
+    private static final String ABI = "mips64";
+    private static final String ID_A = AbiUtils.createId(ABI, NAME_A);
+    private static final String ID_B = AbiUtils.createId(ABI, NAME_B);
+    private static final String DEVICE_A = "device123";
+    private static final String DEVICE_B = "device456";
+    private static final String CLASS_A = "android.test.Foor";
+    private static final String CLASS_B = "android.test.Bar";
+    private static final String METHOD_1 = "testBlah1";
+    private static final String METHOD_2 = "testBlah2";
+    private static final String METHOD_3 = "testBlah3";
+    private static final String METHOD_4 = "testBlah4";
+    private static final String SUMMARY_SOURCE = String.format("%s#%s:20", CLASS_B, METHOD_4);
+    private static final String DETAILS_SOURCE = String.format("%s#%s:18", CLASS_B, METHOD_4);
+    private static final String SUMMARY_MESSAGE = "Headline";
+    private static final double SUMMARY_VALUE = 9001;
+    private static final String DETAILS_MESSAGE = "Deats";
+    private static final double DETAILS_VALUE_1 = 14;
+    private static final double DETAILS_VALUE_2 = 18;
+    private static final double DETAILS_VALUE_3 = 17;
+    private static final String MESSAGE = "Something small is not alright";
+    private static final String STACK_TRACE = "Something small is not alright\n " +
+            "at four.big.insects.Marley.sing(Marley.java:10)";
+    private static final String START = "2015-05-14 00:00:01";
+    private static final long START_MS = 1431586801000L;
+    private static final String END = "2015-05-14 23:59:59";
+    private static final long END_MS = 1431673199000L;
+    private static final String JOIN = "%s%s";
+    private static final String XML_BASE =
+            "<?xml version='1.0' encoding='UTF-8' standalone='no' ?>" +
+            "<?xml-stylesheet type=\"text/xsl\" href=\"compatibility-result.xsl\"?>\n" +
+            "<Result start=\"%s\" end=\"%s\" suite-name=\"%s\" suite-version=\"%s\" " +
+            "suite-plan=\"%s\" report-version=\"%s\" host-name=\"%s\" os-name=\"%s\" " +
+            "os-version=\"%s\" os-arch=\"%s\" java-vendor=\"%s\" java-version=\"%s\">\n" +
+            "%s%s%s" +
+            "</Result>";
+    private static final String XML_DEVICE_INFO =
+            "  <DeviceInfo build_serial=\"%s\" />\n";
+    private static final String XML_SUMMARY =
+            "  <Summary pass=\"%d\" failed=\"%d\" not-executed=\"%d\" />\n";
+    private static final String XML_MODULE =
+            "  <Module name=\"%s\" abi=\"%s\" device=\"%s\">\n" +
+            "%s" +
+            "  </Module>\n";
+    private static final String XML_CASE =
+            "    <TestCase name=\"%s\">\n" +
+            "%s" +
+            "    </TestCase>\n";
+    private static final String XML_TEST_PASS =
+            "      <Test result=\"pass\" name=\"%s\" start=\"%s\" end=\"%s\" />\n";
+    private static final String XML_TEST_NOT_EXECUTED =
+            "      <Test result=\"not-executed\" name=\"%s\" start=\"%s\" end=\"%s\" />\n";
+    private static final String XML_TEST_FAIL =
+            "      <Test result=\"fail\" name=\"%s\" start=\"%s\" end=\"%s\" >\n" +
+            "        <Failure message=\"%s\">\n" +
+            "          <StackTrace>%s</StackTrace>\n" +
+            "        </Failure>\n" +
+            "      </Test>\n";
+    private static final String XML_TEST_RESULT =
+            "      <Test result=\"pass\" name=\"%s\" start=\"%s\" end=\"%s\">\n" +
+            "        <Summary>\n" +
+            "          <Metric source=\"%s\" message=\"%s\" score-type=\"%s\" score-unit=\"%s\">\n" +
+            "             <Value>%s</Value>\n" +
+            "          </Metric>\n" +
+            "        </Summary>\n" +
+            "        <Detail>\n" +
+            "          <Metric source=\"%s\" message=\"%s\" score-type=\"%s\" score-unit=\"%s\">\n" +
+            "            <Value>%s</Value>\n" +
+            "            <Value>%s</Value>\n" +
+            "            <Value>%s</Value>\n" +
+            "          </Metric>\n" +
+            "        </Detail>\n" +
+            "      </Test>\n";
+    private File resultsDir = null;
+    private File resultDir = null;
+
+    @Override
+    public void setUp() throws Exception {
+        resultsDir = FileUtil.createTempDir("results");
+        resultDir = FileUtil.createTempDir("12345", resultsDir);
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+        if (resultsDir != null) {
+            FileUtil.recursiveDelete(resultsDir);
+        }
+    }
+
+    public void testSerialization() throws Exception {
+        IInvocationResult result = new InvocationResult(resultDir);
+        result.setStartTime(START_MS);
+        result.setTestPlan(SUITE_PLAN);
+        IModuleResult moduleA = result.getOrCreateModule(ID_A);
+        moduleA.setDeviceSerial(DEVICE_A);
+        ICaseResult moduleACase = moduleA.getOrCreateResult(CLASS_A);
+        ITestResult moduleATest1 = moduleACase.getOrCreateResult(METHOD_1);
+        moduleATest1.setStartTime(START_MS);
+        moduleATest1.setResultStatus(TestStatus.PASS);
+        moduleATest1.setEndTime(END_MS);
+        ITestResult moduleATest2 = moduleACase.getOrCreateResult(METHOD_2);
+        moduleATest2.setStartTime(START_MS);
+        moduleATest2.setResultStatus(TestStatus.NOT_EXECUTED);
+        moduleATest2.setEndTime(END_MS);
+
+        IModuleResult moduleB = result.getOrCreateModule(ID_B);
+        moduleB.setDeviceSerial(DEVICE_B);
+        ICaseResult moduleBCase = moduleB.getOrCreateResult(CLASS_B);
+        ITestResult moduleBTest3 = moduleBCase.getOrCreateResult(METHOD_3);
+        moduleBTest3.setStartTime(START_MS);
+        moduleBTest3.setResultStatus(TestStatus.FAIL);
+        moduleBTest3.setMessage(MESSAGE);
+        moduleBTest3.setStackTrace(STACK_TRACE);
+        moduleBTest3.setEndTime(END_MS);
+        ITestResult moduleBTest4 = moduleBCase.getOrCreateResult(METHOD_4);
+        moduleBTest4.setStartTime(START_MS);
+        moduleBTest4.setResultStatus(TestStatus.PASS);
+        ReportLog report = new ReportLog();
+        ReportLog.Metric summary = new ReportLog.Metric(SUMMARY_SOURCE, SUMMARY_MESSAGE,
+                SUMMARY_VALUE, ResultType.HIGHER_BETTER, ResultUnit.SCORE);
+        report.setSummary(summary);
+        ReportLog.Metric details = new ReportLog.Metric(DETAILS_SOURCE, DETAILS_MESSAGE,
+                new double[] {DETAILS_VALUE_1, DETAILS_VALUE_2, DETAILS_VALUE_3},
+                ResultType.LOWER_BETTER, ResultUnit.MS);
+        report.addMetric(details);
+        moduleBTest4.setReportLog(report);
+        moduleBTest4.setEndTime(END_MS);
+
+        // Serialize to file
+        XmlResultHandler.writeResults(SUITE_NAME, SUITE_VERSION, SUITE_PLAN, result, resultDir,
+                START_MS, END_MS);
+
+        // Parse the results and assert correctness
+        checkResult(XmlResultHandler.getResults(resultsDir), resultDir);
+    }
+
+    public void testParsing() throws Exception {
+        File resultsDir = null;
+        FileWriter writer = null;
+        try {
+            resultsDir = FileUtil.createTempDir("results");
+            File resultDir = FileUtil.createTempDir("12345", resultsDir);
+            // Create the result file
+            File resultFile = new File(resultDir, XmlResultHandler.TEST_RESULT_FILE_NAME);
+            writer = new FileWriter(resultFile);
+            String deviceInfo = String.format(XML_DEVICE_INFO, DEVICE_A);
+            String summary = String.format(XML_SUMMARY, 2, 1, 1);
+            String moduleATest1 = String.format(XML_TEST_PASS, METHOD_1, START, END);
+            String moduleATest2 = String.format(XML_TEST_NOT_EXECUTED, METHOD_2, START, END);
+            String moduleATests = String.format(JOIN, moduleATest1, moduleATest2);
+            String moduleACases = String.format(XML_CASE, CLASS_A, moduleATests);
+            String moduleA = String.format(XML_MODULE, NAME_A, ABI, DEVICE_A, moduleACases);
+            String moduleBTest3 = String.format(XML_TEST_FAIL, METHOD_3, START, END, MESSAGE, STACK_TRACE);
+            String moduleBTest4 = String.format(XML_TEST_RESULT, METHOD_4, START, END,
+                    SUMMARY_SOURCE, SUMMARY_MESSAGE, ResultType.HIGHER_BETTER.toReportString(),
+                    ResultUnit.SCORE.toReportString(), Double.toString(SUMMARY_VALUE),
+                    DETAILS_SOURCE, DETAILS_MESSAGE, ResultType.LOWER_BETTER.toReportString(),
+                    ResultUnit.MS.toReportString(), Double.toString(DETAILS_VALUE_1),
+                    Double.toString(DETAILS_VALUE_2), Double.toString(DETAILS_VALUE_3));
+            String moduleBTests = String.format(JOIN, moduleBTest3, moduleBTest4);
+            String moduleBCases = String.format(XML_CASE, CLASS_B, moduleBTests);
+            String moduleB = String.format(XML_MODULE, NAME_B, ABI, DEVICE_B, moduleBCases);
+            String modules = String.format(JOIN, moduleA, moduleB);
+            String hostName = "";
+            try {
+                hostName = InetAddress.getLocalHost().getHostName();
+            } catch (UnknownHostException ignored) {}
+            String output = String.format(XML_BASE, START, END, SUITE_NAME, SUITE_VERSION,
+                    SUITE_PLAN, REPORT_VERSION, hostName, OS_NAME,  OS_VERSION, OS_ARCH,
+                    JAVA_VENDOR, JAVA_VERSION, deviceInfo, summary, modules);
+            writer.write(output);
+            writer.flush();
+
+            // Parse the results and assert correctness
+            checkResult(XmlResultHandler.getResults(resultsDir), resultDir);
+        } finally {
+            if (writer != null) {
+                writer.close();
+            }
+        }
+    }
+        
+    private void checkResult(List<IInvocationResult> results, File resultDir) throws Exception {
+        assertEquals("Expected 1 result", 1, results.size());
+        IInvocationResult result = results.get(0);
+        assertEquals("Expected 2 passes", 2, result.countResults(TestStatus.PASS));
+        assertEquals("Expected 1 failure", 1, result.countResults(TestStatus.FAIL));
+        assertEquals("Expected 1 not executed", 1, result.countResults(TestStatus.NOT_EXECUTED));
+        Set<String> serials = result.getDeviceSerials();
+        assertEquals("Expected 2 devices", 2, serials.size());
+        assertTrue("Incorrect devices", serials.contains(DEVICE_A) && serials.contains(DEVICE_B));
+        assertEquals("Incorrect result dir", resultDir.getAbsolutePath(),
+                result.getResultDir().getAbsolutePath());
+        assertEquals("Incorrect start time", START_MS, result.getStartTime());
+        assertEquals("Incorrect test plan", SUITE_PLAN, result.getTestPlan());
+
+        List<IModuleResult> modules = result.getModules();
+        assertEquals("Expected 2 modules", 2, modules.size());
+
+        IModuleResult moduleA = modules.get(0);
+        assertEquals("Expected 1 pass", 1, moduleA.countResults(TestStatus.PASS));
+        assertEquals("Expected 0 failures", 0, moduleA.countResults(TestStatus.FAIL));
+        assertEquals("Expected 1 not executed", 1, moduleA.countResults(TestStatus.NOT_EXECUTED));
+        assertEquals("Incorrect ABI", ABI, moduleA.getAbi());
+        assertEquals("Incorrect name", NAME_A, moduleA.getName());
+        assertEquals("Incorrect ID", ID_A, moduleA.getId());
+        assertEquals("Incorrect device", DEVICE_A, moduleA.getDeviceSerial());
+        List<ICaseResult> moduleACases = moduleA.getResults();
+        assertEquals("Expected 1 test case", 1, moduleACases.size());
+        ICaseResult moduleACase = moduleACases.get(0);
+        assertEquals("Incorrect name", CLASS_A, moduleACase.getName());
+        List<ITestResult> moduleAResults = moduleACase.getResults();
+        assertEquals("Expected 2 results", 2, moduleAResults.size());
+        ITestResult moduleATest1 = moduleAResults.get(0);
+        assertEquals("Incorrect name", METHOD_1, moduleATest1.getName());
+        assertEquals("Incorrect start time", START_MS, moduleATest1.getStartTime());
+        assertEquals("Incorrect end time", END_MS, moduleATest1.getEndTime());
+        assertEquals("Incorrect result", TestStatus.PASS, moduleATest1.getResultStatus());
+        assertNull("Unexpected bugreport", moduleATest1.getBugReport());
+        assertNull("Unexpected log", moduleATest1.getLog());
+        assertNull("Unexpected screenshot", moduleATest1.getScreenshot());
+        assertNull("Unexpected message", moduleATest1.getMessage());
+        assertNull("Unexpected stack trace", moduleATest1.getStackTrace());
+        assertNull("Unexpected report", moduleATest1.getReportLog());
+        ITestResult moduleATest2 = moduleAResults.get(1);
+        assertEquals("Incorrect name", METHOD_2, moduleATest2.getName());
+        assertEquals("Incorrect start time", START_MS, moduleATest2.getStartTime());
+        assertEquals("Incorrect end time", END_MS, moduleATest2.getEndTime());
+        assertEquals("Incorrect result", TestStatus.NOT_EXECUTED, moduleATest2.getResultStatus());
+        assertNull("Unexpected bugreport", moduleATest2.getBugReport());
+        assertNull("Unexpected log", moduleATest2.getLog());
+        assertNull("Unexpected screenshot", moduleATest2.getScreenshot());
+        assertNull("Unexpected message", moduleATest2.getMessage());
+        assertNull("Unexpected stack trace", moduleATest2.getStackTrace());
+        assertNull("Unexpected report", moduleATest2.getReportLog());
+
+        IModuleResult moduleB = modules.get(1);
+        assertEquals("Expected 1 pass", 1, moduleB.countResults(TestStatus.PASS));
+        assertEquals("Expected 1 failure", 1, moduleB.countResults(TestStatus.FAIL));
+        assertEquals("Expected 0 not executed", 0, moduleB.countResults(TestStatus.NOT_EXECUTED));
+        assertEquals("Incorrect ABI", ABI, moduleB.getAbi());
+        assertEquals("Incorrect name", NAME_B, moduleB.getName());
+        assertEquals("Incorrect ID", ID_B, moduleB.getId());
+        assertEquals("Incorrect device", DEVICE_B, moduleB.getDeviceSerial());
+        List<ICaseResult> moduleBCases = moduleB.getResults();
+        assertEquals("Expected 1 test case", 1, moduleBCases.size());
+        ICaseResult moduleBCase = moduleBCases.get(0);
+        assertEquals("Incorrect name", CLASS_B, moduleBCase.getName());
+        List<ITestResult> moduleBResults = moduleBCase.getResults();
+        assertEquals("Expected 2 results", 2, moduleBResults.size());
+        ITestResult moduleBTest3 = moduleBResults.get(0);
+        assertEquals("Incorrect name", METHOD_3, moduleBTest3.getName());
+        assertEquals("Incorrect start time", START_MS, moduleBTest3.getStartTime());
+        assertEquals("Incorrect end time", END_MS, moduleBTest3.getEndTime());
+        assertEquals("Incorrect result", TestStatus.FAIL, moduleBTest3.getResultStatus());
+        assertNull("Unexpected bugreport", moduleBTest3.getBugReport());
+        assertNull("Unexpected log", moduleBTest3.getLog());
+        assertNull("Unexpected screenshot", moduleBTest3.getScreenshot());
+        assertEquals("Incorrect message", MESSAGE, moduleBTest3.getMessage());
+        assertEquals("Incorrect stack trace", STACK_TRACE, moduleBTest3.getStackTrace());
+        assertNull("Unexpected report", moduleBTest3.getReportLog());
+        ITestResult moduleBTest4 = moduleBResults.get(1);
+        assertEquals("Incorrect name", METHOD_4, moduleBTest4.getName());
+        assertEquals("Incorrect start time", START_MS, moduleBTest4.getStartTime());
+        assertEquals("Incorrect end time", END_MS, moduleBTest4.getEndTime());
+        assertEquals("Incorrect result", TestStatus.PASS, moduleBTest4.getResultStatus());
+        assertNull("Unexpected bugreport", moduleBTest4.getBugReport());
+        assertNull("Unexpected log", moduleBTest4.getLog());
+        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()));
+        List<ReportLog.Metric> details = report.getDetailedMetrics();
+        assertEquals("Expected 1 report detail", 1, details.size());
+        ReportLog.Metric detail = details.get(0);
+        assertEquals("Incorrect source", DETAILS_SOURCE, detail.getSource());
+        assertEquals("Incorrect message", DETAILS_MESSAGE, detail.getMessage());
+        assertEquals("Incorrect type", ResultType.LOWER_BETTER, detail.getType());
+        assertEquals("Incorrect unit", ResultUnit.MS, detail.getUnit());
+        assertTrue("Incorrect values", Arrays.equals(new double[] { DETAILS_VALUE_1,
+                DETAILS_VALUE_2, DETAILS_VALUE_3 }, detail.getValues()));
+    }
+}
diff --git a/hostsidetests/appsecurity/src/com/android/cts/appsecurity/AppSecurityTests.java b/hostsidetests/appsecurity/src/com/android/cts/appsecurity/AppSecurityTests.java
index bf9e81c..d7caabe 100644
--- a/hostsidetests/appsecurity/src/com/android/cts/appsecurity/AppSecurityTests.java
+++ b/hostsidetests/appsecurity/src/com/android/cts/appsecurity/AppSecurityTests.java
@@ -16,8 +16,8 @@
 
 package com.android.cts.appsecurity;
 
+import com.android.compatibility.common.util.AbiUtils;
 import com.android.cts.tradefed.build.CtsBuildHelper;
-import com.android.cts.util.AbiUtils;
 import com.android.ddmlib.Log;
 import com.android.tradefed.build.IBuildInfo;
 import com.android.tradefed.device.DeviceNotAvailableException;
diff --git a/hostsidetests/appsecurity/src/com/android/cts/appsecurity/ExternalStorageHostTest.java b/hostsidetests/appsecurity/src/com/android/cts/appsecurity/ExternalStorageHostTest.java
index cb67c63..b475b3c 100644
--- a/hostsidetests/appsecurity/src/com/android/cts/appsecurity/ExternalStorageHostTest.java
+++ b/hostsidetests/appsecurity/src/com/android/cts/appsecurity/ExternalStorageHostTest.java
@@ -16,8 +16,8 @@
 
 package com.android.cts.appsecurity;
 
+import com.android.compatibility.common.util.AbiUtils;
 import com.android.cts.tradefed.build.CtsBuildHelper;
-import com.android.cts.util.AbiUtils;
 import com.android.ddmlib.Log;
 import com.android.tradefed.build.IBuildInfo;
 import com.android.tradefed.device.DeviceNotAvailableException;
diff --git a/hostsidetests/appsecurity/src/com/android/cts/appsecurity/SplitTests.java b/hostsidetests/appsecurity/src/com/android/cts/appsecurity/SplitTests.java
index ef3af8d..e73280c 100644
--- a/hostsidetests/appsecurity/src/com/android/cts/appsecurity/SplitTests.java
+++ b/hostsidetests/appsecurity/src/com/android/cts/appsecurity/SplitTests.java
@@ -16,8 +16,8 @@
 
 package com.android.cts.appsecurity;
 
+import com.android.compatibility.common.util.AbiUtils;
 import com.android.cts.tradefed.build.CtsBuildHelper;
-import com.android.cts.util.AbiUtils;
 import com.android.tradefed.build.IBuildInfo;
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.device.ITestDevice;
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/Android.mk b/hostsidetests/devicepolicy/app/DeviceOwner/Android.mk
index 314f996..b3532fb 100644
--- a/hostsidetests/devicepolicy/app/DeviceOwner/Android.mk
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/Android.mk
@@ -26,7 +26,7 @@
 
 LOCAL_JAVA_LIBRARIES := android.test.runner cts-junit
 
-LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner compatibility-device-util_v2
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner compatibility-device-util
 
 LOCAL_SDK_VERSION := current
 
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/Android.mk b/hostsidetests/devicepolicy/app/ManagedProfile/Android.mk
index b31e74b..9db6718 100644
--- a/hostsidetests/devicepolicy/app/ManagedProfile/Android.mk
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/Android.mk
@@ -26,7 +26,7 @@
 
 LOCAL_JAVA_LIBRARIES := android.test.runner cts-junit
 
-LOCAL_STATIC_JAVA_LIBRARIES = android-support-v4 ctstestrunner compatibility-device-util_v2 \
+LOCAL_STATIC_JAVA_LIBRARIES = android-support-v4 ctstestrunner compatibility-device-util \
 	ub-uiautomator
 
 LOCAL_SDK_VERSION := current
diff --git a/hostsidetests/devicepolicy/app/WifiConfigCreator/Android.mk b/hostsidetests/devicepolicy/app/WifiConfigCreator/Android.mk
index 47db44e..8cfc58d 100644
--- a/hostsidetests/devicepolicy/app/WifiConfigCreator/Android.mk
+++ b/hostsidetests/devicepolicy/app/WifiConfigCreator/Android.mk
@@ -26,7 +26,7 @@
 
 LOCAL_PACKAGE_NAME := CtsWifiConfigCreator
 
-LOCAL_STATIC_JAVA_LIBRARIES := compatibility-device-util_v2
+LOCAL_STATIC_JAVA_LIBRARIES := compatibility-device-util
 
 LOCAL_SDK_VERSION := current
 
diff --git a/hostsidetests/monkey/src/com/android/cts/monkey/AbstractMonkeyTest.java b/hostsidetests/monkey/src/com/android/cts/monkey/AbstractMonkeyTest.java
index b31a32d..c8c9863 100755
--- a/hostsidetests/monkey/src/com/android/cts/monkey/AbstractMonkeyTest.java
+++ b/hostsidetests/monkey/src/com/android/cts/monkey/AbstractMonkeyTest.java
@@ -1,7 +1,7 @@
 package com.android.cts.monkey;
 
+import com.android.compatibility.common.util.AbiUtils;
 import com.android.cts.tradefed.build.CtsBuildHelper;
-import com.android.cts.util.AbiUtils;
 import com.android.tradefed.build.IBuildInfo;
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.device.ITestDevice;
diff --git a/hostsidetests/sample/Android.mk b/hostsidetests/sample/Android.mk
index e8cbdda..8a76a8b 100644
--- a/hostsidetests/sample/Android.mk
+++ b/hostsidetests/sample/Android.mk
@@ -18,15 +18,15 @@
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
-LOCAL_MODULE_TAGS := optional
+LOCAL_MODULE_TAGS := tests
 
-# Must match the package name in CtsTestCaseList.mk
+# tag this module as a cts_v2 test artifact
+LOCAL_COMPATIBILITY_SUITE := cts_v2
+
 LOCAL_MODULE := CtsSampleHostTestCases
 
-LOCAL_JAVA_LIBRARIES := cts-tradefed tradefed-prebuilt
+LOCAL_JAVA_LIBRARIES := compatibility-host-util cts-tradefed_v2 tradefed-prebuilt
 
-LOCAL_CTS_TEST_PACKAGE := android.host.sample
-
-include $(BUILD_CTS_HOST_JAVA_LIBRARY)
+include $(BUILD_HOST_JAVA_LIBRARY)
 
 include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/hostsidetests/sample/AndroidTest.xml b/hostsidetests/sample/AndroidTest.xml
new file mode 100644
index 0000000..f436e84
--- /dev/null
+++ b/hostsidetests/sample/AndroidTest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<configuration description="Base config for CTS package preparer">
+    <include name="common-config" />
+    <option name="apk-installer:test-file-name" value="CtsSampleDeviceApp.apk" />
+    <test class="android.sample.cts.SampleHostTest" />
+    <test class="android.sample.cts.SampleHostResultTest" />
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.DynamicConfigPusher">
+        <option name="target" value="host" />
+        <option name="module-name" value="CtsSampleHostTestCases"/>
+        <option name="version-name" value="1.0"/>
+    </target_preparer>
+</configuration>
diff --git a/hostsidetests/sample/DynamicConfig.xml b/hostsidetests/sample/DynamicConfig.xml
new file mode 100644
index 0000000..18c07ef
--- /dev/null
+++ b/hostsidetests/sample/DynamicConfig.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<DynamicConfig>
+    <Config key ="local-config">local-config-val</Config>
+</DynamicConfig>
diff --git a/hostsidetests/sample/app/Android.mk b/hostsidetests/sample/app/Android.mk
index 4af45b9..773f24f 100644
--- a/hostsidetests/sample/app/Android.mk
+++ b/hostsidetests/sample/app/Android.mk
@@ -16,16 +16,22 @@
 
 include $(CLEAR_VARS)
 
-# Don't include this package in any target.
-LOCAL_MODULE_TAGS := optional
-
+# Don't include this package in any target
+LOCAL_MODULE_TAGS := tests
 # When built, explicitly put it in the data partition.
 LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
 
+LOCAL_DEX_PREOPT := false
+
+LOCAL_PROGUARD_ENABLED := disabled
+
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
+# tag this module as a cts_v2 test artifact
+LOCAL_COMPATIBILITY_SUITE := cts_v2
+
 LOCAL_PACKAGE_NAME := CtsSampleDeviceApp
 
 LOCAL_SDK_VERSION := current
 
-include $(BUILD_CTS_PACKAGE)
+include $(BUILD_PACKAGE)
diff --git a/hostsidetests/sample/app/AndroidManifest.xml b/hostsidetests/sample/app/AndroidManifest.xml
index c087435..dfacf25 100755
--- a/hostsidetests/sample/app/AndroidManifest.xml
+++ b/hostsidetests/sample/app/AndroidManifest.xml
@@ -18,7 +18,6 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="android.sample.app">
 
-    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
     <application>
         <activity android:name=".SampleDeviceActivity" >
             <intent-filter>
diff --git a/hostsidetests/sample/src/android/sample/cts/SampleHostResultTest.java b/hostsidetests/sample/src/android/sample/cts/SampleHostResultTest.java
index bed4c05..1c0b342 100644
--- a/hostsidetests/sample/src/android/sample/cts/SampleHostResultTest.java
+++ b/hostsidetests/sample/src/android/sample/cts/SampleHostResultTest.java
@@ -16,16 +16,15 @@
 
 package android.sample.cts;
 
-import com.android.cts.tradefed.build.CtsBuildHelper;
-import com.android.cts.tradefed.util.HostReportLog;
-import com.android.cts.util.MeasureRun;
-import com.android.cts.util.MeasureTime;
-import com.android.cts.util.ResultType;
-import com.android.cts.util.ResultUnit;
-import com.android.cts.util.ReportLog;
-import com.android.cts.util.Stat;
-import com.android.ddmlib.IDevice;
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.compatibility.common.util.MeasureRun;
+import com.android.compatibility.common.util.MeasureTime;
+import com.android.compatibility.common.util.MetricsReportLog;
+import com.android.compatibility.common.util.ResultType;
+import com.android.compatibility.common.util.ResultUnit;
+import com.android.compatibility.common.util.Stat;
 import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.build.IFolderBuildInfo;
 import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.testtype.DeviceTestCase;
 import com.android.tradefed.testtype.IAbi;
@@ -37,7 +36,6 @@
 import com.android.tradefed.util.RunUtil;
 
 import java.io.File;
-import java.lang.Exception;
 
 /**
  * Test to measure the transfer time of a file from the host to the device.
@@ -52,16 +50,21 @@
     private static final int REPEAT = 5;
 
     /**
-     * The name of the plan to transfer.
-     *
-     * In this case we will transfer the CTS.xml file.
+     * The device-side location to write the file to.
      */
-    private static final String PLAN_NAME = "CTS";
+    private static final String FILE_PATH = "/data/local/tmp/%s";
 
     /**
-     * A reference to the build.
+     * The name of the file to transfer.
+     *
+     * In this case we will transfer this test's module config.
      */
-    private CtsBuildHelper mBuild;
+    private static final String FILE_NAME = "CtsSampleHostTestCases.config";
+
+    /**
+     * A helper to access resources in the build.
+     */
+    private CompatibilityBuildHelper mBuildHelper;
 
     /**
      * A reference to the device under test.
@@ -81,7 +84,7 @@
     @Override
     public void setBuild(IBuildInfo buildInfo) {
         // Get the build, this is used to access the APK.
-        mBuild = CtsBuildHelper.createBuildHelper(buildInfo);
+        mBuildHelper = new CompatibilityBuildHelper((IFolderBuildInfo) buildInfo);
     }
 
     @Override
@@ -100,13 +103,10 @@
      */
     public void testTransferTime() throws Exception {
         final ITestDevice device = mDevice;
-        // Get the external storage location and ensure its not null.
-        final String externalStorePath = mDevice.getMountPoint(IDevice.MNT_EXTERNAL_STORAGE);
-        assertNotNull("External storage location no found", externalStorePath);
         // Create the device side path where the file will be transfered.
-        final String devicePath = String.format("%s/%s", externalStorePath, "tmp_testPushPull.txt");
-        // Get the file from the build.
-        final File testFile = mBuild.getTestPlanFile(PLAN_NAME);
+        final String devicePath = String.format(FILE_PATH, "tmp_testPushPull.txt");
+        // Get this test's module config file from the build.
+        final File testFile = new File(mBuildHelper.getTestsDir(), FILE_NAME);
         double[] result = MeasureTime.measure(REPEAT, new MeasureRun() {
             @Override
             public void prepare(int i) throws Exception {
@@ -133,15 +133,15 @@
         // Compute the stats.
         Stat.StatResult stat = Stat.getStat(result);
         // Get the report for this test and add the results to record.
-        HostReportLog report = new HostReportLog(mDevice.getSerialNumber(), mAbi.getName(),
-                ReportLog.getClassMethodNames());
-        report.printArray("Times", result, ResultType.LOWER_BETTER, ResultUnit.MS);
-        report.printValue("Min", stat.mMin, ResultType.LOWER_BETTER, ResultUnit.MS);
-        report.printValue("Max", stat.mMax, ResultType.LOWER_BETTER, ResultUnit.MS);
+        MetricsReportLog report = new MetricsReportLog(mDevice.getSerialNumber(), mAbi.getName(),
+                String.format("%s#testTransferTime", getClass().getCanonicalName()));
+        report.addValues("Times", result, ResultType.LOWER_BETTER, ResultUnit.MS);
+        report.addValue("Min", stat.mMin, ResultType.LOWER_BETTER, ResultUnit.MS);
+        report.addValue("Max", stat.mMax, ResultType.LOWER_BETTER, ResultUnit.MS);
         // Every report must have a summary,
-        report.printSummary("Average", stat.mAverage, ResultType.LOWER_BETTER, ResultUnit.MS);
+        report.setSummary("Average", stat.mAverage, ResultType.LOWER_BETTER, ResultUnit.MS);
         // Send the report to Tradefed.
-        report.deliverReportToHost();
+        report.submit();
     }
 
     /**
diff --git a/hostsidetests/sample/src/android/sample/cts/SampleHostTest.java b/hostsidetests/sample/src/android/sample/cts/SampleHostTest.java
index ab7e0b0..df86444 100644
--- a/hostsidetests/sample/src/android/sample/cts/SampleHostTest.java
+++ b/hostsidetests/sample/src/android/sample/cts/SampleHostTest.java
@@ -16,27 +16,20 @@
 
 package android.sample.cts;
 
-import com.android.cts.tradefed.build.CtsBuildHelper;
-import com.android.cts.util.AbiUtils;
-import com.android.tradefed.build.IBuildInfo;
+import com.android.compatibility.common.util.DynamicConfigHostSide;
 import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.testtype.DeviceTestCase;
-import com.android.tradefed.testtype.IAbi;
-import com.android.tradefed.testtype.IAbiReceiver;
-import com.android.tradefed.testtype.IBuildReceiver;
 
-import java.io.File;
-import java.lang.String;
 import java.util.Scanner;
 
 /**
  * Test to check the APK logs to Logcat.
  *
  * When this test builds, it also builds {@link android.sample.app.SampleDeviceActivity} into an APK
- * which it then installs at runtime and starts. The activity simply prints a message to Logcat and
- * then gets uninstalled.
+ * which it then installed at runtime and started. The activity simply prints a message to Logcat
+ * and then gets uninstalled.
  */
-public class SampleHostTest extends DeviceTestCase implements IAbiReceiver, IBuildReceiver {
+public class SampleHostTest extends DeviceTestCase {
 
     /**
      * The package name of the APK.
@@ -44,11 +37,6 @@
     private static final String PACKAGE = "android.sample.app";
 
     /**
-     * The file name of the APK.
-     */
-    private static final String APK = "CtsSampleDeviceApp.apk";
-
-    /**
      * The class name of the main activity in the APK.
      */
     private static final String CLASS = "SampleDeviceActivity";
@@ -65,51 +53,21 @@
     private static final String TEST_STRING = "SampleTestString";
 
     /**
-     * The ABI to use.
+     * Test if dynamic config on the host side works
+     * @throws Exception
      */
-    private IAbi mAbi;
+    public void testDynamicConfigLocal() throws Exception {
+        DynamicConfigHostSide config = new DynamicConfigHostSide("CtsSampleHostTestCases");
+        assertEquals("local-config-val", config.getConfig("local-config"));
+    }
 
     /**
-     * A reference to the build.
+     * Test if dynamic config override on the host side works
+     * @throws Exception
      */
-    private CtsBuildHelper mBuild;
-
-    /**
-     * A reference to the device under test.
-     */
-    private ITestDevice mDevice;
-
-    @Override
-    public void setAbi(IAbi abi) {
-        mAbi = abi;
-    }
-
-    @Override
-    public void setBuild(IBuildInfo buildInfo) {
-        // Get the build, this is used to access the APK.
-        mBuild = CtsBuildHelper.createBuildHelper(buildInfo);
-    }
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        // Get the device, this gives a handle to run commands and install APKs.
-        mDevice = getDevice();
-        // Remove any previously installed versions of this APK.
-        mDevice.uninstallPackage(PACKAGE);
-        // Get the APK from the build.
-        File app = mBuild.getTestApp(APK);
-        // Get the ABI flag.
-        String[] options = {AbiUtils.createAbiFlag(mAbi.getName())};
-        // Install the APK on the device.
-        mDevice.installPackage(app, false, options);
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        // Remove the package once complete.
-        mDevice.uninstallPackage(PACKAGE);
-        super.tearDown();
+    public void testDynamicConfigOverride() throws Exception {
+        DynamicConfigHostSide config = new DynamicConfigHostSide("CtsSampleHostTestCases");
+        assertEquals("host-1.0-cts-keyone", config.getConfig("sample_host_key_one"));
     }
 
     /**
@@ -118,12 +76,13 @@
      * @throws Exception
      */
     public void testLogcat() throws Exception {
+        ITestDevice device = getDevice();
         // Clear logcat.
-        mDevice.executeAdbCommand("logcat", "-c");
+        device.executeAdbCommand("logcat", "-c");
         // Start the APK and wait for it to complete.
-        mDevice.executeShellCommand(START_COMMAND);
+        device.executeShellCommand(START_COMMAND);
         // Dump logcat.
-        String logs = mDevice.executeAdbCommand("logcat", "-v", "brief", "-d", CLASS + ":I", "*:S");
+        String logs = device.executeAdbCommand("logcat", "-v", "brief", "-d", CLASS + ":I", "*:S");
         // Search for string.
         String testString = "";
         Scanner in = new Scanner(logs);
diff --git a/hostsidetests/theme/src/android/theme/cts/ThemeHostTest.java b/hostsidetests/theme/src/android/theme/cts/ThemeHostTest.java
index b4bb748..e17e205 100644
--- a/hostsidetests/theme/src/android/theme/cts/ThemeHostTest.java
+++ b/hostsidetests/theme/src/android/theme/cts/ThemeHostTest.java
@@ -16,8 +16,8 @@
 
 package android.theme.cts;
 
+import com.android.compatibility.common.util.AbiUtils;
 import com.android.cts.tradefed.build.CtsBuildHelper;
-import com.android.cts.util.AbiUtils;
 import com.android.cts.util.TimeoutReq;
 import com.android.ddmlib.Log;
 import com.android.ddmlib.Log.LogLevel;
diff --git a/hostsidetests/usb/src/com/android/cts/usb/TestUsbTest.java b/hostsidetests/usb/src/com/android/cts/usb/TestUsbTest.java
index 3af52c0..7c85707 100644
--- a/hostsidetests/usb/src/com/android/cts/usb/TestUsbTest.java
+++ b/hostsidetests/usb/src/com/android/cts/usb/TestUsbTest.java
@@ -15,8 +15,8 @@
  */
 package com.android.cts.usb;
 
+import com.android.compatibility.common.util.AbiUtils;
 import com.android.cts.tradefed.build.CtsBuildHelper;
-import com.android.cts.util.AbiUtils;
 import com.android.ddmlib.testrunner.RemoteAndroidTestRunner;
 import com.android.ddmlib.testrunner.TestRunResult;
 import com.android.tradefed.build.IBuildInfo;
diff --git a/libs/commonutil/Android.mk b/libs/commonutil/Android.mk
deleted file mode 100644
index 9c131b0..0000000
--- a/libs/commonutil/Android.mk
+++ /dev/null
@@ -1,27 +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)
-
-# ======================================================
-# Build a static host library for the AbiUtils
-LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
-LOCAL_SRC_FILES := src/com/android/cts/util/AbiUtils.java
-LOCAL_MODULE_TAGS := optional
-
-LOCAL_MODULE := ctsabiutilslib
-include $(BUILD_HOST_JAVA_LIBRARY)
diff --git a/libs/commonutil/src/com/android/cts/util/MeasureRun.java b/libs/commonutil/src/com/android/cts/util/MeasureRun.java
deleted file mode 100644
index 43b5acf..0000000
--- a/libs/commonutil/src/com/android/cts/util/MeasureRun.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.cts.util;
-
-/**
- * interface for measuring time for each run.
- */
-public abstract class MeasureRun {
-    /**
-     *  called before each run. not included to time measurement.
-     */
-    public void prepare(int i) throws Exception {
-        // default empty implementation
-    };
-
-    abstract public void run(int i) throws Exception;
-}
diff --git a/libs/commonutil/src/com/android/cts/util/MeasureTime.java b/libs/commonutil/src/com/android/cts/util/MeasureTime.java
deleted file mode 100644
index fd22ef2..0000000
--- a/libs/commonutil/src/com/android/cts/util/MeasureTime.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.cts.util;
-
-
-public class MeasureTime {
-    /**
-     * measure time taken for each run for given count
-     * @param count
-     * @param run
-     * @return array of time taken in each run in msec.
-     * @throws Exception
-     */
-    public static double[] measure(int count, MeasureRun run) throws Exception {
-        double[] result = new double[count];
-
-        for (int i = 0; i < count; i++) {
-            run.prepare(i);
-            long start = System.currentTimeMillis();
-            run.run(i);
-            long end =  System.currentTimeMillis();
-            result[i] = end - start;
-        }
-        return result;
-    }
-}
diff --git a/libs/commonutil/src/com/android/cts/util/ReportLog.java b/libs/commonutil/src/com/android/cts/util/ReportLog.java
index dd4b414..05dec0f 100644
--- a/libs/commonutil/src/com/android/cts/util/ReportLog.java
+++ b/libs/commonutil/src/com/android/cts/util/ReportLog.java
@@ -16,29 +16,28 @@
 
 package com.android.cts.util;
 
-import java.util.LinkedList;
-import java.util.List;
+import com.android.compatibility.common.util.Stat;
 
-import junit.framework.Assert;
+import org.xmlpull.v1.XmlPullParserException;
 
+import java.io.IOException;
 
 /**
  * Utility class to print performance measurement result back to host.
  * For now, throws know exception with message.
  *
- * Format:
- * Message = summary log SUMMARY_SEPARATOR [LOG_SEPARATOR log]*
- * summary = message|target|unit|type|value, target can be " " if there is no target set.
- * log for array = classMethodName:line_number|message|unit|type|space seSummaryparated values
+ * This class is deprecated, use {@link com.android.compatibility.common.util.ReportLog}
+ * instead.
  */
+@Deprecated
 public class ReportLog {
-    private static final String LOG_SEPARATOR = "+++";
-    private static final String SUMMARY_SEPARATOR = "++++";
-    private static final String LOG_ELEM_SEPARATOR = "|";
 
-    private List<String> mMessages = new LinkedList<String> ();
-    private String mSummary = null;
     protected static int mDepth = 3;
+    protected com.android.compatibility.common.util.ReportLog mReportLog;
+
+    public ReportLog(com.android.compatibility.common.util.ReportLog reportLog) {
+        mReportLog = reportLog;
+    }
 
     /**
      * print array of values to output log
@@ -80,23 +79,11 @@
 
     private void doPrintArray(String testId, String message,
             double[] values, ResultType type, ResultUnit unit) {
-        StringBuilder builder = new StringBuilder();
-        // note mDepth + 1 as this function will be called by printVaue or printArray
-        // and we need caller of printValue / printArray
-        builder.append(testId);
-        builder.append(LOG_ELEM_SEPARATOR);
-        builder.append(message);
-        builder.append(LOG_ELEM_SEPARATOR);
-        builder.append(type.getXmlString());
-        builder.append(LOG_ELEM_SEPARATOR);
-        builder.append(unit.getXmlString());
-        builder.append(LOG_ELEM_SEPARATOR);
-        for (double v : values) {
-            builder.append(v);
-            builder.append(" ");
-        }
-        mMessages.add(builder.toString());
-        printLog(builder.toString());
+        mReportLog.addValues(testId, message, values,
+                com.android.compatibility.common.util.ResultType.parseReportString(
+                        type.getXmlString()),
+                com.android.compatibility.common.util.ResultUnit.parseReportString(
+                        unit.getXmlString()));
     }
 
     /**
@@ -113,18 +100,12 @@
      */
     public void printSummaryWithTarget(String message, double target, double value,
             ResultType type, ResultUnit unit) {
-        mSummary = message + LOG_ELEM_SEPARATOR + target + LOG_ELEM_SEPARATOR + type.getXmlString()
-                + LOG_ELEM_SEPARATOR + unit.getXmlString() + LOG_ELEM_SEPARATOR + value;
-        boolean resultOk = true;
-        if (type == ResultType.HIGHER_BETTER) {
-            resultOk = value >= target;
-        } else if (type == ResultType.LOWER_BETTER) {
-            resultOk = value <= target;
-        }
-        if (!resultOk) {
-            Assert.fail("Measured result " + value + " does not meet perf target " + target +
-                    " with type " + type.getXmlString());
-        }
+        // Ignore target
+        mReportLog.setSummary(message, value,
+                com.android.compatibility.common.util.ResultType.parseReportString(
+                        type.getXmlString()),
+                com.android.compatibility.common.util.ResultUnit.parseReportString(
+                        unit.getXmlString()));
     }
 
     /**
@@ -136,32 +117,24 @@
      * @param unit unit of the data
      */
     public void printSummary(String message, double value, ResultType type, ResultUnit unit) {
-        mSummary = message + LOG_ELEM_SEPARATOR + " " + LOG_ELEM_SEPARATOR + type.getXmlString() +
-                LOG_ELEM_SEPARATOR + unit.getXmlString() + LOG_ELEM_SEPARATOR + value;
+        mReportLog.setSummary(message, value,
+                com.android.compatibility.common.util.ResultType.parseReportString(
+                        type.getXmlString()),
+                com.android.compatibility.common.util.ResultUnit.parseReportString(
+                        unit.getXmlString()));
     }
 
     /**
      * @return a string representation of this report.
      */
     protected String generateReport() {
-        if ((mSummary == null) && mMessages.isEmpty()) {
-            // just return empty string
-            return "";
+        try {
+            return com.android.compatibility.common.util.ReportLog.serialize(mReportLog);
+        } catch (IllegalArgumentException | IllegalStateException | XmlPullParserException
+                | IOException e) {
+            e.printStackTrace();
         }
-        StringBuilder builder = new StringBuilder();
-        builder.append(mSummary);
-        builder.append(SUMMARY_SEPARATOR);
-        for (String entry : mMessages) {
-            builder.append(entry);
-            builder.append(LOG_SEPARATOR);
-        }
-        // delete the last separator
-        if (builder.length() >= LOG_SEPARATOR.length()) {
-            builder.delete(builder.length() - LOG_SEPARATOR.length(), builder.length());
-        }
-        mSummary = null;
-        mMessages.clear();
-        return builder.toString();
+        return null;
     }
 
     /**
@@ -172,27 +145,14 @@
      * @return
      */
     public static double calcRatePerSec(double change, double timeInMSec) {
-        if (timeInMSec == 0) {
-            return change * 1000.0 / 0.001; // do not allow zero
-        } else {
-            return change * 1000.0 / timeInMSec;
-        }
+        return Stat.calcRatePerSec(change, timeInMSec);
     }
 
     /**
      * array version of calcRatePerSecArray
      */
     public static double[] calcRatePerSecArray(double change, double[] timeInMSec) {
-        double[] result = new double[timeInMSec.length];
-        change *= 1000.0;
-        for (int i = 0; i < timeInMSec.length; i++) {
-            if (timeInMSec[i] == 0) {
-                result[i] = change / 0.001;
-            } else {
-                result[i] = change / timeInMSec[i];
-            }
-        }
-        return result;
+        return Stat.calcRatePerSecArray(change, timeInMSec);
     }
 
     /**
@@ -219,10 +179,4 @@
         return names;
     }
 
-    /**
-     * to be overridden by child to print message to be passed
-     */
-    protected void printLog(String msg) {
-
-    }
 }
diff --git a/libs/commonutil/src/com/android/cts/util/ResultType.java b/libs/commonutil/src/com/android/cts/util/ResultType.java
index a5a388c..dbe8602 100644
--- a/libs/commonutil/src/com/android/cts/util/ResultType.java
+++ b/libs/commonutil/src/com/android/cts/util/ResultType.java
@@ -18,7 +18,11 @@
 
 /**
  * Enum for distinguishing performance results.
+ *
+ * This class is deprecated, use {@link com.android.compatibility.common.util.ResultType}
+ * instead.
  */
+@Deprecated
 public enum ResultType {
     /** lower score shows better performance */
     LOWER_BETTER,
diff --git a/libs/commonutil/src/com/android/cts/util/ResultUnit.java b/libs/commonutil/src/com/android/cts/util/ResultUnit.java
index a216a7e..2148821 100644
--- a/libs/commonutil/src/com/android/cts/util/ResultUnit.java
+++ b/libs/commonutil/src/com/android/cts/util/ResultUnit.java
@@ -19,7 +19,10 @@
 /**
  * Enum for representing the unit of performance results.
  *
+ * This class is deprecated, use {@link com.android.compatibility.common.util.ResultUnit}
+ * instead.
  */
+@Deprecated
 public enum ResultUnit {
     /** for value with no unit */
     NONE,
diff --git a/libs/commonutil/src/com/android/cts/util/Stat.java b/libs/commonutil/src/com/android/cts/util/Stat.java
deleted file mode 100644
index ceafa4e..0000000
--- a/libs/commonutil/src/com/android/cts/util/Stat.java
+++ /dev/null
@@ -1,157 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.cts.util;
-
-import java.util.Arrays;
-
-/**
- * Utilities for doing statistics
- *
- */
-public class Stat {
-
-    /**
-     * Collection of statistical propertirs like average, max, min, and stddev
-     */
-    public static class StatResult {
-        public double mAverage;
-        public double mMin;
-        public double mMax;
-        public double mStddev;
-        public int mDataCount;
-        public StatResult(double average, double min, double max, double stddev, int dataCount) {
-            mAverage = average;
-            mMin = min;
-            mMax = max;
-            mStddev = stddev;
-            mDataCount = dataCount;
-        }
-    }
-
-    /**
-     * Calculate statistics properties likes average, min, max, and stddev for the given array
-     */
-    public static StatResult getStat(double[] data) {
-        double average = data[0];
-        double min = data[0];
-        double max = data[0];
-        double eX2 = data[0] * data[0]; // will become E[X^2]
-        for (int i = 1; i < data.length; i++) {
-            average += data[i];
-            eX2 += data[i] * data[i];
-            if (data[i] > max) {
-                max = data[i];
-            }
-            if (data[i] < min) {
-                min = data[i];
-            }
-        }
-        average /= data.length;
-        eX2 /= data.length;
-        // stddev = sqrt(E[X^2] - (E[X])^2)
-        double stddev = Math.sqrt(eX2 - average * average);
-        return new StatResult(average, min, max, stddev, data.length);
-    }
-
-    /**
-     * Calculate statistics properties likes average, min, max, and stddev for the given array
-     * while rejecting outlier +/- median * rejectionThreshold.
-     * rejectionThreshold should be bigger than 0.0 and be lowerthan 1.0
-     */
-    public static StatResult getStatWithOutlierRejection(double[] data, double rejectionThreshold) {
-        double[] dataCopied = Arrays.copyOf(data, data.length);
-        Arrays.sort(dataCopied);
-        int medianIndex = dataCopied.length / 2;
-        double median;
-        if (dataCopied.length % 2 == 1) {
-            median = dataCopied[medianIndex];
-        } else {
-            median = (dataCopied[medianIndex - 1] + dataCopied[medianIndex]) / 2.0;
-        }
-        double thresholdMin = median * (1.0 - rejectionThreshold);
-        double thresholdMax = median * (1.0 + rejectionThreshold);
-
-        double average = 0.0;
-        double min = median;
-        double max = median;
-        double eX2 = 0.0; // will become E[X^2]
-        int validDataCounter = 0;
-        for (int i = 0; i < data.length; i++) {
-            if ((data[i] > thresholdMin) && (data[i] < thresholdMax)) {
-                validDataCounter++;
-                average += data[i];
-                eX2 += data[i] * data[i];
-                if (data[i] > max) {
-                    max = data[i];
-                }
-                if (data[i] < min) {
-                    min = data[i];
-                }
-            }
-            //TODO report rejected data
-        }
-        double stddev;
-        if (validDataCounter > 0) {
-            average /= validDataCounter;
-            eX2 /= validDataCounter;
-            // stddev = sqrt(E[X^2] - (E[X])^2)
-            stddev = Math.sqrt(eX2 - average * average);
-        } else { // both median is showing too much diff
-            average = median;
-            stddev = 0; // don't care
-        }
-
-        return new StatResult(average, min, max, stddev, validDataCounter);
-    }
-
-    /**
-     * return the average value of the passed array
-     */
-    public static double getAverage(double[] data) {
-        double sum = data[0];
-        for (int i = 1; i < data.length; i++) {
-            sum += data[i];
-        }
-        return sum / data.length;
-    }
-
-    /**
-     * return the minimum value of the passed array
-     */
-    public static double getMin(double[] data) {
-        double min = data[0];
-        for (int i = 1; i < data.length; i++) {
-            if (data[i] < min) {
-                min = data[i];
-            }
-        }
-        return min;
-    }
-
-    /**
-     * return the maximum value of the passed array
-     */
-    public static double getMax(double[] data) {
-        double max = data[0];
-        for (int i = 1; i < data.length; i++) {
-            if (data[i] > max) {
-                max = data[i];
-            }
-        }
-        return max;
-    }
-}
diff --git a/libs/commonutil/src/com/android/cts/util/StatisticsUtils.java b/libs/commonutil/src/com/android/cts/util/StatisticsUtils.java
deleted file mode 100644
index d6589af..0000000
--- a/libs/commonutil/src/com/android/cts/util/StatisticsUtils.java
+++ /dev/null
@@ -1,112 +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.
- */
-package com.android.cts.util;
-
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Set of static helper methods for CTS tests.
- */
-public class StatisticsUtils {
-
-
-    /**
-     * Private constructor for static class.
-     */
-    private StatisticsUtils() {}
-
-    /**
-     * Get the value of the 95th percentile using nearest rank algorithm.
-     *
-     * @throws IllegalArgumentException if the collection is null or empty
-     */
-    public static <TValue extends Comparable<? super TValue>> TValue get95PercentileValue(
-            Collection<TValue> collection) {
-        validateCollection(collection);
-
-        List<TValue> arrayCopy = new ArrayList<TValue>(collection);
-        Collections.sort(arrayCopy);
-
-        // zero-based array index
-        int arrayIndex = (int) Math.round(arrayCopy.size() * 0.95 + .5) - 1;
-
-        return arrayCopy.get(arrayIndex);
-    }
-
-    /**
-     * Calculate the mean of a collection.
-     *
-     * @throws IllegalArgumentException if the collection is null or empty
-     */
-    public static <TValue extends Number> double getMean(Collection<TValue> collection) {
-        validateCollection(collection);
-
-        double sum = 0.0;
-        for(TValue value : collection) {
-            sum += value.doubleValue();
-        }
-        return sum / collection.size();
-    }
-
-    /**
-     * Calculate the bias-corrected sample variance of a collection.
-     *
-     * @throws IllegalArgumentException if the collection is null or empty
-     */
-    public static <TValue extends Number> double getVariance(Collection<TValue> collection) {
-        validateCollection(collection);
-
-        double mean = getMean(collection);
-        ArrayList<Double> squaredDiffs = new ArrayList<Double>();
-        for(TValue value : collection) {
-            double difference = mean - value.doubleValue();
-            squaredDiffs.add(Math.pow(difference, 2));
-        }
-
-        double sum = 0.0;
-        for (Double value : squaredDiffs) {
-            sum += value;
-        }
-        return sum / (squaredDiffs.size() - 1);
-    }
-
-    /**
-     * Calculate the bias-corrected standard deviation of a collection.
-     *
-     * @throws IllegalArgumentException if the collection is null or empty
-     */
-    public static <TValue extends Number> double getStandardDeviation(
-            Collection<TValue> collection) {
-        return Math.sqrt(getVariance(collection));
-    }
-
-    /**
-     * Validate that a collection is not null or empty.
-     *
-     * @throws IllegalStateException if collection is null or empty.
-     */
-    private static <T> void validateCollection(Collection<T> collection) {
-        if(collection == null || collection.size() == 0) {
-            throw new IllegalStateException("Collection cannot be null or empty");
-        }
-    }
-
-}
diff --git a/libs/commonutil/src/com/android/cts/util/StatisticsUtilsTest.java b/libs/commonutil/src/com/android/cts/util/StatisticsUtilsTest.java
deleted file mode 100644
index d78ba99..0000000
--- a/libs/commonutil/src/com/android/cts/util/StatisticsUtilsTest.java
+++ /dev/null
@@ -1,107 +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.
- */
-
-package com.android.cts.util;
-
-import junit.framework.TestCase;
-
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.HashSet;
-import java.util.List;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Unit tests for the {@link StatisticsUtils} class.
- */
-public class StatisticsUtilsTest extends TestCase {
-
-    /**
-     * Test {@link StatisticsUtils#get95PercentileValue(Collection)}.
-     */
-    public void testGet95PercentileValue() {
-        Collection<Integer> values = new HashSet<Integer>();
-        for (int i = 0; i < 100; i++) {
-            values.add(i);
-        }
-        assertEquals(95, (int) StatisticsUtils.get95PercentileValue(values));
-
-        values = new HashSet<Integer>();
-        for (int i = 0; i < 1000; i++) {
-            values.add(i);
-        }
-        assertEquals(950, (int) StatisticsUtils.get95PercentileValue(values));
-
-        values = new HashSet<Integer>();
-        for (int i = 0; i < 100; i++) {
-            values.add(i * i);
-        }
-        assertEquals(95 * 95, (int) StatisticsUtils.get95PercentileValue(values));
-    }
-
-    /**
-     * Test {@link StatisticsUtils#getMean(Collection)}.
-     */
-    public void testGetMean() {
-        List<Integer> values = Arrays.asList(0, 1, 2, 3, 4);
-        double mean = StatisticsUtils.getMean(values);
-        assertEquals(2.0, mean, 0.00001);
-
-        values = Arrays.asList(1, 2, 3, 4, 5);
-        mean = StatisticsUtils.getMean(values);
-        assertEquals(3.0, mean, 0.00001);
-
-        values = Arrays.asList(0, 1, 4, 9, 16);
-        mean = StatisticsUtils.getMean(values);
-        assertEquals(6.0, mean, 0.00001);
-    }
-
-    /**
-     * Test {@link StatisticsUtils#getVariance(Collection)}.
-     */
-    public void testGetVariance() {
-        List<Integer> values = Arrays.asList(0, 1, 2, 3, 4);
-        double variance = StatisticsUtils.getVariance(values);
-        assertEquals(2.5, variance, 0.00001);
-
-        values = Arrays.asList(1, 2, 3, 4, 5);
-        variance = StatisticsUtils.getVariance(values);
-        assertEquals(2.5, variance, 0.00001);
-
-        values = Arrays.asList(0, 2, 4, 6, 8);
-        variance = StatisticsUtils.getVariance(values);
-        assertEquals(10.0, variance, 0.00001);
-    }
-
-    /**
-     * Test {@link StatisticsUtils#getStandardDeviation(Collection)}.
-     */
-    public void testGetStandardDeviation() {
-        List<Integer> values = Arrays.asList(0, 1, 2, 3, 4);
-        double stddev = StatisticsUtils.getStandardDeviation(values);
-        assertEquals(Math.sqrt(2.5), stddev, 0.00001);
-
-        values = Arrays.asList(1, 2, 3, 4, 5);
-        stddev = StatisticsUtils.getStandardDeviation(values);
-        assertEquals(Math.sqrt(2.5), stddev, 0.00001);
-
-        values = Arrays.asList(0, 2, 4, 6, 8);
-        stddev = StatisticsUtils.getStandardDeviation(values);
-        assertEquals(Math.sqrt(10.0), stddev, 0.00001);
-    }
-
-
-}
diff --git a/libs/deviceutil/Android.mk b/libs/deviceutil/Android.mk
index 8c81ee4..bb039ca 100644
--- a/libs/deviceutil/Android.mk
+++ b/libs/deviceutil/Android.mk
@@ -22,6 +22,8 @@
 
 LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
 
+LOCAL_STATIC_JAVA_LIBRARIES := compatibility-device-util
+
 LOCAL_MODULE_TAGS := optional
 
 LOCAL_MODULE := ctsdeviceutil
diff --git a/libs/deviceutil/src/android/cts/util/CtsActivityInstrumentationTestCase2.java b/libs/deviceutil/src/android/cts/util/CtsActivityInstrumentationTestCase2.java
deleted file mode 100644
index e039407..0000000
--- a/libs/deviceutil/src/android/cts/util/CtsActivityInstrumentationTestCase2.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (C) 2012 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 android.cts.util;
-
-import com.android.cts.util.ReportLog;
-
-import android.app.Activity;
-import android.test.ActivityInstrumentationTestCase2;
-
-
-public class CtsActivityInstrumentationTestCase2<T extends Activity> extends
-        ActivityInstrumentationTestCase2<T> {
-
-    private DeviceReportLog mReportLog = new DeviceReportLog();
-
-    public CtsActivityInstrumentationTestCase2(Class<T> activityClass) {
-        super(activityClass);
-    }
-
-    public ReportLog getReportLog() {
-        return mReportLog;
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        mReportLog.deliverReportToHost(getInstrumentation());
-        super.tearDown();
-    }
-
-}
diff --git a/libs/deviceutil/src/android/cts/util/CtsAndroidTestCase.java b/libs/deviceutil/src/android/cts/util/CtsAndroidTestCase.java
index b1164bc..7cf0f7e 100644
--- a/libs/deviceutil/src/android/cts/util/CtsAndroidTestCase.java
+++ b/libs/deviceutil/src/android/cts/util/CtsAndroidTestCase.java
@@ -18,13 +18,14 @@
 package android.cts.util;
 
 import android.content.Context;
+import android.test.ActivityInstrumentationTestCase2;
 
 /**
  *  This class emulates AndroidTestCase, but internally it is ActivityInstrumentationTestCase2
  *  to access Instrumentation.
  *  DummyActivity is not supposed to be accessed.
  */
-public class CtsAndroidTestCase extends CtsActivityInstrumentationTestCase2<DummyActivity> {
+public class CtsAndroidTestCase extends ActivityInstrumentationTestCase2<DummyActivity> {
     public CtsAndroidTestCase() {
         super(DummyActivity.class);
     }
diff --git a/libs/deviceutil/src/android/cts/util/DeviceReportLog.java b/libs/deviceutil/src/android/cts/util/DeviceReportLog.java
index 63b07b7..efc31f0 100644
--- a/libs/deviceutil/src/android/cts/util/DeviceReportLog.java
+++ b/libs/deviceutil/src/android/cts/util/DeviceReportLog.java
@@ -22,6 +22,11 @@
 
 import com.android.cts.util.ReportLog;
 
+/**
+ * This class is deprecated, use {@link com.android.compatibility.common.util.DeviceReportLog}
+ * instead.
+ */
+@Deprecated
 public class DeviceReportLog extends ReportLog {
     private static final String TAG = "DeviceCtsReport";
     private static final String CTS_RESULT_KEY = "CTS_TEST_RESULT";
@@ -33,14 +38,10 @@
     }
 
     public DeviceReportLog(int depth) {
+        super(new com.android.compatibility.common.util.DeviceReportLog());
         mDepth = BASE_DEPTH + depth;
     }
 
-    @Override
-    protected void printLog(String msg) {
-        Log.i(TAG, msg);
-    }
-
     public void deliverReportToHost(Instrumentation instrumentation) {
         Log.i(TAG, "deliverReportToHost");
         String report = generateReport();
@@ -50,4 +51,4 @@
             instrumentation.sendStatus(INST_STATUS_IN_PROGRESS, output);
         }
     }
-}
+}
\ No newline at end of file
diff --git a/run_unit_tests.sh b/run_unit_tests.sh
new file mode 100755
index 0000000..97cd037
--- /dev/null
+++ b/run_unit_tests.sh
@@ -0,0 +1,107 @@
+#!/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
+
+checkFile() {
+    if [ ! -f "$1" ]; then
+        echo "Unable to locate $1"
+        exit
+    fi;
+}
+
+# check if in Android build env
+if [ ! -z ${ANDROID_BUILD_TOP} ]; then
+    HOST=`uname`
+    if [ "$HOST" == "Linux" ]; then
+        OS="linux-x86"
+    elif [ "$HOST" == "Darwin" ]; then
+        OS="darwin-x86"
+    else
+        echo "Unrecognized OS"
+        exit
+    fi;
+fi;
+
+############### Run the device side tests ###############
+JAR_DIR=${ANDROID_HOST_OUT}/framework
+JARS="
+    ddmlib-prebuilt\
+    hosttestlib\
+    tradefed-prebuilt"
+JAR_PATH=
+for JAR in $JARS; do
+    checkFile ${JAR_DIR}/${JAR}.jar
+    JAR_PATH=${JAR_PATH}:${JAR_DIR}/${JAR}.jar
+done
+
+APK=${ANDROID_PRODUCT_OUT}/data/app/CompatibilityTestApp/CompatibilityTestApp.apk
+checkFile ${APK}
+
+TF_CONSOLE=com.android.tradefed.command.Console
+COMMON_PACKAGE=com.android.compatibility.common
+RUNNER=android.support.test.runner.AndroidJUnitRunner
+adb install -r -g ${APK}
+java $RDBG_FLAG -cp ${JAR_PATH} ${TF_CONSOLE} run singleCommand instrument --package ${COMMON_PACKAGE} --runner ${RUNNER}
+adb uninstall ${COMMON_PACKAGE}
+
+############### Run the host side tests ###############
+JARS="
+    compatibility-common-util-hostsidelib\
+    compatibility-common-util-tests\
+    compatibility-host-util\
+    compatibility-host-util-tests\
+    compatibility-mock-tradefed\
+    compatibility-tradefed-tests\
+    ddmlib-prebuilt\
+    hosttestlib\
+    tradefed-prebuilt"
+JAR_PATH=
+for JAR in $JARS; do
+    checkFile ${JAR_DIR}/${JAR}.jar
+    JAR_PATH=${JAR_PATH}:${JAR_DIR}/${JAR}.jar
+done
+
+TEST_CLASSES="
+    com.android.compatibility.common.tradefed.UnitTests\
+    com.android.compatibility.common.util.HostUnitTests\
+    com.android.compatibility.common.util.UnitTests"
+
+for CLASS in ${TEST_CLASSES}; do
+    java $RDBG_FLAG -cp ${JAR_PATH} ${TF_CONSOLE} run singleCommand host -n --class ${CLASS} "$@"
+done
+
+############### Run the cts tests ###############
+JARS="
+    compatibility-common-util-hostsidelib\
+    compatibility-host-util\
+    cts-tradefed-tests_v2\
+    cts-tradefed_v2\
+    ddmlib-prebuilt\
+    hosttestlib\
+    tradefed-prebuilt"
+JAR_PATH=
+for JAR in $JARS; do
+    checkFile ${JAR_DIR}/${JAR}.jar
+    JAR_PATH=${JAR_PATH}:${JAR_DIR}/${JAR}.jar
+done
+
+TEST_CLASSES="
+    com.android.compatibility.tradefed.CtsTradefedTest"
+
+for CLASS in ${TEST_CLASSES}; do
+    java $RDBG_FLAG -cp ${JAR_PATH} ${TF_CONSOLE} run singleCommand host -n --class ${CLASS} "$@"
+done
diff --git a/suite/cts/deviceTests/browserbench/Android.mk b/suite/cts/deviceTests/browserbench/Android.mk
index 3696bcd..6f247de 100644
--- a/suite/cts/deviceTests/browserbench/Android.mk
+++ b/suite/cts/deviceTests/browserbench/Android.mk
@@ -18,7 +18,7 @@
 # don't include this package in any target
 LOCAL_MODULE_TAGS := optional
 
-LOCAL_STATIC_JAVA_LIBRARIES := ctsdeviceutil ctstestrunner ctstestserver
+LOCAL_STATIC_JAVA_LIBRARIES := ctsdeviceutil compatibility-device-util ctstestrunner ctstestserver
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
diff --git a/suite/cts/deviceTests/browserbench/src/com/android/cts/browser/BrowserBenchTest.java b/suite/cts/deviceTests/browserbench/src/com/android/cts/browser/BrowserBenchTest.java
index d74ddb2..98a4ec8 100644
--- a/suite/cts/deviceTests/browserbench/src/com/android/cts/browser/BrowserBenchTest.java
+++ b/suite/cts/deviceTests/browserbench/src/com/android/cts/browser/BrowserBenchTest.java
@@ -18,18 +18,23 @@
 
 import android.content.Intent;
 import android.content.pm.PackageManager;
+import android.cts.util.CtsAndroidTestCase;
 import android.cts.util.WatchDog;
 import android.net.Uri;
 import android.provider.Browser;
 import android.util.Log;
 import android.webkit.cts.CtsTestServer;
 
-import android.cts.util.CtsAndroidTestCase;
-import com.android.cts.util.ResultType;
-import com.android.cts.util.ResultUnit;
-import com.android.cts.util.Stat;
+import com.android.compatibility.common.util.DeviceReportLog;
+import com.android.compatibility.common.util.ResultType;
+import com.android.compatibility.common.util.ResultUnit;
+import com.android.compatibility.common.util.Stat;
 import com.android.cts.util.TimeoutReq;
 
+import org.apache.http.HttpRequest;
+import org.apache.http.HttpResponse;
+import org.apache.http.RequestLine;
+
 import java.net.URLDecoder;
 import java.util.LinkedHashMap;
 import java.util.Map;
@@ -37,10 +42,6 @@
 import java.util.concurrent.TimeUnit;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
-
-import org.apache.http.HttpRequest;
-import org.apache.http.HttpResponse;
-import org.apache.http.RequestLine;
 /**
  * Browser benchmarking.
  * It launches an activity with URL and wait for POST from the client.
@@ -72,10 +73,12 @@
     /** stores results for each runs. last entry will be the final score. */
     private LinkedHashMap<String, double[]> mResultsMap;
     private PackageManager mPackageManager;
+    private DeviceReportLog mReport;
 
     @Override
     protected void setUp() throws Exception {
         super.setUp();
+        mReport = new DeviceReportLog();
         mPackageManager = getInstrumentation().getContext().getPackageManager();
         mWebServer = new CtsTestServer(getContext()) {
             @Override
@@ -102,7 +105,7 @@
                     scores[mRunIndex] = score;
                     if (isFinal == 1) {
                         String userAgent = request.getFirstHeader(HTTP_USER_AGENT).getValue();
-                        getReportLog().printValue(HTTP_USER_AGENT + "=" + userAgent, 0,
+                        mReport.addValue(HTTP_USER_AGENT + "=" + userAgent, 0,
                                 ResultType.NEUTRAL, ResultUnit.NONE);
                         mLatch.countDown();
                     }
@@ -122,6 +125,7 @@
         mWebServer.shutdown();
         mWebServer = null;
         mResultsMap = null;
+        mReport.submit(getInstrumentation());
         super.tearDown();
     }
 
@@ -159,17 +163,16 @@
         }
         // it is somewhat awkward to handle the last one specially with Map
         int numberEntries = mResultsMap.size();
-        int numberToProcess = 1;
+        int numberToProcess = 1; 
         for (Map.Entry<String, double[]> entry : mResultsMap.entrySet()) {
             String message = entry.getKey();
             double[] scores = entry.getValue();
             if (numberToProcess == numberEntries) { // final score
                 // store the whole results first
-                getReportLog().printArray(message, scores, mTypeFinal, mUnitFinal);
-                getReportLog().printSummary(message, Stat.getAverage(scores), mTypeFinal,
-                        mUnitFinal);
+                mReport.addValues(message, scores, mTypeFinal, mUnitFinal);
+                mReport.setSummary(message, Stat.getAverage(scores), mTypeFinal, mUnitFinal);
             } else { // interim results
-                getReportLog().printArray(message, scores, mTypeNonFinal, mUnitNonFinal);
+                mReport.addValues(message, scores, mTypeNonFinal, mUnitNonFinal);
             }
             numberToProcess++;
         }
diff --git a/suite/cts/deviceTests/dram/Android.mk b/suite/cts/deviceTests/dram/Android.mk
index 879d151..fa959e1 100644
--- a/suite/cts/deviceTests/dram/Android.mk
+++ b/suite/cts/deviceTests/dram/Android.mk
@@ -21,7 +21,7 @@
 # Include both the 32 and 64 bit versions
 LOCAL_MULTILIB := both
 
-LOCAL_STATIC_JAVA_LIBRARIES := ctsdeviceutil ctstestrunner
+LOCAL_STATIC_JAVA_LIBRARIES := ctsdeviceutil compatibility-device-util ctstestrunner
 
 LOCAL_JNI_SHARED_LIBRARIES := libctsdram_jni
 
diff --git a/suite/cts/deviceTests/dram/src/com/android/cts/dram/BandwidthTest.java b/suite/cts/deviceTests/dram/src/com/android/cts/dram/BandwidthTest.java
index eeb7f9b..61aabaf 100644
--- a/suite/cts/deviceTests/dram/src/com/android/cts/dram/BandwidthTest.java
+++ b/suite/cts/deviceTests/dram/src/com/android/cts/dram/BandwidthTest.java
@@ -17,15 +17,15 @@
 package com.android.cts.dram;
 
 import android.content.Context;
+import android.cts.util.CtsAndroidTestCase;
 import android.graphics.Point;
 import android.util.Log;
 import android.view.WindowManager;
 
-import com.android.cts.util.ResultType;
-import com.android.cts.util.ResultUnit;
-import android.cts.util.CtsAndroidTestCase;
-import com.android.cts.util.ReportLog;
-import com.android.cts.util.Stat;
+import com.android.compatibility.common.util.DeviceReportLog;
+import com.android.compatibility.common.util.ResultType;
+import com.android.compatibility.common.util.ResultUnit;
+import com.android.compatibility.common.util.Stat;
 
 /**
  * check how many screens the memcpy function can copy in a sec.
@@ -165,11 +165,12 @@
         for (int i = 0; i < MEMCPY_REPETITION; i++) {
             result[i] = MemoryNative.runMemcpy(bufferSize, repeatInEachCall);
         }
-        getReportLog().printArray("memcpy time", result, ResultType.LOWER_BETTER,
+        DeviceReportLog report = new DeviceReportLog();
+        report.addValues("memcpy time", result, ResultType.LOWER_BETTER,
                 ResultUnit.MS);
-        double[] mbps = ReportLog.calcRatePerSecArray(
+        double[] mbps = Stat.calcRatePerSecArray(
                 (double)bufferSize * repeatInEachCall / 1024.0 / 1024.0, result);
-        getReportLog().printArray("memcpy throughput", mbps, ResultType.HIGHER_BETTER,
+        report.addValues("memcpy throughput", mbps, ResultType.HIGHER_BETTER,
                 ResultUnit.MBPS);
         Stat.StatResult stat = Stat.getStatWithOutlierRejection(mbps, OUTLIER_THRESHOLD);
         if (stat.mDataCount != result.length) {
@@ -182,10 +183,11 @@
         double pixels = size.x * size.y;
         // now this represents how many times the whole screen can be copied in a sec.
         double screensPerSecAverage = stat.mAverage / pixels * 1024.0 * 1024.0 / 4.0;
-        getReportLog().printValue("memcpy in fps", screensPerSecAverage,
+        report.addValue("memcpy in fps", screensPerSecAverage,
                 ResultType.HIGHER_BETTER, ResultUnit.FPS);
-        getReportLog().printSummary("memcpy throughput", stat.mAverage, ResultType.HIGHER_BETTER,
+        report.setSummary("memcpy throughput", stat.mAverage, ResultType.HIGHER_BETTER,
                 ResultUnit.MBPS);
+        report.submit(getInstrumentation());
     }
 
     private void doRunMemset(int bufferSize) {
@@ -198,12 +200,11 @@
         for (int i = 0; i < MEMSET_REPETITION; i++) {
             result[i] = MemoryNative.runMemset(bufferSize, repeatInEachCall, MEMSET_CHAR);
         }
-        getReportLog().printArray("memset time", result, ResultType.LOWER_BETTER,
-                ResultUnit.MS);
-        double[] mbps = ReportLog.calcRatePerSecArray(
+        DeviceReportLog report = new DeviceReportLog();
+        report.addValues("memset time", result, ResultType.LOWER_BETTER, ResultUnit.MS);
+        double[] mbps = Stat.calcRatePerSecArray(
                 (double)bufferSize * repeatInEachCall / 1024.0 / 1024.0, result);
-        getReportLog().printArray("memset throughput", mbps, ResultType.HIGHER_BETTER,
-                ResultUnit.MBPS);
+        report.addValues("memset throughput", mbps, ResultType.HIGHER_BETTER, ResultUnit.MBPS);
         Stat.StatResult stat = Stat.getStatWithOutlierRejection(mbps, OUTLIER_THRESHOLD);
         if (stat.mDataCount != result.length) {
             Log.w(TAG, "rejecting " + (result.length - stat.mDataCount) + " outliers");
@@ -215,9 +216,10 @@
         double pixels = size.x * size.y;
         // now this represents how many times the whole screen can be copied in a sec.
         double screensPerSecAverage = stat.mAverage / pixels * 1024.0 * 1024.0 / 4.0;
-        getReportLog().printValue("memset in fps", screensPerSecAverage,
+        report.addValue("memset in fps", screensPerSecAverage,
                 ResultType.HIGHER_BETTER, ResultUnit.FPS);
-        getReportLog().printSummary("memset throughput", stat.mAverage, ResultType.HIGHER_BETTER,
+        report.setSummary("memset throughput", stat.mAverage, ResultType.HIGHER_BETTER,
                 ResultUnit.MBPS);
+        report.submit(getInstrumentation());
     }
 }
diff --git a/suite/cts/deviceTests/filesystemperf/Android.mk b/suite/cts/deviceTests/filesystemperf/Android.mk
index 843d21a..b98971b 100644
--- a/suite/cts/deviceTests/filesystemperf/Android.mk
+++ b/suite/cts/deviceTests/filesystemperf/Android.mk
@@ -18,7 +18,7 @@
 # don't include this package in any target
 LOCAL_MODULE_TAGS := optional
 
-LOCAL_STATIC_JAVA_LIBRARIES := ctsdeviceutil ctstestrunner
+LOCAL_STATIC_JAVA_LIBRARIES := ctsdeviceutil compatibility-device-util ctstestrunner
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
diff --git a/suite/cts/deviceTests/filesystemperf/src/com/android/cts/filesystemperf/AlmostFullTest.java b/suite/cts/deviceTests/filesystemperf/src/com/android/cts/filesystemperf/AlmostFullTest.java
index ab81f16..604dbcd 100644
--- a/suite/cts/deviceTests/filesystemperf/src/com/android/cts/filesystemperf/AlmostFullTest.java
+++ b/suite/cts/deviceTests/filesystemperf/src/com/android/cts/filesystemperf/AlmostFullTest.java
@@ -21,6 +21,7 @@
 import android.cts.util.CtsAndroidTestCase;
 import android.cts.util.SystemUtil;
 
+import com.android.compatibility.common.util.DeviceReportLog;
 import com.android.cts.util.TimeoutReq;
 
 import java.util.concurrent.atomic.AtomicBoolean;
@@ -106,8 +107,10 @@
         }
         final int BUFFER_SIZE = 10 * 1024 * 1024;
         final int NUMBER_REPETITION = 10;
-        FileUtil.doSequentialUpdateTest(getContext(), DIR_SEQ_UPDATE, getReportLog(), FILE_SIZE,
+        DeviceReportLog report = new DeviceReportLog();
+        FileUtil.doSequentialUpdateTest(getContext(), DIR_SEQ_UPDATE, report, FILE_SIZE,
                 BUFFER_SIZE, NUMBER_REPETITION);
+        report.submit(getInstrumentation());
     }
 
     // TODO: file size too small and caching will give wrong better result.
@@ -121,8 +124,9 @@
             Log.w(TAG, "too little space: " + freeDisk);
             return;
         }
-        FileUtil.doRandomReadTest(getContext(), DIR_RANDOM_RD, getReportLog(), fileSize,
-                BUFFER_SIZE);
+        DeviceReportLog report = new DeviceReportLog();
+        FileUtil.doRandomReadTest(getContext(), DIR_RANDOM_RD, report, fileSize, BUFFER_SIZE);
+        report.submit(getInstrumentation());
     }
 
     @TimeoutReq(minutes = 60)
@@ -134,7 +138,8 @@
             Log.w(TAG, "too little space: " + freeDisk);
             return;
         }
-        FileUtil.doRandomWriteTest(getContext(), DIR_RANDOM_WR, getReportLog(), fileSize,
-                BUFFER_SIZE);
+        DeviceReportLog report = new DeviceReportLog();
+        FileUtil.doRandomWriteTest(getContext(), DIR_RANDOM_WR, report, fileSize, BUFFER_SIZE);
+        report.submit(getInstrumentation());
     }
 }
diff --git a/suite/cts/deviceTests/filesystemperf/src/com/android/cts/filesystemperf/FileUtil.java b/suite/cts/deviceTests/filesystemperf/src/com/android/cts/filesystemperf/FileUtil.java
index 6231774..3ab34e7 100755
--- a/suite/cts/deviceTests/filesystemperf/src/com/android/cts/filesystemperf/FileUtil.java
+++ b/suite/cts/deviceTests/filesystemperf/src/com/android/cts/filesystemperf/FileUtil.java
@@ -16,6 +16,17 @@
 
 package com.android.cts.filesystemperf;
 
+import android.content.Context;
+import android.cts.util.SystemUtil;
+import android.util.Log;
+
+import com.android.compatibility.common.util.MeasureRun;
+import com.android.compatibility.common.util.MeasureTime;
+import com.android.compatibility.common.util.ReportLog;
+import com.android.compatibility.common.util.ResultType;
+import com.android.compatibility.common.util.ResultUnit;
+import com.android.compatibility.common.util.Stat;
+
 import java.io.BufferedReader;
 import java.io.File;
 import java.io.FileInputStream;
@@ -25,17 +36,6 @@
 import java.io.RandomAccessFile;
 import java.util.Random;
 
-import com.android.cts.util.MeasureRun;
-import com.android.cts.util.MeasureTime;
-import com.android.cts.util.ResultType;
-import com.android.cts.util.ResultUnit;
-import com.android.cts.util.ReportLog;
-import com.android.cts.util.Stat;
-import android.cts.util.SystemUtil;
-
-import android.content.Context;
-import android.util.Log;
-
 public class FileUtil {
     private static final String TAG = "FileUtil";
     private static final Random mRandom = new Random(0);
@@ -301,15 +301,15 @@
             }
         });
         randomFile.close();
-        double[] mbps = ReportLog.calcRatePerSecArray((double)fileSize / runsInOneGo / 1024 / 1024,
+        double[] mbps = Stat.calcRatePerSecArray((double)fileSize / runsInOneGo / 1024 / 1024,
                 times);
-        report.printArray("read throughput",
+        report.addValues("read throughput",
                 mbps, ResultType.HIGHER_BETTER, ResultUnit.MBPS);
         // This is just the amount of IO returned from kernel. So this is performance neutral.
-        report.printArray("read amount", rdAmount, ResultType.NEUTRAL, ResultUnit.BYTE);
+        report.addValues("read amount", rdAmount, ResultType.NEUTRAL, ResultUnit.BYTE);
         Stat.StatResult stat = Stat.getStat(mbps);
 
-        report.printSummary("read throughput", stat.mAverage, ResultType.HIGHER_BETTER,
+        report.setSummary("read throughput", stat.mAverage, ResultType.HIGHER_BETTER,
                 ResultUnit.MBPS);
     }
 
@@ -354,15 +354,15 @@
             }
         });
         randomFile.close();
-        double[] mbps = ReportLog.calcRatePerSecArray((double)fileSize / runsInOneGo / 1024 / 1024,
+        double[] mbps = Stat.calcRatePerSecArray((double)fileSize / runsInOneGo / 1024 / 1024,
                 times);
-        report.printArray("write throughput",
+        report.addValues("write throughput",
                 mbps, ResultType.HIGHER_BETTER, ResultUnit.MBPS);
-        report.printArray("write amount", wrAmount, ResultType.NEUTRAL,
+        report.addValues("write amount", wrAmount, ResultType.NEUTRAL,
                 ResultUnit.BYTE);
         Stat.StatResult stat = Stat.getStat(mbps);
 
-        report.printSummary("write throughput", stat.mAverage, ResultType.HIGHER_BETTER,
+        report.setSummary("write throughput", stat.mAverage, ResultType.HIGHER_BETTER,
                 ResultUnit.MBPS);
     }
 
@@ -395,14 +395,17 @@
                 }
             });
             randomFile.close();
-            double[] mbps = ReportLog.calcRatePerSecArray((double)bufferSize / 1024 / 1024,
+            double[] mbps = Stat.calcRatePerSecArray((double)bufferSize / 1024 / 1024,
                     times);
-            report.printArray(i + "-th round throughput",
+            report.addValues(i + "-th round throughput",
                     mbps, ResultType.HIGHER_BETTER, ResultUnit.MBPS);
-            ReportLog.copyArray(mbps, mbpsAll, i * numberRepeatInOneRun);
+            int offset = i * numberRepeatInOneRun;
+            for (int j = 0; j < mbps.length; j++) {
+                mbpsAll[offset + j] = mbps[j];
+            }
         }
         Stat.StatResult stat = Stat.getStat(mbpsAll);
-        report.printSummary("update throughput", stat.mAverage, ResultType.HIGHER_BETTER,
+        report.setSummary("update throughput", stat.mAverage, ResultType.HIGHER_BETTER,
                 ResultUnit.MBPS);
     }
 }
diff --git a/suite/cts/deviceTests/filesystemperf/src/com/android/cts/filesystemperf/RandomRWTest.java b/suite/cts/deviceTests/filesystemperf/src/com/android/cts/filesystemperf/RandomRWTest.java
index 6ad927b..08c1c77 100644
--- a/suite/cts/deviceTests/filesystemperf/src/com/android/cts/filesystemperf/RandomRWTest.java
+++ b/suite/cts/deviceTests/filesystemperf/src/com/android/cts/filesystemperf/RandomRWTest.java
@@ -17,6 +17,8 @@
 package com.android.cts.filesystemperf;
 
 import android.cts.util.CtsAndroidTestCase;
+
+import com.android.compatibility.common.util.DeviceReportLog;
 import com.android.cts.util.TimeoutReq;
 
 public class RandomRWTest extends CtsAndroidTestCase {
@@ -37,8 +39,10 @@
         if (fileSize == 0) { // not enough space, give up
             return;
         }
-        FileUtil.doRandomReadTest(getContext(), DIR_RANDOM_RD, getReportLog(), fileSize,
+        DeviceReportLog report = new DeviceReportLog();
+        FileUtil.doRandomReadTest(getContext(), DIR_RANDOM_RD, report, fileSize,
                 READ_BUFFER_SIZE);
+        report.submit(getInstrumentation());
     }
 
     // It is taking too long in some device, and thus cannot run multiple times
@@ -46,7 +50,9 @@
     public void testRandomUpdate() throws Exception {
         final int WRITE_BUFFER_SIZE = 4 * 1024;
         final long fileSize = 256 * 1024 * 1024;
-        FileUtil.doRandomWriteTest(getContext(), DIR_RANDOM_WR, getReportLog(), fileSize,
+        DeviceReportLog report = new DeviceReportLog();
+        FileUtil.doRandomWriteTest(getContext(), DIR_RANDOM_WR, report, fileSize,
                 WRITE_BUFFER_SIZE);
+        report.submit(getInstrumentation());
     }
 }
diff --git a/suite/cts/deviceTests/filesystemperf/src/com/android/cts/filesystemperf/SequentialRWTest.java b/suite/cts/deviceTests/filesystemperf/src/com/android/cts/filesystemperf/SequentialRWTest.java
index d369ce8..6cf41f5 100644
--- a/suite/cts/deviceTests/filesystemperf/src/com/android/cts/filesystemperf/SequentialRWTest.java
+++ b/suite/cts/deviceTests/filesystemperf/src/com/android/cts/filesystemperf/SequentialRWTest.java
@@ -17,12 +17,13 @@
 package com.android.cts.filesystemperf;
 
 import android.cts.util.CtsAndroidTestCase;
-import com.android.cts.util.MeasureRun;
-import com.android.cts.util.MeasureTime;
-import com.android.cts.util.ResultType;
-import com.android.cts.util.ResultUnit;
-import com.android.cts.util.ReportLog;
-import com.android.cts.util.Stat;
+
+import com.android.compatibility.common.util.DeviceReportLog;
+import com.android.compatibility.common.util.MeasureRun;
+import com.android.compatibility.common.util.MeasureTime;
+import com.android.compatibility.common.util.ResultType;
+import com.android.compatibility.common.util.ResultUnit;
+import com.android.compatibility.common.util.Stat;
 import com.android.cts.util.TimeoutReq;
 
 import java.io.File;
@@ -50,8 +51,8 @@
             return;
         }
         final int numberOfFiles =(int)(fileSize / BUFFER_SIZE);
-        getReportLog().printValue("files", numberOfFiles, ResultType.NEUTRAL,
-                ResultUnit.COUNT);
+        DeviceReportLog report = new DeviceReportLog();
+        report.addValue("files", numberOfFiles, ResultType.NEUTRAL, ResultUnit.COUNT);
         final byte[] data = FileUtil.generateRandomData(BUFFER_SIZE);
         final File[] files = FileUtil.createNewFiles(getContext(), DIR_SEQ_WR,
                 numberOfFiles);
@@ -64,14 +65,15 @@
                 FileUtil.writeFile(files[i], data, false);
             }
         });
-        double[] mbps = ReportLog.calcRatePerSecArray((double)BUFFER_SIZE / 1024 / 1024, times);
-        getReportLog().printArray("write throughput",
+        double[] mbps = Stat.calcRatePerSecArray((double)BUFFER_SIZE / 1024 / 1024, times);
+        report.addValues("write throughput",
                 mbps, ResultType.HIGHER_BETTER, ResultUnit.MBPS);
-        getReportLog().printArray("write amount", wrAmount, ResultType.NEUTRAL,
+        report.addValues("write amount", wrAmount, ResultType.NEUTRAL,
                 ResultUnit.BYTE);
         Stat.StatResult stat = Stat.getStat(mbps);
-        getReportLog().printSummary("write throughput", stat.mAverage, ResultType.HIGHER_BETTER,
+        report.setSummary("write throughput", stat.mAverage, ResultType.HIGHER_BETTER,
                 ResultUnit.MBPS);
+        report.submit(getInstrumentation());
     }
 
     @TimeoutReq(minutes = 60)
@@ -81,8 +83,10 @@
             return;
         }
         final int NUMBER_REPETITION = 6;
-        FileUtil.doSequentialUpdateTest(getContext(), DIR_SEQ_UPDATE, getReportLog(), fileSize,
+        DeviceReportLog report = new DeviceReportLog();
+        FileUtil.doSequentialUpdateTest(getContext(), DIR_SEQ_UPDATE, report, fileSize,
                 BUFFER_SIZE, NUMBER_REPETITION);
+        report.submit(getInstrumentation());
     }
 
     @TimeoutReq(minutes = 30)
@@ -95,8 +99,9 @@
         final File file = FileUtil.createNewFilledFile(getContext(),
                 DIR_SEQ_RD, fileSize);
         long finish = System.currentTimeMillis();
-        getReportLog().printValue("write throughput for test file of length " + fileSize,
-                ReportLog.calcRatePerSec((double)fileSize / 1024 / 1024, finish - start),
+        DeviceReportLog report = new DeviceReportLog();
+        report.addValue("write throughput for test file of length " + fileSize,
+                Stat.calcRatePerSec((double)fileSize / 1024 / 1024, finish - start),
                 ResultType.HIGHER_BETTER, ResultUnit.MBPS);
 
         final int NUMBER_READ = 10;
@@ -115,11 +120,12 @@
                 in.close();
             }
         });
-        double[] mbps = ReportLog.calcRatePerSecArray((double)fileSize / 1024 / 1024, times);
-        getReportLog().printArray("read throughput",
+        double[] mbps = Stat.calcRatePerSecArray((double)fileSize / 1024 / 1024, times);
+        report.addValues("read throughput",
                 mbps, ResultType.HIGHER_BETTER, ResultUnit.MBPS);
         Stat.StatResult stat = Stat.getStat(mbps);
-        getReportLog().printSummary("read throughput", stat.mAverage, ResultType.HIGHER_BETTER,
+        report.setSummary("read throughput", stat.mAverage, ResultType.HIGHER_BETTER,
                 ResultUnit.MBPS);
+        report.submit(getInstrumentation());
     }
 }
diff --git a/suite/cts/deviceTests/jank2/Android.mk b/suite/cts/deviceTests/jank2/Android.mk
index 346297e..c719693 100644
--- a/suite/cts/deviceTests/jank2/Android.mk
+++ b/suite/cts/deviceTests/jank2/Android.mk
@@ -24,6 +24,8 @@
 
 LOCAL_PACKAGE_NAME := CtsJankTestCases
 
-LOCAL_STATIC_JAVA_LIBRARIES := ctsdeviceutil ctstestrunner ub-uiautomator ub-janktesthelper
+LOCAL_STATIC_JAVA_LIBRARIES := ctsdeviceutil compatibility-device-util ctstestrunner ub-uiautomator ub-janktesthelper
+
+LOCAL_CTS_MODULE_CONFIG := $(LOCAL_PATH)/Old$(CTS_MODULE_TEST_CONFIG)
 
 include $(BUILD_CTS_PACKAGE)
diff --git a/suite/cts/deviceTests/jank2/AndroidTest.xml b/suite/cts/deviceTests/jank2/OldAndroidTest.xml
similarity index 100%
rename from suite/cts/deviceTests/jank2/AndroidTest.xml
rename to suite/cts/deviceTests/jank2/OldAndroidTest.xml
diff --git a/suite/cts/deviceTests/jank2/src/android/cts/jank/CtsJankTestBase.java b/suite/cts/deviceTests/jank2/src/android/cts/jank/CtsJankTestBase.java
index cb5c122..c936a8b 100644
--- a/suite/cts/deviceTests/jank2/src/android/cts/jank/CtsJankTestBase.java
+++ b/suite/cts/deviceTests/jank2/src/android/cts/jank/CtsJankTestBase.java
@@ -16,14 +16,14 @@
 
 package android.cts.jank;
 
-import android.cts.util.DeviceReportLog;
 import android.os.Bundle;
 import android.support.test.jank.JankTestBase;
 import android.support.test.jank.WindowContentFrameStatsMonitor;
 import android.support.test.uiautomator.UiDevice;
 
-import com.android.cts.util.ResultType;
-import com.android.cts.util.ResultUnit;
+import com.android.compatibility.common.util.DeviceReportLog;
+import com.android.compatibility.common.util.ResultType;
+import com.android.compatibility.common.util.ResultUnit;
 
 public abstract class CtsJankTestBase extends JankTestBase {
 
@@ -33,16 +33,16 @@
     @Override
     public void afterTest(Bundle metrics) {
         String source = String.format("%s#%s", getClass().getCanonicalName(), getName());
-        mLog.printValue(source, WindowContentFrameStatsMonitor.KEY_AVG_FPS,
+        mLog.addValue(source, WindowContentFrameStatsMonitor.KEY_AVG_FPS,
                 metrics.getDouble(WindowContentFrameStatsMonitor.KEY_AVG_FPS),
                 ResultType.HIGHER_BETTER, ResultUnit.FPS);
-        mLog.printValue(source, WindowContentFrameStatsMonitor.KEY_AVG_LONGEST_FRAME,
+        mLog.addValue(source, WindowContentFrameStatsMonitor.KEY_AVG_LONGEST_FRAME,
                 metrics.getDouble(WindowContentFrameStatsMonitor.KEY_AVG_LONGEST_FRAME),
                 ResultType.LOWER_BETTER, ResultUnit.MS);
-        mLog.printValue(source, WindowContentFrameStatsMonitor.KEY_MAX_NUM_JANKY,
+        mLog.addValue(source, WindowContentFrameStatsMonitor.KEY_MAX_NUM_JANKY,
                 metrics.getInt(WindowContentFrameStatsMonitor.KEY_MAX_NUM_JANKY),
                 ResultType.LOWER_BETTER, ResultUnit.COUNT);
-        mLog.printSummary(WindowContentFrameStatsMonitor.KEY_AVG_NUM_JANKY,
+        mLog.setSummary(WindowContentFrameStatsMonitor.KEY_AVG_NUM_JANKY,
                 metrics.getDouble(WindowContentFrameStatsMonitor.KEY_AVG_NUM_JANKY),
                 ResultType.LOWER_BETTER, ResultUnit.COUNT);
     }
@@ -58,7 +58,7 @@
 
     @Override
     protected void tearDown() throws Exception {
-        mLog.deliverReportToHost(getInstrumentation());
+        mLog.submit(getInstrumentation());
         // restore device orientation
         mDevice.unfreezeRotation();
         super.tearDown();
diff --git a/suite/cts/deviceTests/opengl/Android.mk b/suite/cts/deviceTests/opengl/Android.mk
index 0708efb..3aada1b 100644
--- a/suite/cts/deviceTests/opengl/Android.mk
+++ b/suite/cts/deviceTests/opengl/Android.mk
@@ -21,7 +21,7 @@
 # Include both the 32 and 64 bit versions
 LOCAL_MULTILIB := both
 
-LOCAL_STATIC_JAVA_LIBRARIES := ctsdeviceutil ctstestrunner
+LOCAL_STATIC_JAVA_LIBRARIES := ctsdeviceutil compatibility-device-util ctstestrunner
 
 LOCAL_JNI_SHARED_LIBRARIES := libctsopengl_jni
 
diff --git a/suite/cts/deviceTests/opengl/src/com/android/cts/opengl/primitive/GLPrimitiveBenchmark.java b/suite/cts/deviceTests/opengl/src/com/android/cts/opengl/primitive/GLPrimitiveBenchmark.java
index c177129..171776b 100644
--- a/suite/cts/deviceTests/opengl/src/com/android/cts/opengl/primitive/GLPrimitiveBenchmark.java
+++ b/suite/cts/deviceTests/opengl/src/com/android/cts/opengl/primitive/GLPrimitiveBenchmark.java
@@ -13,18 +13,19 @@
  */
 package com.android.cts.opengl.primitive;
 
-import com.android.cts.opengl.GLActivityIntentKeys;
-import android.cts.util.CtsActivityInstrumentationTestCase2;
-import com.android.cts.util.ResultType;
-import com.android.cts.util.ResultUnit;
-
 import android.content.Intent;
+import android.test.ActivityInstrumentationTestCase2;
+
+import com.android.compatibility.common.util.DeviceReportLog;
+import com.android.compatibility.common.util.ResultType;
+import com.android.compatibility.common.util.ResultUnit;
+import com.android.cts.opengl.GLActivityIntentKeys;
 import com.android.cts.util.TimeoutReq;
 
 /**
  * Runs the Primitive OpenGL ES 2.0 Benchmarks.
  */
-public class GLPrimitiveBenchmark extends CtsActivityInstrumentationTestCase2<GLPrimitiveActivity> {
+public class GLPrimitiveBenchmark extends ActivityInstrumentationTestCase2<GLPrimitiveActivity> {
 
     private static final int NUM_FRAMES = 100;
     private static final int NUM_ITERATIONS = 8;
@@ -131,11 +132,9 @@
 
             // TODO: maybe standard deviation / RMSE will be useful?
 
-            getReportLog().printArray(
-                    "Fps Values", fpsValues, ResultType.HIGHER_BETTER, ResultUnit.FPS);
-            getReportLog().printSummary(
-                    "Average Frames Per Second", score, ResultType.HIGHER_BETTER,
-                    ResultUnit.SCORE);
+            DeviceReportLog report = new DeviceReportLog();
+            report.setSummary("Average FPS", score, ResultType.HIGHER_BETTER, ResultUnit.SCORE);
+            report.submit(getInstrumentation());
         }
     }
 }
diff --git a/suite/cts/deviceTests/opengl/src/com/android/cts/opengl/reference/GLReferenceBenchmark.java b/suite/cts/deviceTests/opengl/src/com/android/cts/opengl/reference/GLReferenceBenchmark.java
index fdea916..2d2238a 100644
--- a/suite/cts/deviceTests/opengl/src/com/android/cts/opengl/reference/GLReferenceBenchmark.java
+++ b/suite/cts/deviceTests/opengl/src/com/android/cts/opengl/reference/GLReferenceBenchmark.java
@@ -13,11 +13,13 @@
  */
 package com.android.cts.opengl.reference;
 
+import com.android.compatibility.common.util.DeviceReportLog;
+import com.android.compatibility.common.util.ResultType;
+import com.android.compatibility.common.util.ResultUnit;
 import com.android.cts.opengl.GLActivityIntentKeys;
 
-import android.cts.util.CtsActivityInstrumentationTestCase2;
-import com.android.cts.util.ResultType;
-import com.android.cts.util.ResultUnit;
+import android.test.ActivityInstrumentationTestCase2;
+
 import com.android.cts.util.TimeoutReq;
 
 import android.content.Intent;
@@ -25,7 +27,7 @@
 /**
  * Runs the Reference OpenGL ES 2.0 Benchmark.
  */
-public class GLReferenceBenchmark extends CtsActivityInstrumentationTestCase2<GLReferenceActivity> {
+public class GLReferenceBenchmark extends ActivityInstrumentationTestCase2<GLReferenceActivity> {
 
     private static final int NUM_FRAMES_PER_SCENE = 500;
     private static final int NUM_SCENES = 2;
@@ -65,22 +67,17 @@
             double updateAverage = updateSum / NUM_FRAMES;
             double renderAverage = renderSum / NUM_FRAMES;
 
-            getReportLog().printArray(
-                    "Set Up Times", setUpTimes, ResultType.LOWER_BETTER, ResultUnit.MS);
-            getReportLog().printArray(
-                    "Update Times", updateTimes, ResultType.LOWER_BETTER, ResultUnit.MS);
-            getReportLog().printValue(
-                    "Update Time Average", updateAverage, ResultType.LOWER_BETTER,
+            DeviceReportLog report = new DeviceReportLog();
+            report.addValues("Set Up Times", setUpTimes, ResultType.LOWER_BETTER, ResultUnit.MS);
+            report.addValue("Update Time Average", updateAverage, ResultType.LOWER_BETTER,
                     ResultUnit.MS);
-            getReportLog().printArray(
-                    "Render Times", renderTimes, ResultType.LOWER_BETTER, ResultUnit.MS);
-            getReportLog().printValue(
-                    "Render Time Average", renderAverage, ResultType.LOWER_BETTER,
+            report.addValue("Render Time Average", renderAverage, ResultType.LOWER_BETTER,
                     ResultUnit.MS);
-            totalTime = setUpTimes[0] + setUpTimes[1] + setUpTimes[2] +
-                    setUpTimes[3] + updateAverage + renderAverage;
-            getReportLog().printSummary(
-                    "Total Time", totalTime, ResultType.LOWER_BETTER, ResultUnit.MS);
+            totalTime = setUpTimes[0] + setUpTimes[1] + setUpTimes[2] + setUpTimes[3] +
+                    updateAverage + renderAverage;
+            report.setSummary("Total Time Average", totalTime, ResultType.LOWER_BETTER,
+                    ResultUnit.MS);
+            report.submit(getInstrumentation());
         }
     }
 }
diff --git a/suite/cts/deviceTests/simplecpu/Android.mk b/suite/cts/deviceTests/simplecpu/Android.mk
index 17e7506..0a0be45 100644
--- a/suite/cts/deviceTests/simplecpu/Android.mk
+++ b/suite/cts/deviceTests/simplecpu/Android.mk
@@ -21,7 +21,7 @@
 # Include both the 32 and 64 bit versions
 LOCAL_MULTILIB := both
 
-LOCAL_STATIC_JAVA_LIBRARIES := ctsdeviceutil ctstestrunner
+LOCAL_STATIC_JAVA_LIBRARIES := ctsdeviceutil compatibility-device-util ctstestrunner
 
 LOCAL_JNI_SHARED_LIBRARIES := libctscpu_jni
 
diff --git a/suite/cts/deviceTests/simplecpu/src/com/android/cts/simplecpu/SimpleCpuTest.java b/suite/cts/deviceTests/simplecpu/src/com/android/cts/simplecpu/SimpleCpuTest.java
index ce0feac..520ae79 100644
--- a/suite/cts/deviceTests/simplecpu/src/com/android/cts/simplecpu/SimpleCpuTest.java
+++ b/suite/cts/deviceTests/simplecpu/src/com/android/cts/simplecpu/SimpleCpuTest.java
@@ -16,12 +16,13 @@
 
 package com.android.cts.simplecpu;
 
+import android.cts.util.CtsAndroidTestCase;
 import android.util.Log;
 
-import com.android.cts.util.ResultType;
-import com.android.cts.util.ResultUnit;
-import android.cts.util.CtsAndroidTestCase;
-import com.android.cts.util.Stat;
+import com.android.compatibility.common.util.DeviceReportLog;
+import com.android.compatibility.common.util.ResultType;
+import com.android.compatibility.common.util.ResultUnit;
+import com.android.compatibility.common.util.Stat;
 import com.android.cts.util.TimeoutReq;
 
 /**
@@ -99,14 +100,14 @@
         for (int i = 0; i < numberRepeat; i++) {
             result[i] = CpuNative.runSort(arrayLength, numberRepeatInEachCall);
         }
-        getReportLog().printArray("sorting time", result, ResultType.LOWER_BETTER,
-                ResultUnit.MS);
+        DeviceReportLog report = new DeviceReportLog();
+        report.addValues("sorting time", result, ResultType.LOWER_BETTER, ResultUnit.MS);
         Stat.StatResult stat = Stat.getStatWithOutlierRejection(result, OUTLIER_THRESHOLD);
         if (stat.mDataCount != result.length) {
             Log.w(TAG, "rejecting " + (result.length - stat.mDataCount) + " outliers");
         }
-        getReportLog().printSummary("sorting time", stat.mAverage, ResultType.LOWER_BETTER,
-                ResultUnit.MS);
+        report.setSummary("sorting time", stat.mAverage, ResultType.LOWER_BETTER, ResultUnit.MS);
+        report.submit(getInstrumentation());
     }
 
     /**
@@ -121,14 +122,16 @@
         for (int i = 0; i < numberRepeat; i++) {
             result[i] = CpuNative.runMatrixMultiplication(n, numberRepeatInEachCall);
         }
-        getReportLog().printArray("matrix mutiplication time", result, ResultType.LOWER_BETTER,
+        DeviceReportLog report = new DeviceReportLog();
+        report.addValues("matrix mutiplication time", result, ResultType.LOWER_BETTER,
                 ResultUnit.MS);
         Stat.StatResult stat = Stat.getStatWithOutlierRejection(result, OUTLIER_THRESHOLD);
         if (stat.mDataCount != result.length) {
             Log.w(TAG, "rejecting " + (result.length - stat.mDataCount) + " outliers");
         }
-        getReportLog().printSummary("matrix mutiplication time", stat.mAverage,
+        report.setSummary("matrix mutiplication time", stat.mAverage,
                 ResultType.LOWER_BETTER, ResultUnit.MS);
+        report.submit(getInstrumentation());
     }
 
 }
diff --git a/suite/cts/deviceTests/tvproviderperf/Android.mk b/suite/cts/deviceTests/tvproviderperf/Android.mk
index e268955..8e865fb2 100644
--- a/suite/cts/deviceTests/tvproviderperf/Android.mk
+++ b/suite/cts/deviceTests/tvproviderperf/Android.mk
@@ -18,7 +18,7 @@
 # don't include this package in any target
 LOCAL_MODULE_TAGS := optional
 
-LOCAL_STATIC_JAVA_LIBRARIES := ctsdeviceutil ctstestrunner
+LOCAL_STATIC_JAVA_LIBRARIES := ctsdeviceutil compatibility-device-util ctstestrunner
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
diff --git a/suite/cts/deviceTests/tvproviderperf/src/com/android/cts/tvproviderperf/TvProviderPerfTest.java b/suite/cts/deviceTests/tvproviderperf/src/com/android/cts/tvproviderperf/TvProviderPerfTest.java
index f9daa3c..cb56755 100644
--- a/suite/cts/deviceTests/tvproviderperf/src/com/android/cts/tvproviderperf/TvProviderPerfTest.java
+++ b/suite/cts/deviceTests/tvproviderperf/src/com/android/cts/tvproviderperf/TvProviderPerfTest.java
@@ -24,21 +24,21 @@
 import android.content.ContentValues;
 import android.content.OperationApplicationException;
 import android.content.pm.PackageManager;
-import android.database.Cursor;
 import android.cts.util.CtsAndroidTestCase;
+import android.database.Cursor;
 import android.media.tv.TvContract;
 import android.media.tv.TvContract.Channels;
 import android.media.tv.TvContract.Programs;
 import android.net.Uri;
 import android.os.RemoteException;
 
-import com.android.cts.util.MeasureRun;
-import com.android.cts.util.MeasureTime;
-import com.android.cts.util.ResultType;
-import com.android.cts.util.ResultUnit;
-import com.android.cts.util.ReportLog;
+import com.android.compatibility.common.util.DeviceReportLog;
+import com.android.compatibility.common.util.MeasureRun;
+import com.android.compatibility.common.util.MeasureTime;
+import com.android.compatibility.common.util.ResultType;
+import com.android.compatibility.common.util.ResultUnit;
+import com.android.compatibility.common.util.Stat;
 import com.android.cts.util.TimeoutReq;
-import com.android.cts.util.Stat;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -106,7 +106,8 @@
                 }
             }
         });
-        getReportLog().printArray("Elapsed time for insert: ",
+        DeviceReportLog report = new DeviceReportLog();
+        report.addValues("Elapsed time for insert: ",
                 applyBatchTimes, ResultType.LOWER_BETTER, ResultUnit.MS);
         averages[0] = Stat.getAverage(applyBatchTimes);
 
@@ -134,7 +135,7 @@
                 }
             });
         }
-        getReportLog().printArray("Elapsed time for update: ",
+        report.addValues("Elapsed time for update: ",
                 applyBatchTimes, ResultType.LOWER_BETTER, ResultUnit.MS);
         averages[1] = Stat.getAverage(applyBatchTimes);
 
@@ -150,7 +151,7 @@
                 }
             }
         });
-        getReportLog().printArray("Elapsed time for query (channels): ",
+        report.addValues("Elapsed time for query (channels): ",
                 applyBatchTimes, ResultType.LOWER_BETTER, ResultUnit.MS);
         averages[2] = Stat.getAverage(applyBatchTimes);
 
@@ -170,7 +171,7 @@
                 }
             });
         }
-        getReportLog().printArray("Elapsed time for query (a channel): ",
+        report.addValues("Elapsed time for query (a channel): ",
                 applyBatchTimes, ResultType.LOWER_BETTER, ResultUnit.MS);
         averages[3] = Stat.getAverage(applyBatchTimes);
 
@@ -181,13 +182,14 @@
                 mContentResolver.delete(TvContract.buildChannelsUriForInput(mInputId), null, null);
             }
         });
-        getReportLog().printArray("Elapsed time for delete: ",
+        report.addValues("Elapsed time for delete: ",
                 applyBatchTimes, ResultType.LOWER_BETTER, ResultUnit.MS);
         averages[4] = Stat.getAverage(applyBatchTimes);
 
-        getReportLog().printArray("Average elapsed time for insert, update, query (channels), "
+        report.addValues("Average elapsed time for insert, update, query (channels), "
                 + "query (a channel), delete: ",
                 averages, ResultType.LOWER_BETTER, ResultUnit.MS);
+        report.submit(getInstrumentation());
     }
 
     @TimeoutReq(minutes = 12)
@@ -243,7 +245,8 @@
                 }
             }
         });
-        getReportLog().printArray("Elapsed time for insert: ",
+        DeviceReportLog report = new DeviceReportLog();
+        report.addValues("Elapsed time for insert: ",
                 applyBatchTimes, ResultType.LOWER_BETTER, ResultUnit.MS);
         averages[0] = Stat.getAverage(applyBatchTimes);
 
@@ -278,7 +281,7 @@
                 }
             }
         });
-        getReportLog().printArray("Elapsed time for update: ",
+        report.addValues("Elapsed time for update: ",
                 applyBatchTimes, ResultType.LOWER_BETTER, ResultUnit.MS);
         averages[1] = Stat.getAverage(applyBatchTimes);
 
@@ -294,7 +297,7 @@
                 }
             }
         });
-        getReportLog().printArray("Elapsed time for query (programs): ",
+        report.addValues("Elapsed time for query (programs): ",
                 applyBatchTimes, ResultType.LOWER_BETTER, ResultUnit.MS);
         averages[2] = Stat.getAverage(applyBatchTimes);
 
@@ -314,7 +317,7 @@
                 }
             }
         });
-        getReportLog().printArray("Elapsed time for query (programs with selection): ",
+        report.addValues("Elapsed time for query (programs with selection): ",
                 applyBatchTimes, ResultType.LOWER_BETTER, ResultUnit.MS);
         averages[3] = Stat.getAverage(applyBatchTimes);
 
@@ -334,7 +337,7 @@
                 }
             });
         }
-        getReportLog().printArray("Elapsed time for query (a program): ",
+        report.addValues("Elapsed time for query (a program): ",
                 applyBatchTimes, ResultType.LOWER_BETTER, ResultUnit.MS);
         averages[4] = Stat.getAverage(applyBatchTimes);
 
@@ -351,7 +354,7 @@
                         null, null);
             }
         });
-        getReportLog().printArray("Elapsed time for delete programs: ",
+        report.addValues("Elapsed time for delete programs: ",
                 applyBatchTimes, ResultType.LOWER_BETTER, ResultUnit.MS);
         averages[5] = Stat.getAverage(applyBatchTimes);
 
@@ -363,13 +366,14 @@
                 mContentResolver.delete(channelUri, null, null);
             }
         });
-        getReportLog().printArray("Elapsed time for delete channels: ",
+        report.addValues("Elapsed time for delete channels: ",
                 applyBatchTimes, ResultType.LOWER_BETTER, ResultUnit.MS);
         averages[6] = Stat.getAverage(applyBatchTimes);
 
-        getReportLog().printArray("Average elapsed time for insert, update, query (programs), "
+        report.addValues("Average elapsed time for insert, update, query (programs), "
                 + "query (programs with selection), query (a channel), delete (channels), "
                 + "delete (programs): ",
                 averages, ResultType.LOWER_BETTER, ResultUnit.MS);
+        report.submit(getInstrumentation());
     }
 }
diff --git a/suite/cts/deviceTests/ui/Android.mk b/suite/cts/deviceTests/ui/Android.mk
index ee52172..5b5c8f9 100644
--- a/suite/cts/deviceTests/ui/Android.mk
+++ b/suite/cts/deviceTests/ui/Android.mk
@@ -20,7 +20,7 @@
 
 LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
 
-LOCAL_STATIC_JAVA_LIBRARIES := ctsdeviceutil ctstestrunner
+LOCAL_STATIC_JAVA_LIBRARIES := ctsdeviceutil compatibility-device-util ctstestrunner
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
diff --git a/suite/cts/deviceTests/ui/src/com/android/cts/ui/ScrollingTest.java b/suite/cts/deviceTests/ui/src/com/android/cts/ui/ScrollingTest.java
index b5b40e4..7f8d025 100644
--- a/suite/cts/deviceTests/ui/src/com/android/cts/ui/ScrollingTest.java
+++ b/suite/cts/deviceTests/ui/src/com/android/cts/ui/ScrollingTest.java
@@ -15,17 +15,19 @@
  */
 package com.android.cts.ui;
 
-import com.android.cts.util.MeasureRun;
-import com.android.cts.util.MeasureTime;
-import com.android.cts.util.ResultType;
-import com.android.cts.util.ResultUnit;
-import android.cts.util.CtsActivityInstrumentationTestCase2;
-import com.android.cts.util.Stat;
+import android.test.ActivityInstrumentationTestCase2;
+
+import com.android.compatibility.common.util.DeviceReportLog;
+import com.android.compatibility.common.util.MeasureRun;
+import com.android.compatibility.common.util.MeasureTime;
+import com.android.compatibility.common.util.ResultType;
+import com.android.compatibility.common.util.ResultUnit;
+import com.android.compatibility.common.util.Stat;
 import com.android.cts.util.TimeoutReq;
 
 import java.io.IOException;
 
-public class ScrollingTest extends CtsActivityInstrumentationTestCase2<ScrollingActivity> {
+public class ScrollingTest extends ActivityInstrumentationTestCase2<ScrollingActivity> {
     private ScrollingActivity mActivity;
 
     public ScrollingTest() {
@@ -39,6 +41,7 @@
         getInstrumentation().waitForIdleSync();
         try {
             runTestOnUiThread(new Runnable() {
+                @Override
                 public void run() {
                 }
             });
@@ -66,10 +69,10 @@
                 assertTrue(activity.scrollToTop());
             }
         });
-        getReportLog().printArray("scrolling time", results, ResultType.LOWER_BETTER,
-                ResultUnit.MS);
+        DeviceReportLog report = new DeviceReportLog();
+        report.addValues("scrolling time", results, ResultType.LOWER_BETTER,ResultUnit.MS);
         Stat.StatResult stat = Stat.getStat(results);
-        getReportLog().printSummary("scrolling time", stat.mAverage, ResultType.LOWER_BETTER,
-                ResultUnit.MS);
+        report.setSummary("scrolling time", stat.mAverage, ResultType.LOWER_BETTER,ResultUnit.MS);
+        report.submit(getInstrumentation());
     }
 }
diff --git a/suite/cts/deviceTests/videoperf/Android.mk b/suite/cts/deviceTests/videoperf/Android.mk
index a393683..56d4c2b 100644
--- a/suite/cts/deviceTests/videoperf/Android.mk
+++ b/suite/cts/deviceTests/videoperf/Android.mk
@@ -23,7 +23,7 @@
 # include both the 32 and 64 bit versions
 LOCAL_MULTILIB := both
 
-LOCAL_STATIC_JAVA_LIBRARIES := ctsmediautil ctsdeviceutil ctstestrunner
+LOCAL_STATIC_JAVA_LIBRARIES := ctsmediautil ctsdeviceutil compatibility-device-util ctstestrunner
 
 LOCAL_JNI_SHARED_LIBRARIES := libctsmediacodec_jni
 
diff --git a/suite/cts/deviceTests/videoperf/src/com/android/cts/videoperf/VideoEncoderDecoderTest.java b/suite/cts/deviceTests/videoperf/src/com/android/cts/videoperf/VideoEncoderDecoderTest.java
index 62f37c5..d0af941 100644
--- a/suite/cts/deviceTests/videoperf/src/com/android/cts/videoperf/VideoEncoderDecoderTest.java
+++ b/suite/cts/deviceTests/videoperf/src/com/android/cts/videoperf/VideoEncoderDecoderTest.java
@@ -20,8 +20,6 @@
 import android.cts.util.DeviceReportLog;
 import android.graphics.ImageFormat;
 import android.graphics.Point;
-import android.media.cts.CodecImage;
-import android.media.cts.CodecUtils;
 import android.media.Image;
 import android.media.Image.Plane;
 import android.media.MediaCodec;
@@ -30,25 +28,24 @@
 import android.media.MediaCodecInfo.CodecCapabilities;
 import android.media.MediaCodecList;
 import android.media.MediaFormat;
+import android.media.cts.CodecImage;
+import android.media.cts.CodecUtils;
 import android.util.Log;
 import android.util.Pair;
 import android.util.Range;
-import android.util.Size;
 
 import android.cts.util.CtsAndroidTestCase;
 import com.android.cts.util.ResultType;
 import com.android.cts.util.ResultUnit;
-import com.android.cts.util.Stat;
+import com.android.compatibility.common.util.Stat;
 import com.android.cts.util.TimeoutReq;
 
 import java.io.IOException;
 import java.nio.ByteBuffer;
-import java.lang.System;
-import java.util.Arrays;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.LinkedList;
 import java.util.Random;
-import java.util.Vector;
 
 /**
  * This tries to test video encoder / decoder performance by running encoding / decoding
diff --git a/suite/cts/hostTests/jank/src/com/android/cts/jank/CtsHostJankTest.java b/suite/cts/hostTests/jank/src/com/android/cts/jank/CtsHostJankTest.java
index e196bfb..178fa4d 100644
--- a/suite/cts/hostTests/jank/src/com/android/cts/jank/CtsHostJankTest.java
+++ b/suite/cts/hostTests/jank/src/com/android/cts/jank/CtsHostJankTest.java
@@ -13,10 +13,10 @@
  */
 package com.android.cts.jank;
 
+import com.android.compatibility.common.util.MetricsReportLog;
+import com.android.compatibility.common.util.ResultType;
+import com.android.compatibility.common.util.ResultUnit;
 import com.android.cts.tradefed.build.CtsBuildHelper;
-import com.android.cts.tradefed.util.HostReportLog;
-import com.android.cts.util.ResultType;
-import com.android.cts.util.ResultUnit;
 import com.android.ddmlib.IShellOutputReceiver;
 import com.android.ddmlib.Log;
 import com.android.ddmlib.Log.LogLevel;
@@ -131,17 +131,17 @@
         double avgMaxAccFrames = results.get("average of max accumulated frames");
 
         // Create and deliver the report.
-        HostReportLog report = new HostReportLog(mDevice.getSerialNumber(), mAbi.getName(),
+        MetricsReportLog report = new MetricsReportLog(mDevice.getSerialNumber(), mAbi.getName(),
                 mHostTestClass + "#" + testName);
-        report.printValue(
+        report.addValue(
                 "Average Frame Rate", avgFrameRate, ResultType.HIGHER_BETTER, ResultUnit.COUNT);
-        report.printValue("Average of Maximum Accumulated Frames", avgMaxAccFrames,
+        report.addValue("Average of Maximum Accumulated Frames", avgMaxAccFrames,
                 ResultType.LOWER_BETTER, ResultUnit.COUNT);
-        report.printValue(
+        report.addValue(
                 "Maximum Number of Janks", maxNumJanks, ResultType.LOWER_BETTER, ResultUnit.COUNT);
-        report.printSummary(
+        report.setSummary(
                 "Average Number of Janks", avgNumJanks, ResultType.LOWER_BETTER, ResultUnit.SCORE);
-        report.deliverReportToHost();
+        report.submit();
     }
 
 }
diff --git a/suite/cts/hostTests/jank/src/com/android/cts/jank/opengl/CtsHostJankOpenGl.java b/suite/cts/hostTests/jank/src/com/android/cts/jank/opengl/CtsHostJankOpenGl.java
index 2942ecf..8acf169 100644
--- a/suite/cts/hostTests/jank/src/com/android/cts/jank/opengl/CtsHostJankOpenGl.java
+++ b/suite/cts/hostTests/jank/src/com/android/cts/jank/opengl/CtsHostJankOpenGl.java
@@ -13,8 +13,8 @@
  */
 package com.android.cts.jank.opengl;
 
+import com.android.compatibility.common.util.AbiUtils;
 import com.android.cts.jank.CtsHostJankTest;
-import com.android.cts.util.AbiUtils;
 import java.io.File;
 
 public class CtsHostJankOpenGl extends CtsHostJankTest {
diff --git a/suite/cts/hostTests/uihost/control/Android.mk b/suite/cts/hostTests/uihost/control/Android.mk
index 4de9ae8..de5a6b5 100644
--- a/suite/cts/hostTests/uihost/control/Android.mk
+++ b/suite/cts/hostTests/uihost/control/Android.mk
@@ -20,7 +20,7 @@
 
 LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
 
-LOCAL_STATIC_JAVA_LIBRARIES := ctsdeviceutil ctstestrunner
+LOCAL_STATIC_JAVA_LIBRARIES := ctsdeviceutil compatibility-device-util ctstestrunner
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
diff --git a/suite/cts/hostTests/uihost/control/src/com/android/cts/taskswitching/control/TaskswitchingDeviceTest.java b/suite/cts/hostTests/uihost/control/src/com/android/cts/taskswitching/control/TaskswitchingDeviceTest.java
index bdb3132..4bda0bb 100644
--- a/suite/cts/hostTests/uihost/control/src/com/android/cts/taskswitching/control/TaskswitchingDeviceTest.java
+++ b/suite/cts/hostTests/uihost/control/src/com/android/cts/taskswitching/control/TaskswitchingDeviceTest.java
@@ -25,13 +25,14 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 
-import com.android.cts.util.MeasureRun;
-import com.android.cts.util.MeasureTime;
-import com.android.cts.util.ResultType;
-import com.android.cts.util.ResultUnit;
 import android.cts.util.CtsAndroidTestCase;
-import com.android.cts.util.Stat;
 
+import com.android.compatibility.common.util.DeviceReportLog;
+import com.android.compatibility.common.util.MeasureRun;
+import com.android.compatibility.common.util.MeasureTime;
+import com.android.compatibility.common.util.ResultType;
+import com.android.compatibility.common.util.ResultUnit;
+import com.android.compatibility.common.util.Stat;
 
 /**
  * Device test which actually launches two apps sequentially and
@@ -83,11 +84,12 @@
                 }
             }
         });
-        getReportLog().printArray("taskswitching time", results, ResultType.LOWER_BETTER,
-                ResultUnit.MS);
+        DeviceReportLog report = new DeviceReportLog();
+        report.addValues("taskswitching time", results, ResultType.LOWER_BETTER, ResultUnit.MS);
         Stat.StatResult stat = Stat.getStat(results);
-        getReportLog().printSummary("taskswitching time", stat.mAverage,
+        report.setSummary("taskswitching time", stat.mAverage,
                 ResultType.LOWER_BETTER, ResultUnit.MS);
+        report.submit(getInstrumentation());
     }
 
     private void startActivity(String packageName, String activityName) {
diff --git a/suite/cts/hostTests/uihost/src/com/android/cts/uihost/InstallTimeTest.java b/suite/cts/hostTests/uihost/src/com/android/cts/uihost/InstallTimeTest.java
index 75a2e92..4869e73 100644
--- a/suite/cts/hostTests/uihost/src/com/android/cts/uihost/InstallTimeTest.java
+++ b/suite/cts/hostTests/uihost/src/com/android/cts/uihost/InstallTimeTest.java
@@ -16,15 +16,14 @@
 
 package com.android.cts.uihost;
 
+import com.android.compatibility.common.util.AbiUtils;
+import com.android.compatibility.common.util.MeasureRun;
+import com.android.compatibility.common.util.MeasureTime;
+import com.android.compatibility.common.util.MetricsReportLog;
+import com.android.compatibility.common.util.ResultType;
+import com.android.compatibility.common.util.ResultUnit;
+import com.android.compatibility.common.util.Stat;
 import com.android.cts.tradefed.build.CtsBuildHelper;
-import com.android.cts.tradefed.util.HostReportLog;
-import com.android.cts.util.AbiUtils;
-import com.android.cts.util.MeasureRun;
-import com.android.cts.util.MeasureTime;
-import com.android.cts.util.ResultType;
-import com.android.cts.util.ResultUnit;
-import com.android.cts.util.ReportLog;
-import com.android.cts.util.Stat;
 import com.android.ddmlib.Log;
 import com.android.tradefed.build.IBuildInfo;
 import com.android.tradefed.device.ITestDevice;
@@ -35,7 +34,6 @@
 
 import java.io.File;
 
-
 /**
  * Test to measure installation time of a APK.
  */
@@ -73,8 +71,8 @@
     }
 
     public void testInstallTime() throws Exception {
-        HostReportLog report = new HostReportLog(mDevice.getSerialNumber(), mAbi.getName(),
-                ReportLog.getClassMethodNames());
+        MetricsReportLog report = new MetricsReportLog(mDevice.getSerialNumber(), mAbi.getName(),
+                String.format("%s#%s", getClass().getName(), "testInstallTime"));
         final int NUMBER_REPEAT = 10;
         final CtsBuildHelper build = mBuild;
         final ITestDevice device = mDevice;
@@ -90,15 +88,13 @@
                 device.installPackage(app, false, options);
             }
         });
-        report.printArray("install time", result, ResultType.LOWER_BETTER,
-                ResultUnit.MS);
+        report.addValues("install time", result, ResultType.LOWER_BETTER, ResultUnit.MS);
         Stat.StatResult stat = Stat.getStatWithOutlierRejection(result, OUTLIER_THRESHOLD);
         if (stat.mDataCount != result.length) {
             Log.w(TAG, "rejecting " + (result.length - stat.mDataCount) + " outliers");
         }
-        report.printSummary("install time", stat.mAverage, ResultType.LOWER_BETTER,
-                ResultUnit.MS);
-        report.deliverReportToHost();
+        report.setSummary("install time", stat.mAverage, ResultType.LOWER_BETTER, ResultUnit.MS);
+        report.submit();
     }
 
 }
diff --git a/suite/cts/hostTests/uihost/src/com/android/cts/uihost/TaskSwitchingTest.java b/suite/cts/hostTests/uihost/src/com/android/cts/uihost/TaskSwitchingTest.java
index 2d33436..cea5715 100644
--- a/suite/cts/hostTests/uihost/src/com/android/cts/uihost/TaskSwitchingTest.java
+++ b/suite/cts/hostTests/uihost/src/com/android/cts/uihost/TaskSwitchingTest.java
@@ -16,11 +16,10 @@
 
 package com.android.cts.uihost;
 
+import com.android.compatibility.common.util.AbiUtils;
+import com.android.compatibility.common.util.MetricsStore;
+import com.android.compatibility.common.util.ReportLog;
 import com.android.cts.tradefed.build.CtsBuildHelper;
-import com.android.cts.tradefed.util.CtsHostStore;
-import com.android.cts.tradefed.util.HostReportLog;
-import com.android.cts.util.AbiUtils;
-import com.android.cts.util.ReportLog;
 import com.android.cts.util.TimeoutReq;
 import com.android.ddmlib.testrunner.RemoteAndroidTestRunner;
 import com.android.ddmlib.testrunner.TestIdentifier;
@@ -33,9 +32,11 @@
 import com.android.tradefed.testtype.IAbiReceiver;
 import com.android.tradefed.testtype.IBuildReceiver;
 
-import java.io.File;
-import java.util.Map;
+import org.xmlpull.v1.XmlPullParserException;
 
+import java.io.File;
+import java.io.IOException;
+import java.util.Map;
 
 /**
  * Measure time to taskswitching between two Apps: A & B
@@ -45,9 +46,10 @@
 public class TaskSwitchingTest extends DeviceTestCase implements IAbiReceiver, IBuildReceiver {
     private static final String TAG = "TaskSwitchingTest";
     private final static String RUNNER = "android.support.test.runner.AndroidJUnitRunner";
+    private static final String RESULT_KEY = "COMPATIBILITY_TEST_RESULT";
     private CtsBuildHelper mBuild;
     private ITestDevice mDevice;
-    private String mCtsReport = null;
+    private ReportLog mReport = null;
     private IAbi mAbi;
 
     static final String[] PACKAGES = {
@@ -94,9 +96,6 @@
 
     @TimeoutReq(minutes = 30)
     public void testTaskswitching() throws Exception {
-        // TODO is this used?
-        HostReportLog report = new HostReportLog(mDevice.getSerialNumber(), mAbi.getName(),
-                ReportLog.getClassMethodNames());
         RemoteAndroidTestRunner testRunner = new RemoteAndroidTestRunner(PACKAGES[0], RUNNER,
                 mDevice.getIDevice());
         LocalListener listener = new LocalListener();
@@ -105,9 +104,9 @@
         if (result.isRunFailure()) {
             fail(result.getRunFailureMessage());
         }
-        assertNotNull("no performance data", mCtsReport);
-        CtsHostStore.storeCtsResult(mDevice.getSerialNumber(), mAbi.getName(),
-                ReportLog.getClassMethodNames(), mCtsReport);
+        assertNotNull("no performance data", mReport);
+        MetricsStore.storeResult(mDevice.getSerialNumber(), mAbi.getName(),
+                String.format("%s#%s", getClass().getName(), "testTaskswitching"), mReport);
 
     }
 
@@ -115,8 +114,12 @@
         @Override
         public void testEnded(TestIdentifier test, Map<String, String> testMetrics) {
             // necessary as testMetrics passed from CollectingTestListerner is empty
-            if (testMetrics.containsKey("CTS_TEST_RESULT")) {
-                mCtsReport = testMetrics.get("CTS_TEST_RESULT");
+            if (testMetrics.containsKey(RESULT_KEY)) {
+                try {
+                    mReport = ReportLog.parse(testMetrics.get(RESULT_KEY));
+                } catch (XmlPullParserException | IOException e) {
+                    e.printStackTrace();
+                }
             }
             super.testEnded(test, testMetrics);
         }
diff --git a/tests/acceleration/Android.mk b/tests/acceleration/Android.mk
index f36b64b..87aa7c5 100644
--- a/tests/acceleration/Android.mk
+++ b/tests/acceleration/Android.mk
@@ -24,10 +24,15 @@
 
 LOCAL_PROGUARD_ENABLED := disabled
 
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner
+
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
-LOCAL_PACKAGE_NAME := CtsAccelerationTestStubs
+# Tag this module as a cts_v2 test artifact
+LOCAL_COMPATIBILITY_SUITE := cts_v2
+
+LOCAL_PACKAGE_NAME := CtsAccelerationTestCases
 
 LOCAL_SDK_VERSION := current
 
-include $(BUILD_CTS_SUPPORT_PACKAGE)
+include $(BUILD_CTS_PACKAGE)
diff --git a/tests/acceleration/AndroidManifest.xml b/tests/acceleration/AndroidManifest.xml
index f92b736..1a21554 100644
--- a/tests/acceleration/AndroidManifest.xml
+++ b/tests/acceleration/AndroidManifest.xml
@@ -15,15 +15,25 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.cts.acceleration.stub">
+        package="android.acceleration.cts">
+
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
 
     <application android:hardwareAccelerated="true">
-        <activity android:name="android.acceleration.cts.HardwareAcceleratedActivity" />
-        <activity android:name="android.acceleration.cts.SoftwareAcceleratedActivity"
+        <uses-library android:name="android.test.runner" />
+        <activity android:name="android.acceleration.HardwareAcceleratedActivity" />
+        <activity android:name="android.acceleration.SoftwareAcceleratedActivity"
                 android:hardwareAccelerated="false" />
-        <activity android:name="android.acceleration.cts.WindowFlagHardwareAcceleratedActivity"
+        <activity android:name="android.acceleration.WindowFlagHardwareAcceleratedActivity"
                 android:hardwareAccelerated="false" />
     </application>
 
+    <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+            android:targetPackage="android.acceleration.cts"
+            android:label="Tests for the Hardware Acceleration APIs." >
+        <meta-data android:name="listener"
+                android:value="com.android.cts.runner.CtsTestRunListener" />
+    </instrumentation>
+
 </manifest>
 
diff --git a/tests/acceleration/AndroidTest.xml b/tests/acceleration/AndroidTest.xml
new file mode 100644
index 0000000..5d745c1
--- /dev/null
+++ b/tests/acceleration/AndroidTest.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<configuration description="Base config for CTS package preparer">
+    <include name="common-config" />
+    <option name="apk-installer:test-file-name" value="CtsAccelerationTestCases.apk" />
+    <test class="com.android.tradefed.testtype.InstrumentationTest" >
+        <option name="package" value="android.acceleration.cts" />
+        <option name="runner" value="android.support.test.runner.AndroidJUnitRunner" />
+    </test>
+</configuration>
diff --git a/tests/acceleration/res/layout/acceleration.xml b/tests/acceleration/res/layout/acceleration.xml
index 8dc027a..5127507 100644
--- a/tests/acceleration/res/layout/acceleration.xml
+++ b/tests/acceleration/res/layout/acceleration.xml
@@ -18,25 +18,25 @@
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         >
-    <android.acceleration.cts.AcceleratedView android:id="@+id/hardware_accelerated_view"
+    <android.acceleration.AcceleratedView android:id="@+id/hardware_accelerated_view"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:layout_weight="1"
             android:layerType="hardware"
             />
-    <android.acceleration.cts.AcceleratedView android:id="@+id/software_accelerated_view"
+    <android.acceleration.AcceleratedView android:id="@+id/software_accelerated_view"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:layout_weight="1"
             android:layerType="software"
             />
     <!-- Acceleration will be set via manual setLayerType calls from the activity. -->
-    <android.acceleration.cts.AcceleratedView android:id="@+id/manual_hardware_accelerated_view"
+    <android.acceleration.AcceleratedView android:id="@+id/manual_hardware_accelerated_view"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:layout_weight="1"
             />
-    <android.acceleration.cts.AcceleratedView android:id="@+id/manual_software_accelerated_view"
+    <android.acceleration.AcceleratedView android:id="@+id/manual_software_accelerated_view"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:layout_weight="1"
diff --git a/tests/acceleration/src/android/acceleration/cts/AcceleratedView.java b/tests/acceleration/src/android/acceleration/AcceleratedView.java
similarity index 98%
rename from tests/acceleration/src/android/acceleration/cts/AcceleratedView.java
rename to tests/acceleration/src/android/acceleration/AcceleratedView.java
index 7d749a1..7134a76 100644
--- a/tests/acceleration/src/android/acceleration/cts/AcceleratedView.java
+++ b/tests/acceleration/src/android/acceleration/AcceleratedView.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package android.acceleration.cts;
+package android.acceleration;
 
 import android.content.Context;
 import android.graphics.Canvas;
diff --git a/tests/acceleration/src/android/acceleration/cts/BaseAcceleratedActivity.java b/tests/acceleration/src/android/acceleration/BaseAcceleratedActivity.java
similarity index 93%
rename from tests/acceleration/src/android/acceleration/cts/BaseAcceleratedActivity.java
rename to tests/acceleration/src/android/acceleration/BaseAcceleratedActivity.java
index 8ef6a8e..262b43e 100644
--- a/tests/acceleration/src/android/acceleration/cts/BaseAcceleratedActivity.java
+++ b/tests/acceleration/src/android/acceleration/BaseAcceleratedActivity.java
@@ -14,15 +14,15 @@
  * limitations under the License.
  */
 
-package android.acceleration.cts;
+package android.acceleration;
 
-import com.android.cts.acceleration.stub.R;
+import android.acceleration.cts.R;
 
 import android.app.Activity;
 import android.os.Bundle;
 import android.view.View;
 
-abstract class BaseAcceleratedActivity extends Activity {
+public abstract class BaseAcceleratedActivity extends Activity {
 
     private AcceleratedView mHardwareAcceleratedView;
     private AcceleratedView mSoftwareAcceleratedView;
diff --git a/tests/acceleration/src/android/acceleration/cts/HardwareAcceleratedActivity.java b/tests/acceleration/src/android/acceleration/HardwareAcceleratedActivity.java
similarity index 95%
rename from tests/acceleration/src/android/acceleration/cts/HardwareAcceleratedActivity.java
rename to tests/acceleration/src/android/acceleration/HardwareAcceleratedActivity.java
index bb26202..9122565 100644
--- a/tests/acceleration/src/android/acceleration/cts/HardwareAcceleratedActivity.java
+++ b/tests/acceleration/src/android/acceleration/HardwareAcceleratedActivity.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package android.acceleration.cts;
+package android.acceleration;
 
 public class HardwareAcceleratedActivity extends BaseAcceleratedActivity {
 }
diff --git a/tests/acceleration/src/android/acceleration/cts/SoftwareAcceleratedActivity.java b/tests/acceleration/src/android/acceleration/SoftwareAcceleratedActivity.java
similarity index 95%
rename from tests/acceleration/src/android/acceleration/cts/SoftwareAcceleratedActivity.java
rename to tests/acceleration/src/android/acceleration/SoftwareAcceleratedActivity.java
index 0a6a3df..7555862 100644
--- a/tests/acceleration/src/android/acceleration/cts/SoftwareAcceleratedActivity.java
+++ b/tests/acceleration/src/android/acceleration/SoftwareAcceleratedActivity.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package android.acceleration.cts;
+package android.acceleration;
 
 public class SoftwareAcceleratedActivity extends BaseAcceleratedActivity {
 }
diff --git a/tests/acceleration/src/android/acceleration/cts/WindowFlagHardwareAcceleratedActivity.java b/tests/acceleration/src/android/acceleration/WindowFlagHardwareAcceleratedActivity.java
similarity index 96%
rename from tests/acceleration/src/android/acceleration/cts/WindowFlagHardwareAcceleratedActivity.java
rename to tests/acceleration/src/android/acceleration/WindowFlagHardwareAcceleratedActivity.java
index 9def8b7..d448334 100644
--- a/tests/acceleration/src/android/acceleration/cts/WindowFlagHardwareAcceleratedActivity.java
+++ b/tests/acceleration/src/android/acceleration/WindowFlagHardwareAcceleratedActivity.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package android.acceleration.cts;
+package android.acceleration;
 
 import android.os.Bundle;
 import android.view.WindowManager;
diff --git a/tests/tests/acceleration/src/android/acceleration/cts/BaseAccelerationTest.java b/tests/acceleration/src/android/acceleration/cts/BaseAccelerationTest.java
similarity index 96%
rename from tests/tests/acceleration/src/android/acceleration/cts/BaseAccelerationTest.java
rename to tests/acceleration/src/android/acceleration/cts/BaseAccelerationTest.java
index d2f1d9f..c6e94ec 100644
--- a/tests/tests/acceleration/src/android/acceleration/cts/BaseAccelerationTest.java
+++ b/tests/acceleration/src/android/acceleration/cts/BaseAccelerationTest.java
@@ -16,6 +16,8 @@
 
 package android.acceleration.cts;
 
+import android.acceleration.AcceleratedView;
+import android.acceleration.BaseAcceleratedActivity;
 import android.app.ActivityManager;
 import android.content.Context;
 import android.content.pm.ConfigurationInfo;
diff --git a/tests/tests/acceleration/src/android/acceleration/cts/HardwareAccelerationTest.java b/tests/acceleration/src/android/acceleration/cts/HardwareAccelerationTest.java
similarity index 97%
rename from tests/tests/acceleration/src/android/acceleration/cts/HardwareAccelerationTest.java
rename to tests/acceleration/src/android/acceleration/cts/HardwareAccelerationTest.java
index eddd34f..94089cc 100644
--- a/tests/tests/acceleration/src/android/acceleration/cts/HardwareAccelerationTest.java
+++ b/tests/acceleration/src/android/acceleration/cts/HardwareAccelerationTest.java
@@ -16,6 +16,8 @@
 
 package android.acceleration.cts;
 
+import android.acceleration.HardwareAcceleratedActivity;
+
 /**
  * Test that uses an Activity with hardware acceleration enabled.
  */
diff --git a/tests/tests/acceleration/src/android/acceleration/cts/SoftwareAccelerationTest.java b/tests/acceleration/src/android/acceleration/cts/SoftwareAccelerationTest.java
similarity index 96%
rename from tests/tests/acceleration/src/android/acceleration/cts/SoftwareAccelerationTest.java
rename to tests/acceleration/src/android/acceleration/cts/SoftwareAccelerationTest.java
index 146fa6a..4e12c1e 100644
--- a/tests/tests/acceleration/src/android/acceleration/cts/SoftwareAccelerationTest.java
+++ b/tests/acceleration/src/android/acceleration/cts/SoftwareAccelerationTest.java
@@ -16,6 +16,8 @@
 
 package android.acceleration.cts;
 
+import android.acceleration.SoftwareAcceleratedActivity;
+
 /**
  * Test that uses an Activity with hardware acceleration explicitly disabled
  * and makes sure that all views are rendered using software acceleration.
diff --git a/tests/tests/acceleration/src/android/acceleration/cts/WindowFlagHardwareAccelerationTest.java b/tests/acceleration/src/android/acceleration/cts/WindowFlagHardwareAccelerationTest.java
similarity index 96%
rename from tests/tests/acceleration/src/android/acceleration/cts/WindowFlagHardwareAccelerationTest.java
rename to tests/acceleration/src/android/acceleration/cts/WindowFlagHardwareAccelerationTest.java
index bfbbe63..2070666 100644
--- a/tests/tests/acceleration/src/android/acceleration/cts/WindowFlagHardwareAccelerationTest.java
+++ b/tests/acceleration/src/android/acceleration/cts/WindowFlagHardwareAccelerationTest.java
@@ -16,6 +16,8 @@
 
 package android.acceleration.cts;
 
+import android.acceleration.WindowFlagHardwareAcceleratedActivity;
+
 /**
  * Test that uses an Activity with hardware acceleration enabled.
  */
diff --git a/tests/expectations/knownfailures.txt b/tests/expectations/knownfailures.txt
index c9d1dca..6b3adb9 100644
--- a/tests/expectations/knownfailures.txt
+++ b/tests/expectations/knownfailures.txt
@@ -237,14 +237,6 @@
   bug: 17989532
 },
 {
-  description: "The new long processing test is not yet passing on all devices",
-  names: [
-    "android.hardware.camera2.cts.ImageReaderTest#testLongProcessingRepeatingRaw",
-    "android.hardware.camera2.cts.ImageReaderTest#testLongProcessingRepeatingFlexibleYuv"
-  ],
-  bug: 22861512
-},
-{
   description: "The timing measurements for preview callbacks are not reliable",
   names: [
     "android.hardware.cts.CameraTest#testPreviewFpsRange"
diff --git a/tests/sample/Android.mk b/tests/sample/Android.mk
index 5e8f571..1255032 100755
--- a/tests/sample/Android.mk
+++ b/tests/sample/Android.mk
@@ -16,19 +16,24 @@
 
 include $(CLEAR_VARS)
 
-# Don't include this package in any target.
-LOCAL_MODULE_TAGS := optional
-
+# Don't include this package in any target
+LOCAL_MODULE_TAGS := tests
 # When built, explicitly put it in the data partition.
 LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
 
-LOCAL_STATIC_JAVA_LIBRARIES := ctsdeviceutil ctstestrunner
+LOCAL_DEX_PREOPT := false
+
+LOCAL_PROGUARD_ENABLED := disabled
+
+LOCAL_STATIC_JAVA_LIBRARIES := compatibility-device-util android-support-test
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
-# Must match the package name in CtsTestCaseList.mk
+# Tag this module as a cts_v2 test artifact
+LOCAL_COMPATIBILITY_SUITE := cts_v2
+
 LOCAL_PACKAGE_NAME := CtsSampleDeviceTestCases
 
 LOCAL_SDK_VERSION := current
 
-include $(BUILD_CTS_PACKAGE)
+include $(BUILD_PACKAGE)
diff --git a/tests/sample/AndroidManifest.xml b/tests/sample/AndroidManifest.xml
index f07ebbe..2f32e86 100755
--- a/tests/sample/AndroidManifest.xml
+++ b/tests/sample/AndroidManifest.xml
@@ -18,7 +18,7 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="android.sample.cts">
 
-    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
     <application>
         <uses-library android:name="android.test.runner" />
         <activity android:name="android.sample.SampleDeviceActivity" >
@@ -34,9 +34,6 @@
         android:name="android.support.test.runner.AndroidJUnitRunner"
         android:label="CTS sample tests"
         android:targetPackage="android.sample.cts" >
-        <meta-data
-            android:name="listener"
-            android:value="com.android.cts.runner.CtsTestRunListener" />
     </instrumentation>
 </manifest>
 
diff --git a/tests/sample/AndroidTest.xml b/tests/sample/AndroidTest.xml
new file mode 100644
index 0000000..0b165f8
--- /dev/null
+++ b/tests/sample/AndroidTest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<configuration description="Base config for CTS package preparer">
+    <include name="common-config" />
+    <option name="apk-installer:test-file-name" value="CtsSampleDeviceTestCases.apk" />
+    <test class="com.android.tradefed.testtype.InstrumentationTest" >
+        <option name="package" value="android.sample.cts" />
+        <option name="runner" value="android.support.test.runner.AndroidJUnitRunner" />
+    </test>
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.DynamicConfigPusher">
+        <option name="target" value="device" />
+        <option name="module-name" value="CtsSampleDeviceTestCases"/>
+        <option name="version-name" value="1.0"/>
+    </target_preparer>
+</configuration>
diff --git a/tests/sample/DynamicConfig.xml b/tests/sample/DynamicConfig.xml
new file mode 100644
index 0000000..18c07ef
--- /dev/null
+++ b/tests/sample/DynamicConfig.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<DynamicConfig>
+    <Config key ="local-config">local-config-val</Config>
+</DynamicConfig>
diff --git a/tests/sample/src/android/sample/cts/SampleDeviceResultTest.java b/tests/sample/src/android/sample/cts/SampleDeviceResultTest.java
index 6bf883f..7bd434e 100644
--- a/tests/sample/src/android/sample/cts/SampleDeviceResultTest.java
+++ b/tests/sample/src/android/sample/cts/SampleDeviceResultTest.java
@@ -15,21 +15,25 @@
  */
 package android.sample.cts;
 
-import com.android.cts.util.MeasureRun;
-import com.android.cts.util.MeasureTime;
-import com.android.cts.util.ReportLog;
-import com.android.cts.util.ResultType;
-import com.android.cts.util.ResultUnit;
-import com.android.cts.util.Stat;
+import android.sample.SampleDeviceActivity;
+import android.test.ActivityInstrumentationTestCase2;
 
-import android.cts.util.CtsAndroidTestCase;
+import com.android.compatibility.common.util.DeviceReportLog;
+import com.android.compatibility.common.util.MeasureRun;
+import com.android.compatibility.common.util.MeasureTime;
+import com.android.compatibility.common.util.ResultType;
+import com.android.compatibility.common.util.ResultUnit;
+import com.android.compatibility.common.util.Stat;
+
+import java.util.Arrays;
+import java.util.Random;
 
 /**
  * A simple compatibility test which includes results in the report.
  *
  * This test measures the time taken to run a workload and adds in the report.
  */
-public class SampleDeviceResultTest extends CtsAndroidTestCase {
+public class SampleDeviceResultTest extends ActivityInstrumentationTestCase2<SampleDeviceActivity> {
 
     /**
      * The number of times to repeat the test.
@@ -37,86 +41,74 @@
     private static final int REPEAT = 5;
 
     /**
-     * The input number for the factorial.
+     * A {@link Random} to generate random integers to test the sort.
      */
-    private static final int IN = 15;
+    private static final Random random = new Random(12345);
 
     /**
-     * The expected output number for the factorial.
+     * Constructor which passes the class of the activity to be instrumented.
      */
-    private static final long OUT = 1307674368000L;
-
-    /**
-     * Measures the time taken to compute the factorial of 15 with a recursive method.
-     *
-     * @throws Exception
-     */
-    public void testFactorialRecursive() throws Exception {
-        runTest(new MeasureRun() {
-            @Override
-            public void run(int i) throws Exception {
-                // Compute the factorial and assert it is correct.
-                assertEquals("Incorrect result", OUT, factorialRecursive(IN));
-            }
-        });
+    public SampleDeviceResultTest() {
+        super(SampleDeviceActivity.class);
     }
 
     /**
-     * Measures the time taken to compute the factorial of 15 with a iterative method.
-     *
-     * @throws Exception
+     * Creates an array filled with random numbers of the given size.
      */
-    public void testFactorialIterative() throws Exception {
-        runTest(new MeasureRun() {
-            @Override
-            public void run(int i) throws Exception {
-                // Compute the factorial and assert it is correct.
-                assertEquals("Incorrect result", OUT, factorialIterative(IN));
-            }
-        });
-    }
-
-    /**
-     * Computes the factorial of a number with a recursive method.
-     *
-     * @param num The number to compute the factorial of.
-     */
-    private static long factorialRecursive(int num) {
-        if (num <= 0) {
-            return 1;
+    private static int[] createArray(int size) {
+        int[] array = new int[size];
+        for (int i = 0; i < size; i++) {
+            array[i] = random.nextInt();
         }
-        return num * factorialRecursive(num - 1);
+        return array;
     }
 
     /**
-     * Computes the factorial of a number with a iterative method.
-     *
-     * @param num The number to compute the factorial of.
+     * Tests an array is sorted.
      */
-    private static long factorialIterative(int num) {
-        long result = 1;
-        for (int i = 2; i <= num; i++) {
-            result *= i;
+    private static boolean isSorted(int[] array) {
+        int len = array.length;
+        for (int i = 0, j = 1; j < len; i++, j++) {
+            if (array[i] > array[j]) {
+                return false;
+            }
         }
-        return result;
+        return true;
     }
 
     /**
-     * Runs the workload and records the result to the report log.
-     *
-     * @param workload
+     * Measures the time taken to sort an array.
      */
-    private void runTest(MeasureRun workload) throws Exception {
+    public void testSort() throws Exception {
         // MeasureTime runs the workload N times and records the time taken by each run.
-        double[] result = MeasureTime.measure(REPEAT, workload);
+        double[] result = MeasureTime.measure(REPEAT, new MeasureRun() {
+            /**
+             * The size of the array to sort.
+             */
+            private static final int ARRAY_SIZE = 100000;
+            private int[] array;
+            @Override
+            public void prepare(int i) throws Exception {
+                array = createArray(ARRAY_SIZE);
+            }
+            @Override
+            public void run(int i) throws Exception {
+                Arrays.sort(array);
+                assertTrue("Array not sorted", isSorted(array));
+            }
+        });
         // Compute the stats.
         Stat.StatResult stat = Stat.getStat(result);
-        // Get the report for this test and add the results to record.
-        ReportLog log = getReportLog();
-        log.printArray("Times", result, ResultType.LOWER_BETTER, ResultUnit.MS);
-        log.printValue("Min", stat.mMin, ResultType.LOWER_BETTER, ResultUnit.MS);
-        log.printValue("Max", stat.mMax, ResultType.LOWER_BETTER, ResultUnit.MS);
+        // Create a new report to hold the metrics.
+        DeviceReportLog reportLog = new DeviceReportLog();
+        // Add the results to the report.
+        reportLog.addValues("Times", result, ResultType.LOWER_BETTER, ResultUnit.MS);
+        reportLog.addValue("Min", stat.mMin, ResultType.LOWER_BETTER, ResultUnit.MS);
+        reportLog.addValue("Max", stat.mMax, ResultType.LOWER_BETTER, ResultUnit.MS);
         // Every report must have a summary,
-        log.printSummary("Average", stat.mAverage, ResultType.LOWER_BETTER, ResultUnit.MS);
+        reportLog.setSummary("Average", stat.mAverage, ResultType.LOWER_BETTER, ResultUnit.MS);
+        // Submit the report to the given instrumentation.
+        reportLog.submit(getInstrumentation());
     }
+
 }
diff --git a/tests/sample/src/android/sample/cts/SampleDeviceTest.java b/tests/sample/src/android/sample/cts/SampleDeviceTest.java
index 13b7ea6..f51d227 100644
--- a/tests/sample/src/android/sample/cts/SampleDeviceTest.java
+++ b/tests/sample/src/android/sample/cts/SampleDeviceTest.java
@@ -18,6 +18,8 @@
 import android.sample.SampleDeviceActivity;
 import android.test.ActivityInstrumentationTestCase2;
 
+import com.android.compatibility.common.util.DynamicConfigDeviceSide;
+
 /**
  * A simple compatibility test which tests the SharedPreferences API.
  *
@@ -73,4 +75,23 @@
         mActivity.clearPreferences();
         assertNull("Preferences were not cleared", mActivity.getPreference(KEY));
     }
+
+    /**
+     * Test if Dynamic Config on the device side works
+     * @throws Exception
+     */
+    public void testDynamicConfigLocal() throws Exception {
+        DynamicConfigDeviceSide config = new DynamicConfigDeviceSide("CtsSampleDeviceTestCases");
+        assertEquals("local-config-val", config.getConfig("local-config"));
+    }
+
+    /**
+     * Test if Dynamic Config override on the device side works
+     * @throws Exception
+     */
+    public void testDynamicConfigOverride() throws Exception {
+        DynamicConfigDeviceSide config = new DynamicConfigDeviceSide("CtsSampleDeviceTestCases");
+        assertEquals("device-1.0-cts-keyone", config.getConfig("sample_device_key_one"));
+
+    }
 }
diff --git a/tests/sample/src/android/sample/cts/SampleJUnit4DeviceTest.java b/tests/sample/src/android/sample/cts/SampleJUnit4DeviceTest.java
new file mode 100755
index 0000000..c8863b3
--- /dev/null
+++ b/tests/sample/src/android/sample/cts/SampleJUnit4DeviceTest.java
@@ -0,0 +1,68 @@
+/*
+ * 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 android.sample.cts;
+
+import org.junit.Assert;
+import org.junit.runner.RunWith;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+import android.app.Activity;
+import android.sample.SampleDeviceActivity;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.test.ActivityInstrumentationTestCase2;
+
+/**
+ * A simple compatibility test which tests the SharedPreferences API.
+ *
+ * This test uses {@link ActivityTestRule} to instrument the
+ * {@link android.sample.SampleDeviceActivity}.
+ */
+@RunWith(AndroidJUnit4.class)
+public class SampleJUnit4DeviceTest {
+
+    private static final String KEY = "foo";
+
+    private static final String VALUE = "bar";
+
+    @Rule
+    public ActivityTestRule<SampleDeviceActivity> mActivityRule =
+        new ActivityTestRule(SampleDeviceActivity.class);
+
+
+    /**
+     * This inserts the key value pair and assert they can be retrieved. Then it clears the
+     * preferences and asserts they can no longer be retrieved.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void shouldSaveSharedPreferences() throws Exception {
+        // Save the key value pair to the preferences and assert they were saved.
+        mActivityRule.getActivity().savePreference(KEY, VALUE);
+        Assert.assertEquals("Preferences were not saved", VALUE,
+            mActivityRule.getActivity().getPreference(KEY));
+
+        // Clear the shared preferences and assert the data was removed.
+        mActivityRule.getActivity().clearPreferences();
+        Assert.assertNull("Preferences were not cleared",
+            mActivityRule.getActivity().getPreference(KEY));
+    }
+}
diff --git a/tests/tests/acceleration/AndroidManifest.xml b/tests/tests/acceleration/AndroidManifest.xml
deleted file mode 100644
index 0dd2722..0000000
--- a/tests/tests/acceleration/AndroidManifest.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<!--
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- -->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.cts.acceleration">
-
-  <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
-  <application>
-      <uses-library android:name="android.test.runner" />
-  </application>
-
-  <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
-                   android:targetPackage="com.android.cts.acceleration.stub"
-                   android:label="Tests for the Hardware Acceleration APIs." >
-        <meta-data android:name="listener"
-            android:value="com.android.cts.runner.CtsTestRunListener" />
-    </instrumentation>
-
-</manifest>
diff --git a/tests/tests/accessibility/Android.mk b/tests/tests/accessibility/Android.mk
index 263c47b..c231ab04 100644
--- a/tests/tests/accessibility/Android.mk
+++ b/tests/tests/accessibility/Android.mk
@@ -26,6 +26,8 @@
 
 LOCAL_STATIC_JAVA_LIBRARIES := ctsdeviceutil ctstestrunner
 
+LOCAL_CTS_MODULE_CONFIG := $(LOCAL_PATH)/Old$(CTS_MODULE_TEST_CONFIG)
+
 LOCAL_SDK_VERSION := current
 
 include $(BUILD_CTS_PACKAGE)
diff --git a/tests/tests/accessibility/AndroidTest.xml b/tests/tests/accessibility/OldAndroidTest.xml
similarity index 100%
rename from tests/tests/accessibility/AndroidTest.xml
rename to tests/tests/accessibility/OldAndroidTest.xml
diff --git a/tests/tests/app/Android.mk b/tests/tests/app/Android.mk
index 301f931..8bdccea 100644
--- a/tests/tests/app/Android.mk
+++ b/tests/tests/app/Android.mk
@@ -31,6 +31,8 @@
 
 LOCAL_INSTRUMENTATION_FOR := CtsAppTestStubs
 
+LOCAL_CTS_MODULE_CONFIG := $(LOCAL_PATH)/Old$(CTS_MODULE_TEST_CONFIG)
+
 LOCAL_SDK_VERSION := current
 
 include $(BUILD_CTS_PACKAGE)
diff --git a/tests/tests/app/AndroidTest.xml b/tests/tests/app/OldAndroidTest.xml
similarity index 100%
rename from tests/tests/app/AndroidTest.xml
rename to tests/tests/app/OldAndroidTest.xml
diff --git a/tests/tests/content/src/android/content/res/cts/ConfigurationTest.java b/tests/tests/content/src/android/content/res/cts/ConfigurationTest.java
index 30c78a8..d4b6c63 100644
--- a/tests/tests/content/src/android/content/res/cts/ConfigurationTest.java
+++ b/tests/tests/content/src/android/content/res/cts/ConfigurationTest.java
@@ -22,6 +22,7 @@
 import android.content.res.Configuration;
 import android.os.Parcel;
 import android.test.AndroidTestCase;
+import android.util.LocaleList;
 import android.view.View;
 
 public class ConfigurationTest extends AndroidTestCase {
@@ -40,7 +41,7 @@
         mConfig = new Configuration();
         mConfig.fontScale = 2;
         mConfig.mcc = mConfig.mnc = 1;
-        mConfig.locale = Locale.getDefault();
+        mConfig.setLocale(Locale.getDefault());
         mConfig.touchscreen = Configuration.TOUCHSCREEN_NOTOUCH;
         mConfig.keyboard = Configuration.KEYBOARD_NOKEYS;
         mConfig.keyboardHidden = Configuration.KEYBOARDHIDDEN_NO;
@@ -93,6 +94,27 @@
         cfg2.touchscreen = 2;
         assertEquals(1, cfg1.compareTo(cfg2));
 
+        cfg1.setLocales(LocaleList.forLanguageTags("fr"));
+        cfg2.setLocales(LocaleList.forLanguageTags("fr,en"));
+        assertTrue(cfg1.compareTo(cfg2) < 0);
+        cfg1.setLocales(LocaleList.forLanguageTags("fr,en"));
+        cfg2.setLocales(LocaleList.forLanguageTags("fr"));
+        assertTrue(cfg1.compareTo(cfg2) > 0);
+
+        cfg1.setLocales(LocaleList.forLanguageTags("fr,en"));
+        cfg2.setLocales(LocaleList.forLanguageTags("fr,en-US"));
+        assertTrue(cfg1.compareTo(cfg2) < 0);
+        cfg1.setLocales(LocaleList.forLanguageTags("fr,en-US"));
+        cfg2.setLocales(LocaleList.forLanguageTags("fr,en"));
+        assertTrue(cfg1.compareTo(cfg2) > 0);
+
+        cfg1.locale = Locale.forLanguageTag("en");
+        cfg2.locale = Locale.forLanguageTag("en-Shaw");
+        assertTrue(cfg1.compareTo(cfg2) < 0);
+        cfg1.locale = Locale.forLanguageTag("en-Shaw");
+        cfg2.locale = Locale.forLanguageTag("en");
+        assertTrue(cfg1.compareTo(cfg2) > 0);
+
         cfg1.locale = new Locale("", "", "2");
         cfg2.locale = new Locale("", "", "3");
         assertEquals(-1, cfg1.compareTo(cfg2));
@@ -114,6 +136,13 @@
         cfg2.locale = new Locale("2", "", "");
         assertEquals(1, cfg1.compareTo(cfg2));
 
+        cfg1.locale = new Locale("");
+        cfg2.locale = null;
+        assertTrue(cfg1.compareTo(cfg2) < 0);
+        cfg1.locale = null;
+        cfg2.locale = new Locale("");
+        assertTrue(cfg1.compareTo(cfg2) > 0);
+
         cfg1.mnc = 2;
         cfg2.mnc = 3;
         assertEquals(-1, cfg1.compareTo(cfg2));
@@ -160,6 +189,11 @@
                 | ActivityInfo.CONFIG_MNC
                 | ActivityInfo.CONFIG_LOCALE
                 | ActivityInfo.CONFIG_LAYOUT_DIRECTION, mConfigDefault, config);
+        config.setLocales(LocaleList.forLanguageTags("fr,en"));
+        doConfigCompare(ActivityInfo.CONFIG_MCC
+                | ActivityInfo.CONFIG_MNC
+                | ActivityInfo.CONFIG_LOCALE
+                | ActivityInfo.CONFIG_LAYOUT_DIRECTION, mConfigDefault, config);
         config.screenLayout = 1;
         doConfigCompare(ActivityInfo.CONFIG_MCC
                 | ActivityInfo.CONFIG_MNC
@@ -282,6 +316,7 @@
         assertFalse(temp.equals(mConfigDefault));
         temp.setToDefaults();
         assertTrue(temp.equals(mConfigDefault));
+        assertTrue(temp.getLocales().isEmpty());
     }
 
     public void testToString() {
@@ -289,47 +324,62 @@
     }
 
     public void testWriteToParcel() {
-        assertWriteToParcel(createConfig(null), Parcel.obtain());
+        assertWriteToParcel(createConfig((Locale) null), Parcel.obtain());
+        assertWriteToParcel(createConfig(new Locale("")), Parcel.obtain());
         assertWriteToParcel(createConfig(Locale.JAPAN), Parcel.obtain());
+        assertWriteToParcel(createConfig(Locale.forLanguageTag("en-Shaw")), Parcel.obtain());
+        assertWriteToParcel(createConfig(LocaleList.forLanguageTags("fr,en-US")), Parcel.obtain());
     }
 
     public void testSetLocale() {
         Configuration config = new Configuration();
 
+        config.setLocale(null);
+        assertNull(config.locale);
+        assertTrue(config.getLocales().isEmpty());
+
         config.setLocale(Locale.getDefault());
         assertEquals(Locale.getDefault(), config.locale);
-        assertEquals(View.LAYOUT_DIRECTION_LTR, config.getLayoutDirection());
+        assertEquals(new LocaleList(Locale.getDefault()), config.getLocales());
 
         config.setLocale(Locale.ENGLISH);
         assertEquals(Locale.ENGLISH, config.locale);
+        assertEquals(new LocaleList(Locale.ENGLISH), config.getLocales());
         assertEquals(View.LAYOUT_DIRECTION_LTR, config.getLayoutDirection());
 
         config.setLocale(Locale.US);
         assertEquals(Locale.US, config.locale);
+        assertEquals(new LocaleList(Locale.US), config.getLocales());
         assertEquals(View.LAYOUT_DIRECTION_LTR, config.getLayoutDirection());
 
         final Locale arEGLocale = new Locale("ar", "EG");
         config.setLocale(arEGLocale);
         assertEquals(arEGLocale, config.locale);
+        assertEquals(new LocaleList(arEGLocale), config.getLocales());
         assertEquals(View.LAYOUT_DIRECTION_RTL, config.getLayoutDirection());
 
-        final Locale faFALocale = new Locale("fa", "FA");
-        config.setLocale(faFALocale);
-        assertEquals(faFALocale, config.locale);
+        final Locale faIRLocale = new Locale("fa", "IR");
+        config.setLocale(faIRLocale);
+        assertEquals(faIRLocale, config.locale);
+        assertEquals(new LocaleList(faIRLocale), config.getLocales());
         assertEquals(View.LAYOUT_DIRECTION_RTL, config.getLayoutDirection());
 
         final Locale iwILLocale = new Locale("iw", "IL");
         config.setLocale(iwILLocale);
         assertEquals(iwILLocale, config.locale);
+        assertEquals(new LocaleList(iwILLocale), config.getLocales());
+        assertEquals(View.LAYOUT_DIRECTION_RTL, config.getLayoutDirection());
+
+        final Locale urPKLocale = new Locale("ur", "PK");
+        config.setLocale(urPKLocale);
+        assertEquals(urPKLocale, config.locale);
+        assertEquals(new LocaleList(urPKLocale), config.getLocales());
         assertEquals(View.LAYOUT_DIRECTION_RTL, config.getLayoutDirection());
     }
 
     public void testSetGetLayoutDirection() {
         Configuration config = new Configuration();
 
-        config.setLayoutDirection(Locale.getDefault());
-        assertEquals(View.LAYOUT_DIRECTION_LTR, config.getLayoutDirection());
-
         config.setLayoutDirection(Locale.ENGLISH);
         assertEquals(View.LAYOUT_DIRECTION_LTR, config.getLayoutDirection());
 
@@ -340,21 +390,230 @@
         config.setLayoutDirection(arEGLocale);
         assertEquals(View.LAYOUT_DIRECTION_RTL, config.getLayoutDirection());
 
-        final Locale faFALocale = new Locale("fa", "FA");
-        config.setLayoutDirection(faFALocale);
+        final Locale faIRLocale = new Locale("fa", "IR");
+        config.setLayoutDirection(faIRLocale);
         assertEquals(View.LAYOUT_DIRECTION_RTL, config.getLayoutDirection());
 
         final Locale iwILLocale = new Locale("iw", "IL");
         config.setLayoutDirection(iwILLocale);
         assertEquals(View.LAYOUT_DIRECTION_RTL, config.getLayoutDirection());
+
+        final Locale urPKLocale = new Locale("ur", "PK");
+        config.setLayoutDirection(urPKLocale);
+        assertEquals(View.LAYOUT_DIRECTION_RTL, config.getLayoutDirection());
+    }
+
+    public void testFixUpLocaleList() {
+        Configuration config = new Configuration();
+
+        config.setLocales(LocaleList.forLanguageTags("fr"));
+        config.locale = null;
+        assertEquals(LocaleList.getEmptyLocaleList(), config.getLocales());
+
+        config.setLocales(LocaleList.forLanguageTags("fr,en"));
+        config.locale = Locale.forLanguageTag("en");
+        assertEquals(LocaleList.forLanguageTags("en"), config.getLocales());
+
+        config.setLocales(LocaleList.forLanguageTags("fr,en"));
+        config.locale = Locale.forLanguageTag("fr");
+        assertEquals(LocaleList.forLanguageTags("fr,en"), config.getLocales());
+    }
+
+    public void testSetTo_localeFixUp() {
+        Configuration config1 = new Configuration();
+        Configuration config2 = new Configuration();
+        config2.locale = Locale.FRENCH;
+
+        config1.setTo(config2);
+        assertEquals(Locale.FRENCH, config1.locale);
+        assertEquals(new LocaleList(Locale.FRENCH), config1.getLocales());
+        assertEquals(new LocaleList(Locale.FRENCH), config2.getLocales());
+    }
+
+    public void testToString_localeFixUp() {
+        Configuration config1 = new Configuration();
+        Configuration config2 = new Configuration();
+        config1.setLocales(LocaleList.forLanguageTags("fr,en"));
+        config1.locale = Locale.forLanguageTag("en");
+        config2.setLocales(LocaleList.forLanguageTags("en"));
+
+        assertEquals(config1.toString(), config2.toString());
+    }
+
+    public void testUpdateFrom_localeFixUp() {
+        Configuration config1, config2;
+        int changed;
+
+        config1 = new Configuration();
+        config2 = new Configuration();
+        config1.locale = Locale.FRENCH;
+        changed = config1.updateFrom(config2);
+        assertEquals(0, changed);
+        assertEquals(Locale.FRENCH, config1.locale);
+        assertEquals(new LocaleList(Locale.FRENCH), config1.getLocales());
+
+        config1 = new Configuration();
+        config2 = new Configuration();
+        config2.locale = Locale.FRENCH;
+        changed = config1.updateFrom(config2);
+        assertEquals(ActivityInfo.CONFIG_LOCALE | ActivityInfo.CONFIG_LAYOUT_DIRECTION, changed);
+        assertEquals(Locale.FRENCH, config1.locale);
+        assertEquals(new LocaleList(Locale.FRENCH), config1.getLocales());
+        assertEquals(new LocaleList(Locale.FRENCH), config2.getLocales());
+
+        config1 = new Configuration();
+        config2 = new Configuration();
+        config1.setLocales(LocaleList.forLanguageTags("en,fr"));
+        config1.locale = Locale.forLanguageTag("fr");
+        config2.setLocales(LocaleList.forLanguageTags("en,de"));
+        config2.locale = Locale.forLanguageTag("fr");
+        changed = config1.updateFrom(config2);
+        assertEquals(0, changed);
+        assertEquals(Locale.forLanguageTag("fr"), config1.locale);
+        assertEquals(LocaleList.forLanguageTags("fr"), config1.getLocales());
+        assertEquals(LocaleList.forLanguageTags("fr"), config2.getLocales());
+    }
+
+    public void testUpdateFrom_layoutDirection() {
+        Configuration config1, config2;
+        int changed;
+
+        config1 = new Configuration();
+        config2 = new Configuration();
+        config1.setLocales(LocaleList.forLanguageTags("fr,en"));
+        config2.setLocales(LocaleList.forLanguageTags("de,en"));
+        changed = config1.updateFrom(config2);
+        assertTrue((changed & ActivityInfo.CONFIG_LAYOUT_DIRECTION) != 0);
+
+        config1 = new Configuration();
+        config2 = new Configuration();
+        config1.setLocales(LocaleList.forLanguageTags("fr,en"));
+        config2.setLocales(LocaleList.forLanguageTags("fr,de"));
+        changed = config1.updateFrom(config2);
+        assertEquals(0, changed & ActivityInfo.CONFIG_LAYOUT_DIRECTION);
+    }
+
+    public void testDiff_localeFixUp() {
+        Configuration config1 = new Configuration();
+        Configuration config2 = new Configuration();
+        config1.setLocales(LocaleList.forLanguageTags("en,fr"));
+        config1.locale = Locale.forLanguageTag("fr");
+        config2.setLocales(LocaleList.forLanguageTags("en,de"));
+        config2.locale = Locale.forLanguageTag("fr");
+
+        int diff = config1.diff(config2);
+        assertEquals(0, diff);
+    }
+
+    public void testCompareTo_localeFixUp() {
+        Configuration config1 = new Configuration();
+        Configuration config2 = new Configuration();
+        config1.setLocales(LocaleList.forLanguageTags("en,fr"));
+        config2.setLocales(LocaleList.forLanguageTags("en,fr"));
+        assertEquals(0, config1.compareTo(config2));
+        config1.locale = new Locale("2");
+        config2.locale = new Locale("3");
+        assertEquals(-1, config1.compareTo(config2));
+    }
+
+    public void testSetLocales_null() {
+        Configuration config = new Configuration();
+        config.setLocales(null);
+        assertNull(config.locale);
+        assertNotNull(config.getLocales());
+        assertTrue(config.getLocales().isEmpty());
+        assertEquals(View.LAYOUT_DIRECTION_LTR, config.getLayoutDirection());
+    }
+
+    public void testSetLocales_emptyList() {
+        Configuration config = new Configuration();
+        config.setLocales(LocaleList.getEmptyLocaleList());
+        assertNull(config.locale);
+        assertNotNull(config.getLocales());
+        assertTrue(config.getLocales().isEmpty());
+        assertEquals(View.LAYOUT_DIRECTION_LTR, config.getLayoutDirection());
+    }
+
+    public void testSetLocales_oneLtr() {
+        Configuration config = new Configuration();
+        Locale loc = Locale.forLanguageTag("en");
+        LocaleList ll = new LocaleList(loc);
+        config.setLocales(ll);
+        assertEquals(loc, config.locale);
+        assertEquals(ll, config.getLocales());
+        assertEquals(View.LAYOUT_DIRECTION_LTR, config.getLayoutDirection());
+    }
+
+    public void testSetLocales_oneRtl() {
+        Configuration config = new Configuration();
+        Locale loc = Locale.forLanguageTag("az-Arab");
+        LocaleList ll = new LocaleList(loc);
+        config.setLocales(ll);
+        assertEquals(loc, config.locale);
+        assertEquals(ll, config.getLocales());
+        assertEquals(View.LAYOUT_DIRECTION_RTL, config.getLayoutDirection());
+    }
+
+    public void testSetLocales_twoLocales() {
+        Configuration config = new Configuration();
+        Locale rtlLoc = Locale.forLanguageTag("az-Arab");
+        Locale ltrLoc = Locale.forLanguageTag("en");
+        LocaleList ll = LocaleList.forLanguageTags("az-Arab,en");
+        config.setLocales(ll);
+        assertEquals(rtlLoc, config.locale);
+        assertEquals(ll, config.getLocales());
+        assertEquals(View.LAYOUT_DIRECTION_RTL, config.getLayoutDirection());
+    }
+
+    public void testSetLocales_overridesLocale() {
+        Configuration config = new Configuration();
+        config.locale = Locale.forLanguageTag("en");
+        LocaleList ll = LocaleList.forLanguageTags("az-Arab,en");
+        config.setLocales(ll);
+
+        assertEquals(Locale.forLanguageTag("az-Arab"), config.locale);
+        assertEquals(ll, config.getLocales());
+        assertEquals(View.LAYOUT_DIRECTION_RTL, config.getLayoutDirection());
+    }
+
+    public void testSetLocales_overridesSetLocale() {
+        Configuration config = new Configuration();
+        config.setLocale(Locale.forLanguageTag("en"));
+        LocaleList ll = LocaleList.forLanguageTags("az-Arab,en");
+        config.setLocales(ll);
+
+        assertEquals(Locale.forLanguageTag("az-Arab"), config.locale);
+        assertEquals(ll, config.getLocales());
+        assertEquals(View.LAYOUT_DIRECTION_RTL, config.getLayoutDirection());
+    }
+
+    public void testSetLocale_overridesSetLocales() {
+        Configuration config = new Configuration();
+        config.setLocales(LocaleList.forLanguageTags("az-Arab,en"));
+        config.setLocale(Locale.ENGLISH);
+
+        assertEquals(Locale.ENGLISH, config.locale);
+        assertEquals(new LocaleList(Locale.ENGLISH), config.getLocales());
+        assertEquals(View.LAYOUT_DIRECTION_LTR, config.getLayoutDirection());
+    }
+
+    private Configuration createConfig(LocaleList list) {
+        Configuration config = createConfig();
+        config.setLocales(list);
+        return config;
     }
 
     private Configuration createConfig(Locale locale) {
+        Configuration config = createConfig();
+        config.locale = locale;
+        return config;
+    }
+
+    private Configuration createConfig() {
         Configuration config = new Configuration();
         config.fontScale = 13.37f;
         config.mcc = 0;
         config.mnc = 1;
-        config.locale = locale;
         config.touchscreen = Configuration.TOUCHSCREEN_STYLUS;
         config.keyboard = Configuration.KEYBOARD_UNDEFINED;
         config.keyboardHidden = Configuration.KEYBOARDHIDDEN_YES;
diff --git a/tests/tests/display/Android.mk b/tests/tests/display/Android.mk
index f17f580..dc815c3 100644
--- a/tests/tests/display/Android.mk
+++ b/tests/tests/display/Android.mk
@@ -16,17 +16,26 @@
 
 include $(CLEAR_VARS)
 
-# don't include this package in any target
-LOCAL_MODULE_TAGS := optional
-# and when built explicitly put it in the data partition
+# Don't include this package in any target
+LOCAL_MODULE_TAGS := tests
+# When built explicitly put it in the data partition
 LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
 
-LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner
+LOCAL_DEX_PREOPT := false
+
+LOCAL_PROGUARD_ENABLED := disabled
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
+
+# Tag this module as a cts_v2 test artifact
+LOCAL_COMPATIBILITY_SUITE := cts_v2
+
 LOCAL_PACKAGE_NAME := CtsDisplayTestCases
 
+LOCAL_CTS_MODULE_CONFIG := $(LOCAL_PATH)/Old$(CTS_MODULE_TEST_CONFIG)
+
 LOCAL_SDK_VERSION := current
 
 include $(BUILD_CTS_PACKAGE)
diff --git a/tests/tests/display/AndroidManifest.xml b/tests/tests/display/AndroidManifest.xml
index bf84219..22cc824 100644
--- a/tests/tests/display/AndroidManifest.xml
+++ b/tests/tests/display/AndroidManifest.xml
@@ -16,9 +16,8 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.display">
+    package="android.display.cts">
 
-    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
     <!-- For special presentation windows when testing mode switches. -->
     <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
 
@@ -26,12 +25,11 @@
         <uses-library android:name="android.test.runner" />
     </application>
 
-    <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="com.android.cts.display"
-                     android:label="CTS tests of android.view.display">
-        <meta-data
-            android:name="listener"
-            android:value="com.android.cts.runner.CtsTestRunListener" />
+    <!--  self-instrumenting test package. -->
+    <instrumentation
+        android:name="android.support.test.runner.AndroidJUnitRunner"
+        android:targetPackage="android.display.cts"
+        android:label="CTS tests of android.display">
     </instrumentation>
 
 </manifest>
diff --git a/tests/tests/display/AndroidTest.xml b/tests/tests/display/AndroidTest.xml
index dd42984..0296e8e 100644
--- a/tests/tests/display/AndroidTest.xml
+++ b/tests/tests/display/AndroidTest.xml
@@ -18,4 +18,9 @@
     <!-- Use a non-standard pattern, must match values in tests/tests/display/.../DisplayTest.java -->
     <option name="run-command:run-command" value="settings put global overlay_display_devices '181x161/214|181x161/214'" />
     <option name="run-command:teardown-command" value="settings put global overlay_display_devices &quot;&quot;" />
+    <option name="apk-installer:test-file-name" value="CtsDisplayTestCases.apk" />
+    <test class="com.android.tradefed.testtype.InstrumentationTest" >
+        <option name="package" value="android.display.cts" />
+        <option name="runner" value="android.support.test.runner.AndroidJUnitRunner" />
+    </test>
 </configuration>
diff --git a/tests/tests/display/OldAndroidTest.xml b/tests/tests/display/OldAndroidTest.xml
new file mode 100644
index 0000000..dd42984
--- /dev/null
+++ b/tests/tests/display/OldAndroidTest.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<configuration description="Base config for CTS package preparer">
+    <include name="common-config" />
+    <!-- Use a non-standard pattern, must match values in tests/tests/display/.../DisplayTest.java -->
+    <option name="run-command:run-command" value="settings put global overlay_display_devices '181x161/214|181x161/214'" />
+    <option name="run-command:teardown-command" value="settings put global overlay_display_devices &quot;&quot;" />
+</configuration>
diff --git a/tests/tests/gesture/Android.mk b/tests/tests/gesture/Android.mk
index 4a97931..0c4e023 100755
--- a/tests/tests/gesture/Android.mk
+++ b/tests/tests/gesture/Android.mk
@@ -16,15 +16,22 @@
 
 include $(CLEAR_VARS)
 
-# don't include this package in any target
-LOCAL_MODULE_TAGS := optional
-# and when built explicitly put it in the data partition
+# Don't include this package in any target
+LOCAL_MODULE_TAGS := tests
+# When built explicitly put it in the data partition
 LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
 
-LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner
+LOCAL_DEX_PREOPT := false
+
+LOCAL_PROGUARD_ENABLED := disabled
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
+
+# Tag this module as a cts_v2 test artifact
+LOCAL_COMPATIBILITY_SUITE := cts_v2
+
 LOCAL_PACKAGE_NAME := CtsGestureTestCases
 
 LOCAL_SDK_VERSION := current
diff --git a/tests/tests/gesture/AndroidManifest.xml b/tests/tests/gesture/AndroidManifest.xml
index b288cd2..fb3ee51 100755
--- a/tests/tests/gesture/AndroidManifest.xml
+++ b/tests/tests/gesture/AndroidManifest.xml
@@ -16,19 +16,17 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.gesture">
+    package="android.gesture.cts">
 
-    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
     <application>
         <uses-library android:name="android.test.runner" />
     </application>
 
     <!--  self-instrumenting test package. -->
-    <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="com.android.cts.gesture"
-                     android:label="CTS tests of android.gesture">
-        <meta-data android:name="listener"
-            android:value="com.android.cts.runner.CtsTestRunListener" />
+    <instrumentation
+        android:name="android.support.test.runner.AndroidJUnitRunner"
+        android:targetPackage="android.gesture.cts"
+        android:label="CTS tests of android.gesture">
     </instrumentation>
 
 </manifest>
diff --git a/tests/tests/gesture/AndroidTest.xml b/tests/tests/gesture/AndroidTest.xml
new file mode 100644
index 0000000..e0be497
--- /dev/null
+++ b/tests/tests/gesture/AndroidTest.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<configuration description="Base config for CTS package preparer">
+    <include name="common-config" />
+    <option name="apk-installer:test-file-name" value="CtsGestureTestCases.apk" />
+    <test class="com.android.tradefed.testtype.InstrumentationTest" >
+        <option name="package" value="android.gesture.cts" />
+        <option name="runner" value="android.support.test.runner.AndroidJUnitRunner" />
+    </test>
+</configuration>
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/TradefedTest.java b/tests/tests/graphics/res/drawable/custom_drawable.xml
similarity index 65%
copy from common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/TradefedTest.java
copy to tests/tests/graphics/res/drawable/custom_drawable.xml
index ab19369..cfb9bdb 100644
--- a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/TradefedTest.java
+++ b/tests/tests/graphics/res/drawable/custom_drawable.xml
@@ -1,5 +1,6 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * 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.
@@ -12,14 +13,8 @@
  * 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;
-
-import junit.framework.TestCase;
-
-public class TradefedTest extends TestCase {
-
-    // TODO(stuartscott): Add tests when there is something to test.
-
-}
+<drawable xmlns:android="http://schemas.android.com/apk/res/android"
+    class="android.graphics.drawable.cts.CustomDrawableTest$CustomDrawable"
+    android:color="#ffff0000" />
diff --git a/tests/tests/graphics/src/android/graphics/cts/PaintTest.java b/tests/tests/graphics/src/android/graphics/cts/PaintTest.java
index 80e0253..c813bdb 100644
--- a/tests/tests/graphics/src/android/graphics/cts/PaintTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/PaintTest.java
@@ -36,6 +36,7 @@
 import android.os.Build;
 import android.test.AndroidTestCase;
 import android.text.SpannedString;
+import android.util.LocaleList;
 import android.util.Log;
 
 import java.util.Locale;
@@ -600,30 +601,73 @@
         // Check default
         assertEquals(defaultLocale, p.getTextLocale());
 
-        // Check setter / getter
+        // Check setter / getters
         p.setTextLocale(Locale.US);
         assertEquals(Locale.US, p.getTextLocale());
+        assertEquals(new LocaleList(Locale.US), p.getTextLocales());
 
         p.setTextLocale(Locale.CHINESE);
         assertEquals(Locale.CHINESE, p.getTextLocale());
+        assertEquals(new LocaleList(Locale.CHINESE), p.getTextLocales());
 
         p.setTextLocale(Locale.JAPANESE);
         assertEquals(Locale.JAPANESE, p.getTextLocale());
+        assertEquals(new LocaleList(Locale.JAPANESE), p.getTextLocales());
 
         p.setTextLocale(Locale.KOREAN);
         assertEquals(Locale.KOREAN, p.getTextLocale());
+        assertEquals(new LocaleList(Locale.KOREAN), p.getTextLocales());
 
         // Check reverting back to default
         p.setTextLocale(defaultLocale);
         assertEquals(defaultLocale, p.getTextLocale());
+        assertEquals(new LocaleList(defaultLocale), p.getTextLocales());
 
         // Check that we cannot pass a null locale
         try {
             p.setTextLocale(null);
-            assertFalse(true);
+            fail("Setting the text locale to null should throw");
+        } catch (Throwable e) {
+            assertEquals(IllegalArgumentException.class, e.getClass());
         }
-        catch (IllegalArgumentException iae) {
-            // OK !!
+    }
+
+    public void testAccessTextLocales() {
+        Paint p = new Paint();
+
+        final LocaleList defaultLocales = LocaleList.getDefault();
+
+        // Check default
+        assertEquals(defaultLocales, p.getTextLocales());
+
+        // Check setter / getters for a one-member locale list
+        p.setTextLocales(new LocaleList(Locale.CHINESE));
+        assertEquals(Locale.CHINESE, p.getTextLocale());
+        assertEquals(new LocaleList(Locale.CHINESE), p.getTextLocales());
+
+        // Check setter / getters for a two-member locale list
+        p.setTextLocales(LocaleList.forLanguageTags("fr,de"));
+        assertEquals(Locale.forLanguageTag("fr"), p.getTextLocale());
+        assertEquals(LocaleList.forLanguageTags("fr,de"), p.getTextLocales());
+
+        // Check reverting back to default
+        p.setTextLocales(defaultLocales);
+        assertEquals(defaultLocales, p.getTextLocales());
+
+        // Check that we cannot pass a null locale list
+        try {
+            p.setTextLocales(null);
+            fail("Setting the text locale list to null should throw");
+        } catch (Throwable e) {
+            assertEquals(IllegalArgumentException.class, e.getClass());
+        }
+
+        // Check that we cannot pass an empty locale list
+        try {
+            p.setTextLocales(new LocaleList());
+            fail("Setting the text locale list to an empty list should throw");
+        } catch (Throwable e) {
+            assertEquals(IllegalArgumentException.class, e.getClass());
         }
     }
 
diff --git a/tests/tests/graphics/src/android/graphics/drawable/cts/AnimationDrawableTest.java b/tests/tests/graphics/src/android/graphics/drawable/cts/AnimationDrawableTest.java
index 28a3c6b..e8a5c42 100644
--- a/tests/tests/graphics/src/android/graphics/drawable/cts/AnimationDrawableTest.java
+++ b/tests/tests/graphics/src/android/graphics/drawable/cts/AnimationDrawableTest.java
@@ -275,6 +275,23 @@
         pollingCheckDrawable(THIRD_FRAME_INDEX, SECOND_FRAME_DURATION);
         // do not repeat
         assertStoppedAnimation(THIRD_FRAME_INDEX, THIRD_FRAME_DURATION);
+        runTestOnUiThread(new Runnable() {
+            public void run() {
+                // Set visible to false and restart to false
+                mAnimationDrawable.setVisible(false, false);
+            }
+        });
+        // Check that animation drawable stays on the same frame
+        assertStoppedAnimation(THIRD_FRAME_INDEX, THIRD_FRAME_DURATION);
+
+        runTestOnUiThread(new Runnable() {
+            public void run() {
+                // Set visible to true and restart to false
+                mAnimationDrawable.setVisible(true, false);
+            }
+        });
+        // Check that animation drawable stays on the same frame
+        assertStoppedAnimation(THIRD_FRAME_INDEX, THIRD_FRAME_DURATION);
     }
 
     public void testInflateCorrect() throws XmlPullParserException, IOException {
diff --git a/tests/tests/graphics/src/android/graphics/drawable/cts/CustomDrawableTest.java b/tests/tests/graphics/src/android/graphics/drawable/cts/CustomDrawableTest.java
new file mode 100644
index 0000000..2d3ffd9
--- /dev/null
+++ b/tests/tests/graphics/src/android/graphics/drawable/cts/CustomDrawableTest.java
@@ -0,0 +1,89 @@
+/*
+ * 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 android.graphics.drawable.cts;
+
+import com.android.cts.graphics.R;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import android.content.res.Resources;
+import android.content.res.Resources.Theme;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.ColorFilter;
+import android.graphics.PixelFormat;
+import android.graphics.drawable.Drawable;
+import android.test.AndroidTestCase;
+import android.util.AttributeSet;
+
+import java.io.IOException;
+
+public class CustomDrawableTest extends AndroidTestCase {
+
+    public void testInflation() {
+        Drawable dr = getContext().getDrawable(R.drawable.custom_drawable);
+        assertTrue(dr instanceof CustomDrawable);
+        assertEquals(Color.RED, ((CustomDrawable) dr).getColor());
+    }
+
+    public static class CustomDrawable extends Drawable {
+        private static final int[] ATTRS = new int[] { android.R.attr.color };
+
+        private int mColor;
+
+        @Override
+        public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)
+                throws XmlPullParserException, IOException {
+            super.inflate(r, parser, attrs, theme);
+
+            final TypedArray ta;
+            if (theme != null) {
+                ta = theme.obtainStyledAttributes(attrs, ATTRS, 0, 0);
+            } else {
+                ta = r.obtainAttributes(attrs, ATTRS);
+            }
+
+            mColor = ta.getColor(0, Color.BLACK);
+        }
+
+        public int getColor() {
+            return mColor;
+        }
+
+        @Override
+        public void draw(Canvas canvas) {
+
+        }
+
+        @Override
+        public void setAlpha(int alpha) {
+
+        }
+
+        @Override
+        public void setColorFilter(ColorFilter colorFilter) {
+
+        }
+
+        @Override
+        public int getOpacity() {
+            return PixelFormat.OPAQUE;
+        }
+    }
+}
diff --git a/tests/tests/graphics/src/android/graphics/drawable/cts/LayerDrawableTest.java b/tests/tests/graphics/src/android/graphics/drawable/cts/LayerDrawableTest.java
index a2f9ddf..1d7f623 100644
--- a/tests/tests/graphics/src/android/graphics/drawable/cts/LayerDrawableTest.java
+++ b/tests/tests/graphics/src/android/graphics/drawable/cts/LayerDrawableTest.java
@@ -640,6 +640,21 @@
         assertTrue(mockDrawable2.hasCalledOnBoundsChange());
     }
 
+    public void testJumpToCurrentState() {
+        MockDrawable mockDrawable1 = new MockDrawable();
+        MockDrawable mockDrawable2 = new MockDrawable();
+        Drawable[] array = new Drawable[] { mockDrawable1, mockDrawable2 };
+        LayerDrawable layerDrawable = new LayerDrawable(array);
+
+        assertFalse(mockDrawable1.hasCalledJumpToCurrentState());
+        assertFalse(mockDrawable2.hasCalledJumpToCurrentState());
+
+        layerDrawable.jumpToCurrentState();
+
+        assertTrue(mockDrawable1.hasCalledJumpToCurrentState());
+        assertTrue(mockDrawable2.hasCalledJumpToCurrentState());
+    }
+
     public void testSetLevel() {
         MockDrawable mockDrawable1 = new MockDrawable();
         MockDrawable mockDrawable2 = new MockDrawable();
@@ -1400,7 +1415,7 @@
         private boolean mCalledSetState = false;
         private boolean mCalledOnLevelChange = false;
         private boolean mCalledOnBoundsChange = false;
-
+        private boolean mCalledJumpToCurrentState = false;
 
         private boolean mCalledDraw = false;
 
@@ -1474,11 +1489,23 @@
             mCalledSetState = false;
             mCalledOnLevelChange = false;
             mCalledOnBoundsChange = false;
+            mCalledJumpToCurrentState = false;
 
             mCalledDraw = false;
         }
 
         @Override
+        public void jumpToCurrentState() {
+            super.jumpToCurrentState();
+
+            mCalledJumpToCurrentState = true;
+        }
+
+        public boolean hasCalledJumpToCurrentState() {
+            return mCalledJumpToCurrentState;
+        }
+
+        @Override
         protected boolean onStateChange(int[] state) {
             increasePadding();
             return mIsStateful;
diff --git a/tests/tests/hardware/Android.mk b/tests/tests/hardware/Android.mk
index 9c26d8a..950d61c 100644
--- a/tests/tests/hardware/Android.mk
+++ b/tests/tests/hardware/Android.mk
@@ -22,7 +22,7 @@
 
 LOCAL_MODULE_TAGS := tests
 
-LOCAL_STATIC_JAVA_LIBRARIES := ctsdeviceutil
+LOCAL_STATIC_JAVA_LIBRARIES := ctsdeviceutil compatibility-device-util
 
 LOCAL_SDK_VERSION := current
 
@@ -36,8 +36,6 @@
     src/android/hardware/cts/SensorTest.java \
     src/android/hardware/cts/SensorManagerStaticTest.java
 
-LOCAL_STATIC_JAVA_LIBRARIES := ctsdeviceutil
-
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
 
@@ -47,12 +45,14 @@
 
 LOCAL_MODULE_TAGS := tests
 
-LOCAL_STATIC_JAVA_LIBRARIES := ctsdeviceutil ctstestrunner mockito-target android-ex-camera2
+LOCAL_STATIC_JAVA_LIBRARIES := ctsdeviceutil compatibility-device-util ctstestrunner mockito-target android-ex-camera2
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src) $(call all-renderscript-files-under, src)
 
 LOCAL_PACKAGE_NAME := CtsHardwareTestCases
 
+LOCAL_CTS_MODULE_CONFIG := $(LOCAL_PATH)/Old$(CTS_MODULE_TEST_CONFIG)
+
 LOCAL_SDK_VERSION := current
 
 LOCAL_JAVA_LIBRARIES := android.test.runner
diff --git a/tests/tests/hardware/AndroidManifest.xml b/tests/tests/hardware/AndroidManifest.xml
index 031b19e..34a1475 100644
--- a/tests/tests/hardware/AndroidManifest.xml
+++ b/tests/tests/hardware/AndroidManifest.xml
@@ -72,6 +72,9 @@
             android:process=":camera2ActivityProcess">
         </activity>
 
+        <activity android:name="android.hardware.input.cts.InputCtsActivity"
+            android:label="InputCtsActivity" />
+
         <activity android:name="android.hardware.cts.FingerprintTestActivity"
             android:label="FingerprintTestActivity">
         </activity>
diff --git a/tests/tests/hardware/AndroidTest.xml b/tests/tests/hardware/OldAndroidTest.xml
similarity index 100%
rename from tests/tests/hardware/AndroidTest.xml
rename to tests/tests/hardware/OldAndroidTest.xml
diff --git a/tests/tests/hardware/res/raw/gamepad_press_a.json b/tests/tests/hardware/res/raw/gamepad_press_a.json
new file mode 100644
index 0000000..ff3ca4f
--- /dev/null
+++ b/tests/tests/hardware/res/raw/gamepad_press_a.json
@@ -0,0 +1,39 @@
+{
+    "id": 1,
+    "command": "register",
+    "name": "Odie (Test)",
+    "vid": 0x18d1,
+    "pid": 0x2c40,
+    "descriptor": [0x05, 0x01, 0x09, 0x05, 0xa1, 0x01, 0x85, 0x01, 0x05, 0x09, 0x0a, 0x01, 0x00,
+        0x0a, 0x02, 0x00, 0x0a, 0x04, 0x00, 0x0a, 0x05, 0x00, 0x0a, 0x07, 0x00, 0x0a, 0x08, 0x00,
+        0x0a, 0x0e, 0x00, 0x0a, 0x0f, 0x00, 0x0a, 0x0d, 0x00, 0x05, 0x0c, 0x0a, 0x24, 0x02, 0x0a,
+        0x23, 0x02, 0x15, 0x00, 0x25, 0x01, 0x75, 0x01, 0x95, 0x0b, 0x81, 0x02, 0x75, 0x01, 0x95,
+        0x01, 0x81, 0x03, 0x05, 0x01, 0x75, 0x04, 0x95, 0x01, 0x25, 0x07, 0x46, 0x3b, 0x01, 0x66,
+        0x14, 0x00, 0x09, 0x39, 0x81, 0x42, 0x66, 0x00, 0x00, 0x09, 0x01, 0xa1, 0x00, 0x09, 0x30,
+        0x09, 0x31, 0x09, 0x32, 0x09, 0x35, 0x05, 0x02, 0x09, 0xc5, 0x09, 0xc4, 0x15, 0x00, 0x26,
+        0xff, 0x00, 0x35, 0x00, 0x46, 0xff, 0x00, 0x75, 0x08, 0x95, 0x06, 0x81, 0x02, 0xc0, 0x85,
+        0x02, 0x05, 0x08, 0x0a, 0x01, 0x00, 0x0a, 0x02, 0x00, 0x0a, 0x03, 0x00, 0x0a, 0x04, 0x00,
+        0x15, 0x00, 0x25, 0x01, 0x75, 0x01, 0x95, 0x04, 0x91, 0x02, 0x75, 0x04, 0x95, 0x01, 0x91,
+        0x03, 0xc0, 0x05, 0x0c, 0x09, 0x01, 0xa1, 0x01, 0x85, 0x03, 0x05, 0x01, 0x09, 0x06, 0xa1,
+        0x02, 0x05, 0x06, 0x09, 0x20, 0x15, 0x00, 0x26, 0xff, 0x00, 0x75, 0x08, 0x95, 0x01, 0x81,
+        0x02, 0x06, 0xbc, 0xff, 0x0a, 0xad, 0xbd, 0x75, 0x08, 0x95, 0x06, 0x81, 0x02, 0xc0, 0xc0],
+    "report": [0x01, 0x00, 0x80, 0x90, 0x80, 0x7f, 0x73, 0x00, 0x00]
+}
+
+{
+    "id": 1,
+    "command": "report",
+    "report": [0x01, 0x01, 0x80, 0x90, 0x80, 0x7f, 0x73, 0x00, 0x00]
+}
+
+{
+    "id": 1,
+    "command": "delay",
+    "duration": 10
+}
+
+{
+    "id": 1,
+    "command": "report",
+    "report": [0x01, 0x00, 0x80, 0x90, 0x80, 0x7f, 0x73, 0x00, 0x00]
+}
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/PerformanceTest.java b/tests/tests/hardware/src/android/hardware/camera2/cts/PerformanceTest.java
index 4af88ca..1055169 100644
--- a/tests/tests/hardware/src/android/hardware/camera2/cts/PerformanceTest.java
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/PerformanceTest.java
@@ -16,7 +16,7 @@
 
 package android.hardware.camera2.cts;
 
-import static com.android.ex.camera2.blocking.BlockingSessionCallback.*;
+import static com.android.ex.camera2.blocking.BlockingSessionCallback.SESSION_CLOSED;
 
 import android.graphics.ImageFormat;
 import android.hardware.camera2.CameraAccessException;
@@ -32,20 +32,20 @@
 import android.hardware.camera2.cts.helpers.StaticMetadata.CheckLevel;
 import android.hardware.camera2.cts.testcases.Camera2SurfaceViewTestCase;
 import android.hardware.camera2.params.InputConfiguration;
-import android.util.Log;
-import android.util.Pair;
-import android.util.Size;
-import android.view.Surface;
-import android.cts.util.DeviceReportLog;
 import android.media.Image;
 import android.media.ImageReader;
 import android.media.ImageWriter;
 import android.os.ConditionVariable;
 import android.os.SystemClock;
+import android.util.Log;
+import android.util.Pair;
+import android.util.Size;
+import android.view.Surface;
 
-import com.android.cts.util.ResultType;
-import com.android.cts.util.ResultUnit;
-import com.android.cts.util.Stat;
+import com.android.compatibility.common.util.DeviceReportLog;
+import com.android.compatibility.common.util.ResultType;
+import com.android.compatibility.common.util.ResultUnit;
+import com.android.compatibility.common.util.Stat;
 import com.android.ex.camera2.blocking.BlockingSessionCallback;
 import com.android.ex.camera2.exceptions.TimeoutRuntimeException;
 
@@ -96,7 +96,7 @@
     @Override
     protected void tearDown() throws Exception {
         // Deliver the report to host will automatically clear the report log.
-        mReportLog.deliverReportToHost(getInstrumentation());
+        mReportLog.submit(getInstrumentation());
         super.tearDown();
     }
 
@@ -181,22 +181,22 @@
 
                 avgCameraLaunchTimes[counter] = Stat.getAverage(cameraLaunchTimes);
                 // Finish the data collection, report the KPIs.
-                mReportLog.printArray("Camera " + id
+                mReportLog.addValues("Camera " + id
                         + ": Camera open time", cameraOpenTimes,
                         ResultType.LOWER_BETTER, ResultUnit.MS);
-                mReportLog.printArray("Camera " + id
+                mReportLog.addValues("Camera " + id
                         + ": Camera configure stream time", configureStreamTimes,
                         ResultType.LOWER_BETTER, ResultUnit.MS);
-                mReportLog.printArray("Camera " + id
+                mReportLog.addValues("Camera " + id
                         + ": Camera start preview time", startPreviewTimes,
                         ResultType.LOWER_BETTER, ResultUnit.MS);
-                mReportLog.printArray("Camera " + id
+                mReportLog.addValues("Camera " + id
                         + ": Camera stop preview", stopPreviewTimes,
                         ResultType.LOWER_BETTER, ResultUnit.MS);
-                mReportLog.printArray("Camera " + id
+                mReportLog.addValues("Camera " + id
                         + ": Camera close time", cameraCloseTimes,
                         ResultType.LOWER_BETTER, ResultUnit.MS);
-                mReportLog.printArray("Camera " + id
+                mReportLog.addValues("Camera " + id
                         + ": Camera launch time", cameraLaunchTimes,
                         ResultType.LOWER_BETTER, ResultUnit.MS);
             }
@@ -206,7 +206,7 @@
             counter++;
         }
         if (mCameraIds.length != 0) {
-            mReportLog.printSummary("Camera launch average time for all cameras ",
+            mReportLog.setSummary("Camera launch average time for all cameras ",
                     Stat.getAverage(avgCameraLaunchTimes), ResultType.LOWER_BETTER, ResultUnit.MS);
         }
     }
@@ -298,19 +298,19 @@
                     // simulate real scenario (preview runs a bit)
                     waitForNumResults(previewResultListener, NUM_RESULTS_WAIT);
 
-                    blockingStopPreview();
+                    stopPreview();
 
                 }
-                mReportLog.printArray("Camera " + id
+                mReportLog.addValues("Camera " + id
                         + ": Camera capture latency", captureTimes,
                         ResultType.LOWER_BETTER, ResultUnit.MS);
                 // If any of the partial results do not contain AE and AF state, then no report
                 if (isPartialTimingValid) {
-                    mReportLog.printArray("Camera " + id
+                    mReportLog.addValues("Camera " + id
                             + ": Camera partial result latency", getPartialTimes,
                             ResultType.LOWER_BETTER, ResultUnit.MS);
                 }
-                mReportLog.printArray("Camera " + id
+                mReportLog.addValues("Camera " + id
                         + ": Camera capture result latency", getResultTimes,
                         ResultType.LOWER_BETTER, ResultUnit.MS);
 
@@ -325,7 +325,7 @@
 
         // Result will not be reported in CTS report if no summary is printed.
         if (mCameraIds.length != 0) {
-            mReportLog.printSummary("Camera capture result average latency for all cameras ",
+            mReportLog.setSummary("Camera capture result average latency for all cameras ",
                     Stat.getAverage(avgResultTimes), ResultType.LOWER_BETTER, ResultUnit.MS);
         }
     }
@@ -480,13 +480,13 @@
             reprocessType = " opaque reprocessing ";
         }
 
-        mReportLog.printArray("Camera " + mCamera.getId()
+        mReportLog.addValues("Camera " + mCamera.getId()
                 + ":" + reprocessType + " max capture timestamp gaps", maxCaptureGapsMs,
                 ResultType.LOWER_BETTER, ResultUnit.MS);
-        mReportLog.printArray("Camera " + mCamera.getId()
+        mReportLog.addValues("Camera " + mCamera.getId()
                 + ":" + reprocessType + "capture average frame duration", averageFrameDurationMs,
                 ResultType.LOWER_BETTER, ResultUnit.MS);
-        mReportLog.printSummary("Camera reprocessing average max capture timestamp gaps for Camera "
+        mReportLog.setSummary("Camera reprocessing average max capture timestamp gaps for Camera "
                 + mCamera.getId(), Stat.getAverage(maxCaptureGapsMs), ResultType.LOWER_BETTER,
                 ResultUnit.MS);
 
@@ -564,17 +564,17 @@
 
         // Report the performance data
         if (asyncMode) {
-            mReportLog.printArray("Camera " + mCamera.getId()
+            mReportLog.addValues("Camera " + mCamera.getId()
                     + ":" + reprocessType + "capture latency", getImageLatenciesMs,
                     ResultType.LOWER_BETTER, ResultUnit.MS);
-            mReportLog.printSummary("Camera reprocessing average latency for Camera " +
+            mReportLog.setSummary("Camera reprocessing average latency for Camera " +
                     mCamera.getId(), Stat.getAverage(getImageLatenciesMs), ResultType.LOWER_BETTER,
                     ResultUnit.MS);
         } else {
-            mReportLog.printArray("Camera " + mCamera.getId()
+            mReportLog.addValues("Camera " + mCamera.getId()
                     + ":" + reprocessType + "shot to shot latency", getImageLatenciesMs,
                     ResultType.LOWER_BETTER, ResultUnit.MS);
-            mReportLog.printSummary("Camera reprocessing shot to shot average latency for Camera " +
+            mReportLog.setSummary("Camera reprocessing shot to shot average latency for Camera " +
                     mCamera.getId(), Stat.getAverage(getImageLatenciesMs), ResultType.LOWER_BETTER,
                     ResultUnit.MS);
         }
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/StaticMetadataCollectionTest.java b/tests/tests/hardware/src/android/hardware/camera2/cts/StaticMetadataCollectionTest.java
index 283f09b..4f4c5fe 100644
--- a/tests/tests/hardware/src/android/hardware/camera2/cts/StaticMetadataCollectionTest.java
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/StaticMetadataCollectionTest.java
@@ -17,13 +17,13 @@
 package android.hardware.camera2.cts;
 
 import android.content.pm.PackageManager;
-import android.cts.util.DeviceReportLog;
-import android.hardware.camera2.cts.testcases.Camera2SurfaceViewTestCase;
 import android.hardware.camera2.cts.helpers.CameraMetadataGetter;
+import android.hardware.camera2.cts.testcases.Camera2SurfaceViewTestCase;
 import android.util.Log;
 
-import com.android.cts.util.ResultType;
-import com.android.cts.util.ResultUnit;
+import com.android.compatibility.common.util.DeviceReportLog;
+import com.android.compatibility.common.util.ResultType;
+import com.android.compatibility.common.util.ResultUnit;
 
 import org.json.JSONArray;
 import org.json.JSONObject;
@@ -48,7 +48,7 @@
     @Override
     protected void tearDown() throws Exception {
         // Deliver the report to host will automatically clear the report log.
-        mReportLog.deliverReportToHost(getInstrumentation());
+        mReportLog.submit(getInstrumentation());
         super.tearDown();
     }
 
@@ -80,7 +80,7 @@
                 Log.e(TAG, "Unable to close camera info getter " + e.getMessage());
             }
 
-            mReportLog.printSummary("Camera data collection for static info and capture request"
+            mReportLog.setSummary("Camera data collection for static info and capture request"
                     + " templates",
                     0.0, ResultType.NEUTRAL, ResultUnit.NONE);
         }
@@ -88,11 +88,11 @@
     }
 
     private void dumpDoubleAsCtsResult(String name, double value) {
-        mReportLog.printValue(name, value, ResultType.NEUTRAL, ResultUnit.NONE);
+        mReportLog.addValue(name, value, ResultType.NEUTRAL, ResultUnit.NONE);
     }
 
     public void dumpDoubleArrayAsCtsResult(String name, double[] values) {
-        mReportLog.printArray(name, values, ResultType.NEUTRAL, ResultUnit.NONE);
+        mReportLog.addValues(name, values, ResultType.NEUTRAL, ResultUnit.NONE);
     }
 
     private double getJsonValueAsDouble(String name, Object obj) throws Exception {
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/JitterVerification.java b/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/JitterVerification.java
index d246ec5..e94ddbf 100644
--- a/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/JitterVerification.java
+++ b/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/JitterVerification.java
@@ -23,15 +23,13 @@
 
 import android.util.Log;
 import android.hardware.Sensor;
-import android.hardware.cts.helpers.SensorCtsHelper;
 import android.hardware.cts.helpers.SensorStats;
 import android.hardware.cts.helpers.TestSensorEnvironment;
 import android.hardware.cts.helpers.TestSensorEvent;
 import android.util.SparseIntArray;
 
-import com.android.cts.util.StatisticsUtils;
+import com.android.compatibility.common.util.Stat;
 
-import java.util.ArrayList;
 import java.util.LinkedList;
 import java.util.List;
 
@@ -99,8 +97,8 @@
             return;
         }
 
-        List<Double> jitters = getJitterValues();
-        double jitter95PercentileNs = SensorCtsHelper.get95PercentileValue(jitters);
+        double[] jitters = getJitterValues();
+        double jitter95PercentileNs = Stat.get95PercentileValue(jitters);
         long firstTimestamp = mTimestamps.get(0);
         long lastTimestamp = mTimestamps.get(timestampsCount - 1);
         long measuredPeriodNs = (lastTimestamp - firstTimestamp) / (timestampsCount - 1);
@@ -140,15 +138,16 @@
     /**
      * Get the list of all jitter values. Exposed for unit testing.
      */
-    List<Double> getJitterValues() {
-        List<Long> deltas = new ArrayList<Long>(mTimestamps.size() - 1);
-        for (int i = 1; i < mTimestamps.size(); i++) {
-            deltas.add(mTimestamps.get(i) - mTimestamps.get(i - 1));
+    double[] getJitterValues() {
+        int length = mTimestamps.size() - 1;
+        double[] deltas = new double[length];
+        for (int i = 0; i < length; i++) {
+            deltas[i] = mTimestamps.get(i + 1) - mTimestamps.get(i);
         }
-        double deltaMean = StatisticsUtils.getMean(deltas);
-        List<Double> jitters = new ArrayList<Double>(deltas.size());
-        for (long delta : deltas) {
-            jitters.add(Math.abs(delta - deltaMean));
+        double deltaMean = Stat.getAverage(deltas);
+        double[] jitters = new double[length];
+        for (int i = 0; i < length; i++) {
+            jitters[i] = Math.abs(deltas[i] - deltaMean);
         }
         return jitters;
     }
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/JitterVerificationTest.java b/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/JitterVerificationTest.java
index 50e288c..5cf747f 100644
--- a/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/JitterVerificationTest.java
+++ b/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/JitterVerificationTest.java
@@ -24,7 +24,6 @@
 
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.List;
 
 /**
  * Tests for {@link EventOrderingVerification}.
@@ -73,31 +72,30 @@
     public void testCalculateJitter() {
         long[] timestamps = new long[]{0, 1, 2, 3, 4};
         JitterVerification verification = getVerification(1, timestamps);
-        List<Double> jitterValues = verification.getJitterValues();
-        assertEquals(4, jitterValues.size());
-        assertEquals(0.0, jitterValues.get(0));
-        assertEquals(0.0, jitterValues.get(1));
-        assertEquals(0.0, jitterValues.get(2));
-        assertEquals(0.0, jitterValues.get(3));
+        double[] jitterValues = verification.getJitterValues();
+        assertEquals(4, jitterValues.length);
+        assertEquals(0.0, jitterValues[0]);
+        assertEquals(0.0, jitterValues[1]);
+        assertEquals(0.0, jitterValues[2]);
+        assertEquals(0.0, jitterValues[3]);
 
         timestamps = new long[]{0, 0, 2, 4, 4};
         verification = getVerification(1, timestamps);
         jitterValues = verification.getJitterValues();
-        assertEquals(4, jitterValues.size());
-        assertEquals(1.0, jitterValues.get(0));
-        assertEquals(1.0, jitterValues.get(1));
-        assertEquals(1.0, jitterValues.get(2));
-        assertEquals(1.0, jitterValues.get(3));
+        assertEquals(4, jitterValues.length);
+        assertEquals(1.0, jitterValues[0]);
+        assertEquals(1.0, jitterValues[1]);
+        assertEquals(1.0, jitterValues[2]);
+        assertEquals(1.0, jitterValues[3]);
 
         timestamps = new long[]{0, 1, 4, 9, 16};
         verification = getVerification(1, timestamps);
         jitterValues = verification.getJitterValues();
-        assertEquals(4, jitterValues.size());
-        assertEquals(4, jitterValues.size());
-        assertEquals(3.0, jitterValues.get(0));
-        assertEquals(1.0, jitterValues.get(1));
-        assertEquals(1.0, jitterValues.get(2));
-        assertEquals(3.0, jitterValues.get(3));
+        assertEquals(4, jitterValues.length);
+        assertEquals(3.0, jitterValues[0]);
+        assertEquals(1.0, jitterValues[1]);
+        assertEquals(1.0, jitterValues[2]);
+        assertEquals(3.0, jitterValues[3]);
     }
 
     private static JitterVerification getVerification(int threshold, long ... timestamps) {
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/TradefedTest.java b/tests/tests/hardware/src/android/hardware/input/cts/InputCallback.java
similarity index 67%
rename from common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/TradefedTest.java
rename to tests/tests/hardware/src/android/hardware/input/cts/InputCallback.java
index ab19369..accdcaf 100644
--- a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/TradefedTest.java
+++ b/tests/tests/hardware/src/android/hardware/input/cts/InputCallback.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2014 The Android Open Source Project
+ * Copyright 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.
@@ -14,12 +14,12 @@
  * limitations under the License.
  */
 
-package com.android.compatibility.common.tradefed;
+package android.hardware.input.cts;
 
-import junit.framework.TestCase;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
 
-public class TradefedTest extends TestCase {
-
-    // TODO(stuartscott): Add tests when there is something to test.
-
+public interface InputCallback {
+    public void onKeyEvent(KeyEvent ev);
+    public void onMotionEvent(MotionEvent ev);
 }
diff --git a/tests/tests/hardware/src/android/hardware/input/cts/InputCtsActivity.java b/tests/tests/hardware/src/android/hardware/input/cts/InputCtsActivity.java
new file mode 100644
index 0000000..b16cadb
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/input/cts/InputCtsActivity.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 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 android.hardware.input.cts;
+
+import android.app.Activity;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class InputCtsActivity extends Activity {
+    private InputCallback mInputCallback;
+
+    @Override
+    public boolean dispatchGenericMotionEvent(MotionEvent ev) {
+        if (mInputCallback != null) {
+            mInputCallback.onMotionEvent(ev);
+        }
+        return true;
+    }
+
+    @Override
+    public boolean dispatchTouchEvent(MotionEvent ev) {
+        if (mInputCallback != null) {
+            mInputCallback.onMotionEvent(ev);
+        }
+        return true;
+    }
+
+    @Override
+    public boolean dispatchTrackballEvent(MotionEvent ev) {
+        if (mInputCallback != null) {
+            mInputCallback.onMotionEvent(ev);
+        }
+        return true;
+    }
+
+    @Override
+    public boolean dispatchKeyEvent(KeyEvent ev) {
+        if (mInputCallback != null) {
+            mInputCallback.onKeyEvent(ev);
+        }
+        return true;
+    }
+
+    public void setInputCallback(InputCallback callback) {
+        mInputCallback = callback;
+    }
+}
diff --git a/tests/tests/hardware/src/android/hardware/input/cts/tests/GamepadTestCase.java b/tests/tests/hardware/src/android/hardware/input/cts/tests/GamepadTestCase.java
new file mode 100644
index 0000000..92fba12
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/input/cts/tests/GamepadTestCase.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 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 android.hardware.input.cts.tests;
+
+import android.util.Log;
+import android.view.KeyEvent;
+
+import java.io.Writer;
+import java.util.List;
+
+import com.android.cts.hardware.R;
+
+public class GamepadTestCase extends InputTestCase {
+    private static final String TAG = "GamepadTests";
+
+    public void testButtonA() throws Exception {
+        sendHidCommands(R.raw.gamepad_press_a);
+        assertReceivedKeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BUTTON_A);
+        assertReceivedKeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BUTTON_A);
+        assertNoMoreEvents();
+    }
+}
diff --git a/tests/tests/hardware/src/android/hardware/input/cts/tests/InputTestCase.java b/tests/tests/hardware/src/android/hardware/input/cts/tests/InputTestCase.java
new file mode 100644
index 0000000..fba5f51
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/input/cts/tests/InputTestCase.java
@@ -0,0 +1,221 @@
+/*
+ * Copyright 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 android.hardware.input.cts.tests;
+
+import android.app.UiAutomation;
+import android.hardware.input.cts.InputCtsActivity;
+import android.hardware.input.cts.InputCallback;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.test.ActivityInstrumentationTestCase2;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.InputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+import java.util.List;
+import java.util.UUID;
+
+public class InputTestCase extends ActivityInstrumentationTestCase2<InputCtsActivity> {
+    private static final String TAG = "InputTestCase";
+    private static final String HID_EXECUTABLE = "hid";
+    private static final int SHELL_UID = 2000;
+    private static final String[] KEY_ACTIONS = {"DOWN", "UP", "MULTIPLE"};
+
+    private File mFifo;
+    private Writer mWriter;
+
+    private BlockingQueue<KeyEvent> mKeys;
+    private BlockingQueue<MotionEvent> mMotions;
+    private InputListener mInputListener;
+
+    public InputTestCase() {
+        super(InputCtsActivity.class);
+        mKeys = new LinkedBlockingQueue<KeyEvent>();
+        mMotions = new LinkedBlockingQueue<MotionEvent>();
+        mInputListener = new InputListener();
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mFifo = setupFifo();
+        clearKeys();
+        clearMotions();
+        getActivity().setInputCallback(mInputListener);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        if (mFifo != null) {
+            mFifo.delete();
+            mFifo = null;
+        }
+        closeQuietly(mWriter);
+        mWriter = null;
+        super.tearDown();
+    }
+
+    /**
+     * Sends the HID commands designated by the given resource id.
+     * The commands must be in the format expected by the `hid` shell command.
+     *
+     * @param id The resource id from which to load the HID commands. This must be a "raw"
+     * resource.
+     */
+    public void sendHidCommands(int id) {
+        try {
+            Writer w = getWriter();
+            w.write(getEvents(id));
+            w.flush();
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Asserts that the application received a {@link android.view.KeyEvent} with the given action
+     * and keycode.
+     *
+     * If other KeyEvents are received by the application prior to the expected KeyEvent, or no
+     * KeyEvents are received within a reasonable amount of time, then this will throw an
+     * AssertionFailedError.
+     *
+     * @param action The action to expect on the next KeyEvent
+     * (e.g. {@link android.view.KeyEvent#ACTION_DOWN}).
+     * @param keyCode The expected key code of the next KeyEvent.
+     */
+    public void assertReceivedKeyEvent(int action, int keyCode) {
+        KeyEvent k = waitForKey();
+        if (k == null) {
+            fail("Timed out waiting for " + KeyEvent.keyCodeToString(keyCode)
+                    + " with action " + KEY_ACTIONS[action]);
+            return;
+        }
+        assertEquals(action, k.getAction());
+        assertEquals(keyCode, k.getKeyCode());
+    }
+
+    /**
+     * Asserts that no more events have been received by the application.
+     *
+     * If any more events have been received by the application, this throws an
+     * AssertionFailedError.
+     */
+    public void assertNoMoreEvents() {
+        KeyEvent key;
+        MotionEvent motion;
+        if ((key = mKeys.poll()) != null) {
+            fail("Extraneous key events generated: " + key);
+        }
+        if ((motion = mMotions.poll()) != null) {
+            fail("Extraneous motion events generated: " + motion);
+        }
+    }
+
+    private KeyEvent waitForKey() {
+        try {
+            return mKeys.poll(1, TimeUnit.SECONDS);
+        } catch (InterruptedException e) {
+            return null;
+        }
+    }
+
+    private void clearKeys() {
+        mKeys.clear();
+    }
+
+    private void clearMotions() {
+        mMotions.clear();
+    }
+
+    private File setupFifo() throws ErrnoException {
+        File dir = getActivity().getCacheDir();
+        String filename = dir.getAbsolutePath() + File.separator +  UUID.randomUUID().toString();
+        Os.mkfifo(filename, 0666);
+        File f = new File(filename);
+        return f;
+    }
+
+    private Writer getWriter() throws IOException {
+        if (mWriter == null) {
+            UiAutomation ui = getInstrumentation().getUiAutomation();
+            ui.executeShellCommand("hid " + mFifo.getAbsolutePath());
+            mWriter = new FileWriter(mFifo);
+        }
+        return mWriter;
+    }
+
+    private String getEvents(int id) throws IOException {
+        InputStream is =
+            getInstrumentation().getTargetContext().getResources().openRawResource(id);
+        return readFully(is);
+    }
+
+
+    private static void closeQuietly(AutoCloseable closeable) {
+        if (closeable != null) {
+            try {
+                closeable.close();
+            } catch (RuntimeException rethrown) {
+                throw rethrown;
+            } catch (Exception ignored) { }
+        }
+    }
+
+    private static String readFully(InputStream is) throws IOException {
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        int read = 0;
+        byte[] buffer = new byte[1024];
+        while ((read = is.read(buffer)) >= 0) {
+            baos.write(buffer, 0, read);
+        }
+        return baos.toString();
+    }
+
+    private class InputListener implements InputCallback {
+        public void onKeyEvent(KeyEvent ev) {
+            boolean done = false;
+            do {
+                try {
+                    mKeys.put(new KeyEvent(ev));
+                    done = true;
+                } catch (InterruptedException ignore) { }
+            } while (!done);
+        }
+
+        public void onMotionEvent(MotionEvent ev) {
+            boolean done = false;
+            do {
+                try {
+                    mMotions.put(MotionEvent.obtain(ev));
+                    done = true;
+                } catch (InterruptedException ignore) { }
+            } while (!done);
+        }
+    }
+}
diff --git a/tests/tests/media/Android.mk b/tests/tests/media/Android.mk
index 13daca6..bbf1f04 100644
--- a/tests/tests/media/Android.mk
+++ b/tests/tests/media/Android.mk
@@ -34,12 +34,13 @@
 LOCAL_MODULE_TAGS := optional
 # and when built explicitly put it in the data partition
 LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+LOCAL_DEX_PREOPT := false
+LOCAL_PROGUARD_ENABLED := disabled
 
 # include both the 32 and 64 bit versions
 LOCAL_MULTILIB := both
 
-LOCAL_STATIC_JAVA_LIBRARIES := \
-    ctsmediautil ctsdeviceutil ctstestserver ctstestrunner
+LOCAL_STATIC_JAVA_LIBRARIES := ctsmediautil ctsdeviceutil compatibility-device-util ctstestserver ctstestrunner
 
 LOCAL_JNI_SHARED_LIBRARIES := libctsmediacodec_jni libaudio_jni
 
@@ -51,6 +52,8 @@
 #LOCAL_SDK_VERSION := current
 LOCAL_JAVA_LIBRARIES += android.test.runner org.apache.http.legacy
 
-include $(BUILD_CTS_PACKAGE)
+LOCAL_COMPATIBILITY_SUITE := cts_v2
+
+include $(BUILD_PACKAGE)
 
 include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tests/tests/media/AndroidTest.xml b/tests/tests/media/AndroidTest.xml
new file mode 100644
index 0000000..64a048b
--- /dev/null
+++ b/tests/tests/media/AndroidTest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<configuration description="Base config for CTS package preparer">
+    <include name="common-config" />
+    <option name="apk-installer:test-file-name" value="CtsMediaTestCases.apk" />
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.DynamicConfigPusher">
+        <option name="target" value="device" />
+        <option name="module-name" value="CtsMediaTestCases"/>
+        <option name="version-name" value="1.0"/>
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.InstrumentationTest" >
+        <option name="package" value="com.android.cts.media" />
+        <option name="runner" value="android.support.test.runner.AndroidJUnitRunner" />
+    </test>
+</configuration>
diff --git a/tests/tests/media/DynamicConfig.xml b/tests/tests/media/DynamicConfig.xml
new file mode 100644
index 0000000..702157d
--- /dev/null
+++ b/tests/tests/media/DynamicConfig.xml
@@ -0,0 +1,29 @@
+<!-- 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.
+-->
+
+<DynamicConfig>
+    <Config key="DecoderTest-VIDEO_URL">http://redirector.gvt1.com/videoplayback?id=c80658495af60617&amp;itag=18&amp;source=youtube&amp;ip=0.0.0.0&amp;ipbits=0&amp;expire=19000000000&amp;sparams=ip,ipbits,expire,id,itag,source&amp;signature=46A04ED550CA83B79B60060BA80C79FDA5853D26.49582D382B4A9AFAA163DED38D2AE531D85603C0&amp;key=ik0&amp;user=android-device-test</Config>
+    <Config key="StreamingMediaPlayerTest-testHTTP_H264Base_AAC_Video1">http://redirector.gvt1.com/videoplayback?id=271de9756065677e&amp;itag=18&amp;source=youtube&amp;ip=0.0.0.0&amp;ipbits=0&amp;expire=19000000000&amp;sparams=ip,ipbits,expire,id,itag,source&amp;signature=667AEEF54639926662CE62361400B8F8C1753B3F.15F46C382C68A9F121BA17BF1F56BEDEB4B06091&amp;key=ik0&amp;user=android-device-test</Config>
+    <Config key="StreamingMediaPlayerTest-testHTTP_H264Base_AAC_Video2">http://www.youtube.com/api/manifest/hls_variant/id/0168724d02bd9945/itag/5/source/youtube/playlist_type/DVR/ip/0.0.0.0/ipbits/0/expire/19000000000/sparams/ip,ipbits,expire,id,itag,source,playlist_type/signature/773AB8ACC68A96E5AA481996AD6A1BBCB70DCB87.95733B544ACC5F01A1223A837D2CF04DF85A3360/key/ik0/file/m3u8</Config>
+    <Config key="MediaCodecCapabilitiesTest-testAvcHigh31">http://redirector.gvt1.com/videoplayback?id=271de9756065677e&amp;itag=22&amp;source=youtube&amp;user=android-device-test&amp;sparams=ip,ipbits,expire,id,itag,source,user&amp;ip=0.0.0.0&amp;ipbits=0&amp;expire=19000000000&amp;signature=179525311196616BD8E1381759B0E5F81A9E91B5.C4A50E44059FEBCC6BBC78E3B3A4E0E0065777&amp;key=ik0</Config>
+    <Config key="StreamingMediaPlayerTest-testHTTP_MPEG4SP_AAC_Video2">http://redirector.gvt1.com/videoplayback?id=c80658495af60617&amp;itag=17&amp;source=youtube&amp;ip=0.0.0.0&amp;ipbits=0&amp;expire=19000000000&amp;sparams=ip,ipbits,expire,id,itag,source&amp;signature=70E979A621001201BC18622BDBF914FA870BDA40.6E78890B80F4A33A18835F775B1FF64F0A4D0003&amp;key=ik0&amp;user=android-device-test</Config>
+    <Config key="StreamingMediaPlayerTest-testHTTP_MPEG4SP_AAC_Video1">http://redirector.gvt1.com/videoplayback?id=271de9756065677e&amp;itag=17&amp;source=youtube&amp;ip=0.0.0.0&amp;ipbits=0&amp;expire=19000000000&amp;sparams=ip,ipbits,expire,id,itag,source&amp;signature=837198AAADF6F36BA6B2D324F690A7C5B7AFE3FF.7138CE5E36D718220726C1FC305497FF2D082249&amp;key=ik0&amp;user=android-device-test</Config>
+    <Config key="MediaCodecCapabilitiesTest-testAvcBaseline12">http://redirector.gvt1.com/videoplayback?id=271de9756065677e&amp;itag=160&amp;source=youtube&amp;user=android-device-test&amp;sparams=ip,ipbits,expire,id,itag,source,user&amp;ip=0.0.0.0&amp;ipbits=0&amp;expire=19000000000&amp;signature=9EDCA0B395B8A949C511FD5E59B9F805CFF797FD.702DE9BA7AF96785FD6930AD2DD693A0486C880E&amp;key=ik0</Config>
+    <Config key="DecoderTest-AUDIO_URL">http://redirector.gvt1.com/videoplayback?id=c80658495af60617&amp;itag=18&amp;source=youtube&amp;ip=0.0.0.0&amp;ipbits=0&amp;expire=19000000000&amp;sparams=ip,ipbits,expire,id,itag,source&amp;signature=46A04ED550CA83B79B60060BA80C79FDA5853D26.49582D382B4A9AFAA163DED38D2AE531D85603C0&amp;key=ik0&amp;user=android-device-test</Config>
+    <Config key="MediaCodecCapabilitiesTest-testAvcBaseline30">http://redirector.gvt1.com/videoplayback?id=271de9756065677e&amp;itag=18&amp;source=youtube&amp;user=android-device-test&amp;sparams=ip,ipbits,expire,id,itag,source,user&amp;ip=0.0.0.0&amp;ipbits=0&amp;expire=19000000000&amp;signature=7DCDE3A6594D0B91A27676A3CDC3A87B149F82EA.7A83031734CB1EDCE06766B6228842F954927960&amp;key=ik0</Config>
+    <Config key="MediaCodecCapabilitiesTest-testAvcHigh40">http://redirector.gvt1.com/videoplayback?id=271de9756065677e&amp;itag=137&amp;source=youtube&amp;user=android-device-test&amp;sparams=ip,ipbits,expire,id,itag,source,user&amp;ip=0.0.0.0&amp;ipbits=0&amp;expire=19000000000&amp;signature=B0976085596DD42DEA3F08307F76587241CB132B.043B719C039E8B92F45391ADC0BE3665E2332930&amp;key=ik0</Config>
+    <Config key="StreamingMediaPlayerTest-testHTTP_H263_AMR_Video2">http://redirector.gvt1.com/videoplayback?id=c80658495af60617&amp;itag=13&amp;source=youtube&amp;ip=0.0.0.0&amp;ipbits=0&amp;expire=19000000000&amp;sparams=ip,ipbits,expire,id,itag,source&amp;signature=508D82AB36939345BF6B8D0623CB6CABDD9C64C3.9B3336A96846DF38E5343C46AA57F6CF2956E427&amp;key=ik0&amp;user=android-device-test</Config>
+    <Config key="StreamingMediaPlayerTest-testHTTP_H263_AMR_Video1">http://redirector.gvt1.com/videoplayback?id=271de9756065677e&amp;itag=13&amp;source=youtube&amp;ip=0.0.0.0&amp;ipbits=0&amp;expire=19000000000&amp;sparams=ip,ipbits,expire,id,itag,source&amp;signature=5729247E22691EBB3E804DDD523EC42DC17DD8CE.443B81C1E8E6D64E4E1555F568BA46C206507D78&amp;key=ik0&amp;user=android-device-test</Config>
+</DynamicConfig>
diff --git a/tests/tests/media/src/android/media/cts/AudioRecordTest.java b/tests/tests/media/src/android/media/cts/AudioRecordTest.java
index b1ee3f5..5b7b031 100644
--- a/tests/tests/media/src/android/media/cts/AudioRecordTest.java
+++ b/tests/tests/media/src/android/media/cts/AudioRecordTest.java
@@ -16,9 +16,6 @@
 
 package android.media.cts;
 
-import java.nio.ByteBuffer;
-import java.util.ArrayList;
-
 import android.content.pm.PackageManager;
 import android.cts.util.CtsAndroidTestCase;
 import android.media.AudioFormat;
@@ -31,11 +28,13 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
-import android.util.Log;
 
-import com.android.cts.util.ReportLog;
-import com.android.cts.util.ResultType;
-import com.android.cts.util.ResultUnit;
+import com.android.compatibility.common.util.DeviceReportLog;
+import com.android.compatibility.common.util.ResultType;
+import com.android.compatibility.common.util.ResultUnit;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
 
 public class AudioRecordTest extends CtsAndroidTestCase {
     private final static String TAG = "AudioRecordTest";
@@ -944,38 +943,39 @@
         }
 
         // report this
-        ReportLog log = getReportLog();
-        log.printValue(reportName + ": startRecording lag", firstSampleTime - startTime,
+        DeviceReportLog log = new DeviceReportLog();
+        log.addValue(reportName + ": startRecording lag", firstSampleTime - startTime,
                 ResultType.LOWER_BETTER, ResultUnit.MS);
-        log.printValue(reportName + ": stop execution time", stopTime - stopRequestTime,
+        log.addValue(reportName + ": stop execution time", stopTime - stopRequestTime,
                 ResultType.LOWER_BETTER, ResultUnit.MS);
-        log.printValue(reportName + ": Total record time expected", TEST_TIME_MS,
+        log.addValue(reportName + ": Total record time expected", TEST_TIME_MS,
                 ResultType.NEUTRAL, ResultUnit.MS);
-        log.printValue(reportName + ": Total record time actual", endTime - firstSampleTime,
+        log.addValue(reportName + ": Total record time actual", endTime - firstSampleTime,
                 ResultType.NEUTRAL, ResultUnit.MS);
-        log.printValue(reportName + ": Total markers expected", markerPeriods,
+        log.addValue(reportName + ": Total markers expected", markerPeriods,
                 ResultType.NEUTRAL, ResultUnit.COUNT);
-        log.printValue(reportName + ": Total markers actual", markerList.size(),
+        log.addValue(reportName + ": Total markers actual", markerList.size(),
                 ResultType.NEUTRAL, ResultUnit.COUNT);
-        log.printValue(reportName + ": Total periods expected", updatePeriods,
+        log.addValue(reportName + ": Total periods expected", updatePeriods,
                 ResultType.NEUTRAL, ResultUnit.COUNT);
-        log.printValue(reportName + ": Total periods actual", periodicList.size(),
+        log.addValue(reportName + ": Total periods actual", periodicList.size(),
                 ResultType.NEUTRAL, ResultUnit.COUNT);
-        log.printValue(reportName + ": Average Marker diff", markerStat.getAvg(),
+        log.addValue(reportName + ": Average Marker diff", markerStat.getAvg(),
                 ResultType.LOWER_BETTER, ResultUnit.MS);
-        log.printValue(reportName + ": Maximum Marker abs diff", markerStat.getMaxAbs(),
+        log.addValue(reportName + ": Maximum Marker abs diff", markerStat.getMaxAbs(),
                 ResultType.LOWER_BETTER, ResultUnit.MS);
-        log.printValue(reportName + ": Average Marker abs diff", markerStat.getAvgAbs(),
+        log.addValue(reportName + ": Average Marker abs diff", markerStat.getAvgAbs(),
                 ResultType.LOWER_BETTER, ResultUnit.MS);
-        log.printValue(reportName + ": Average Periodic diff", periodicStat.getAvg(),
+        log.addValue(reportName + ": Average Periodic diff", periodicStat.getAvg(),
                 ResultType.LOWER_BETTER, ResultUnit.MS);
-        log.printValue(reportName + ": Maximum Periodic abs diff", periodicStat.getMaxAbs(),
+        log.addValue(reportName + ": Maximum Periodic abs diff", periodicStat.getMaxAbs(),
                 ResultType.LOWER_BETTER, ResultUnit.MS);
-        log.printValue(reportName + ": Average Periodic abs diff", periodicStat.getAvgAbs(),
+        log.addValue(reportName + ": Average Periodic abs diff", periodicStat.getAvgAbs(),
                 ResultType.LOWER_BETTER, ResultUnit.MS);
-        log.printSummary(reportName + ": Unified abs diff",
+        log.setSummary(reportName + ": Unified abs diff",
                 (periodicStat.getAvgAbs() + markerStat.getAvgAbs()) / 2,
                 ResultType.LOWER_BETTER, ResultUnit.MS);
+        log.submit(getInstrumentation());
     }
 
     private class MockOnRecordPositionUpdateListener
diff --git a/tests/tests/media/src/android/media/cts/AudioTrackTest.java b/tests/tests/media/src/android/media/cts/AudioTrackTest.java
index 4c03183..49ebd2b 100644
--- a/tests/tests/media/src/android/media/cts/AudioTrackTest.java
+++ b/tests/tests/media/src/android/media/cts/AudioTrackTest.java
@@ -26,14 +26,13 @@
 import android.media.PlaybackParams;
 import android.util.Log;
 
-import com.android.cts.util.ReportLog;
-import com.android.cts.util.ResultType;
-import com.android.cts.util.ResultUnit;
+import com.android.compatibility.common.util.DeviceReportLog;
+import com.android.compatibility.common.util.ResultType;
+import com.android.compatibility.common.util.ResultUnit;
 
-import java.nio.ByteOrder;
 import java.nio.ByteBuffer;
-import java.nio.ShortBuffer;
 import java.nio.FloatBuffer;
+import java.nio.ShortBuffer;
 
 public class AudioTrackTest extends CtsAndroidTestCase {
     private String TAG = "AudioTrackTest";
@@ -2013,15 +2012,16 @@
         track.release();
         // Log the average jitter
         if (cumulativeJitterCount > 0) {
-            ReportLog log = getReportLog();
+            DeviceReportLog log = new DeviceReportLog();
             final float averageJitterInFrames = cumulativeJitter / cumulativeJitterCount;
             final float averageJitterInMs = averageJitterInFrames * 1000 / TEST_SR;
             final float maxJitterInMs = maxJitter * 1000 / TEST_SR;
             // ReportLog needs at least one Value and Summary.
-            log.printValue("Maximum Jitter", maxJitterInMs,
+            log.addValue("Maximum Jitter", maxJitterInMs,
                     ResultType.LOWER_BETTER, ResultUnit.MS);
-            log.printSummary("Average Jitter", averageJitterInMs,
+            log.setSummary("Average Jitter", averageJitterInMs,
                     ResultType.LOWER_BETTER, ResultUnit.MS);
+            log.submit(getInstrumentation());
         }
     }
 
diff --git a/tests/tests/media/src/android/media/cts/AudioTrack_ListenerTest.java b/tests/tests/media/src/android/media/cts/AudioTrack_ListenerTest.java
index e059e36..37affd0 100644
--- a/tests/tests/media/src/android/media/cts/AudioTrack_ListenerTest.java
+++ b/tests/tests/media/src/android/media/cts/AudioTrack_ListenerTest.java
@@ -16,21 +16,20 @@
 
 package android.media.cts;
 
-import java.util.ArrayList;
-
 import android.cts.util.CtsAndroidTestCase;
 import android.media.AudioFormat;
 import android.media.AudioManager;
 import android.media.AudioTrack;
 import android.media.AudioTrack.OnPlaybackPositionUpdateListener;
-import android.media.cts.AudioHelper;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
-import android.util.Log;
-import com.android.cts.util.ReportLog;
-import com.android.cts.util.ResultType;
-import com.android.cts.util.ResultUnit;
+
+import com.android.compatibility.common.util.DeviceReportLog;
+import com.android.compatibility.common.util.ResultType;
+import com.android.compatibility.common.util.ResultUnit;
+
+import java.util.ArrayList;
 
 public class AudioTrack_ListenerTest extends CtsAndroidTestCase {
     private final static String TAG = "AudioTrack_ListenerTest";
@@ -206,22 +205,23 @@
         }
 
         // report this
-        ReportLog log = getReportLog();
-        log.printValue(reportName + ": Average Marker diff", markerStat.getAvg(),
+        DeviceReportLog log = new DeviceReportLog();
+        log.addValue(reportName + ": Average Marker diff", markerStat.getAvg(),
                 ResultType.LOWER_BETTER, ResultUnit.MS);
-        log.printValue(reportName + ": Maximum Marker abs diff", markerStat.getMaxAbs(),
+        log.addValue(reportName + ": Maximum Marker abs diff", markerStat.getMaxAbs(),
                 ResultType.LOWER_BETTER, ResultUnit.MS);
-        log.printValue(reportName + ": Average Marker abs diff", markerStat.getAvgAbs(),
+        log.addValue(reportName + ": Average Marker abs diff", markerStat.getAvgAbs(),
                 ResultType.LOWER_BETTER, ResultUnit.MS);
-        log.printValue(reportName + ": Average Periodic diff", periodicStat.getAvg(),
+        log.addValue(reportName + ": Average Periodic diff", periodicStat.getAvg(),
                 ResultType.LOWER_BETTER, ResultUnit.MS);
-        log.printValue(reportName + ": Maximum Periodic abs diff", periodicStat.getMaxAbs(),
+        log.addValue(reportName + ": Maximum Periodic abs diff", periodicStat.getMaxAbs(),
                 ResultType.LOWER_BETTER, ResultUnit.MS);
-        log.printValue(reportName + ": Average Periodic abs diff", periodicStat.getAvgAbs(),
+        log.addValue(reportName + ": Average Periodic abs diff", periodicStat.getAvgAbs(),
                 ResultType.LOWER_BETTER, ResultUnit.MS);
-        log.printSummary(reportName + ": Unified abs diff",
+        log.setSummary(reportName + ": Unified abs diff",
                 (periodicStat.getAvgAbs() + markerStat.getAvgAbs()) / 2,
                 ResultType.LOWER_BETTER, ResultUnit.MS);
+        log.submit(getInstrumentation());
     }
 
     private class MockOnPlaybackPositionUpdateListener
diff --git a/tests/tests/media/src/android/media/cts/DecoderTest.java b/tests/tests/media/src/android/media/cts/DecoderTest.java
index 6765051..c5bdce8 100755
--- a/tests/tests/media/src/android/media/cts/DecoderTest.java
+++ b/tests/tests/media/src/android/media/cts/DecoderTest.java
@@ -16,6 +16,7 @@
 
 package android.media.cts;
 
+import com.android.compatibility.common.util.DynamicConfigDeviceSide;
 import com.android.cts.media.R;
 
 import android.content.Context;
@@ -65,20 +66,6 @@
     private MediaCodecTunneledPlayer mMediaCodecPlayer;
     private static final int SLEEP_TIME_MS = 1000;
     private static final long PLAY_TIME_MS = TimeUnit.MILLISECONDS.convert(1, TimeUnit.MINUTES);
-    private static final Uri AUDIO_URL = Uri.parse(
-            "http://redirector.c.youtube.com/videoplayback?id=c80658495af60617"
-                + "&itag=18&source=youtube&ip=0.0.0.0&ipbits=0&expire=19000000000"
-                + "&sparams=ip,ipbits,expire,id,itag,source"
-                + "&signature=46A04ED550CA83B79B60060BA80C79FDA5853D26."
-                + "49582D382B4A9AFAA163DED38D2AE531D85603C0"
-                + "&key=ik0&user=android-device-test");  // H.264 Base + AAC
-    private static final Uri VIDEO_URL = Uri.parse(
-            "http://redirector.c.youtube.com/videoplayback?id=c80658495af60617"
-                + "&itag=18&source=youtube&ip=0.0.0.0&ipbits=0&expire=19000000000"
-                + "&sparams=ip,ipbits,expire,id,itag,source"
-                + "&signature=46A04ED550CA83B79B60060BA80C79FDA5853D26."
-                + "49582D382B4A9AFAA163DED38D2AE531D85603C0"
-                + "&key=ik0&user=android-device-test");  // H.264 Base + AAC
 
     @Override
     protected void setUp() throws Exception {
@@ -1959,8 +1946,11 @@
         mMediaCodecPlayer = new MediaCodecTunneledPlayer(
                 getActivity().getSurfaceHolder(), true, am.generateAudioSessionId());
 
-        mMediaCodecPlayer.setAudioDataSource(AUDIO_URL, null);
-        mMediaCodecPlayer.setVideoDataSource(VIDEO_URL, null);
+        DynamicConfigDeviceSide config = new DynamicConfigDeviceSide("CtsMediaTestCases");
+        Uri audio_url = Uri.parse(config.getConfig("DecoderTest-AUDIO_URL"));
+        mMediaCodecPlayer.setAudioDataSource(audio_url, null);
+        Uri video_url = Uri.parse(config.getConfig("DecoderTest-VIDEO_URL"));
+        mMediaCodecPlayer.setVideoDataSource(video_url, null);
         assertTrue("MediaCodecPlayer.start() failed!", mMediaCodecPlayer.start());
         assertTrue("MediaCodecPlayer.prepare() failed!", mMediaCodecPlayer.prepare());
 
@@ -1998,8 +1988,11 @@
         mMediaCodecPlayer = new MediaCodecTunneledPlayer(
                 getActivity().getSurfaceHolder(), true, am.generateAudioSessionId());
 
-        mMediaCodecPlayer.setAudioDataSource(AUDIO_URL, null);
-        mMediaCodecPlayer.setVideoDataSource(VIDEO_URL, null);
+        DynamicConfigDeviceSide config = new DynamicConfigDeviceSide("CtsMediaTestCases");
+        Uri audio_url = Uri.parse(config.getConfig("DecoderTest-AUDIO_URL"));
+        mMediaCodecPlayer.setAudioDataSource(audio_url, null);
+        Uri video_url = Uri.parse(config.getConfig("DecoderTest-VIDEO_URL"));
+        mMediaCodecPlayer.setVideoDataSource(video_url, null);
         assertTrue("MediaCodecPlayer.start() failed!", mMediaCodecPlayer.start());
         assertTrue("MediaCodecPlayer.prepare() failed!", mMediaCodecPlayer.prepare());
 
diff --git a/tests/tests/media/src/android/media/cts/MediaCodecCapabilitiesTest.java b/tests/tests/media/src/android/media/cts/MediaCodecCapabilitiesTest.java
index 813af0f2..812f009d 100644
--- a/tests/tests/media/src/android/media/cts/MediaCodecCapabilitiesTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaCodecCapabilitiesTest.java
@@ -36,6 +36,8 @@
 import android.os.Build;
 import android.util.Log;
 
+import com.android.compatibility.common.util.DynamicConfigDeviceSide;
+
 import java.io.IOException;
 import java.util.HashSet;
 import java.util.Set;
@@ -172,39 +174,28 @@
             return; // skip
         }
 
-        playVideoWithRetries("http://redirector.c.youtube.com/videoplayback?id=271de9756065677e"
-                + "&itag=160&source=youtube&user=android-device-test"
-                + "&sparams=ip,ipbits,expire,id,itag,source,user"
-                + "&ip=0.0.0.0&ipbits=0&expire=19000000000"
-                + "&signature=9EDCA0B395B8A949C511FD5E59B9F805CFF797FD."
-                + "702DE9BA7AF96785FD6930AD2DD693A0486C880E"
-                + "&key=ik0", 256, 144, PLAY_TIME_MS);
+        String url = new DynamicConfigDeviceSide("CtsMediaTestCases")
+                .getConfig("MediaCodecCapabilitiesTest-testAvcBaseline12");
+        playVideoWithRetries(url, 256, 144, PLAY_TIME_MS);
     }
 
     public void testAvcBaseline30() throws Exception {
         if (!checkDecoder(MIMETYPE_VIDEO_AVC, AVCProfileBaseline, AVCLevel3)) {
             return; // skip
         }
-        playVideoWithRetries("http://redirector.c.youtube.com/videoplayback?id=271de9756065677e"
-                + "&itag=18&source=youtube&user=android-device-test"
-                + "&sparams=ip,ipbits,expire,id,itag,source,user"
-                + "&ip=0.0.0.0&ipbits=0&expire=19000000000"
-                + "&signature=7DCDE3A6594D0B91A27676A3CDC3A87B149F82EA."
-                + "7A83031734CB1EDCE06766B6228842F954927960"
-                + "&key=ik0", 640, 360, PLAY_TIME_MS);
+        String url = new DynamicConfigDeviceSide("CtsMediaTestCases")
+                .getConfig("MediaCodecCapabilitiesTest-testAvcBaseline30");
+        playVideoWithRetries(url, 640, 360, PLAY_TIME_MS);
     }
 
     public void testAvcHigh31() throws Exception {
         if (!checkDecoder(MIMETYPE_VIDEO_AVC, AVCProfileHigh, AVCLevel31)) {
             return; // skip
         }
-        playVideoWithRetries("http://redirector.c.youtube.com/videoplayback?id=271de9756065677e"
-                + "&itag=22&source=youtube&user=android-device-test"
-                + "&sparams=ip,ipbits,expire,id,itag,source,user"
-                + "&ip=0.0.0.0&ipbits=0&expire=19000000000"
-                + "&signature=179525311196616BD8E1381759B0E5F81A9E91B5."
-                + "C4A50E44059FEBCC6BBC78E3B3A4E0E0065777"
-                + "&key=ik0", 1280, 720, PLAY_TIME_MS);
+
+        String url = new DynamicConfigDeviceSide("CtsMediaTestCases")
+                .getConfig("MediaCodecCapabilitiesTest-testAvcHigh31");
+        playVideoWithRetries(url, 1280, 720, PLAY_TIME_MS);
     }
 
     public void testAvcHigh40() throws Exception {
@@ -215,13 +206,10 @@
             MediaUtils.skipTest(TAG, "fragmented mp4 not supported");
             return;
         }
-        playVideoWithRetries("http://redirector.c.youtube.com/videoplayback?id=271de9756065677e"
-                + "&itag=137&source=youtube&user=android-device-test"
-                + "&sparams=ip,ipbits,expire,id,itag,source,user"
-                + "&ip=0.0.0.0&ipbits=0&expire=19000000000"
-                + "&signature=B0976085596DD42DEA3F08307F76587241CB132B."
-                + "043B719C039E8B92F45391ADC0BE3665E2332930"
-                + "&key=ik0", 1920, 1080, PLAY_TIME_MS);
+
+        String url = new DynamicConfigDeviceSide("CtsMediaTestCases")
+                .getConfig("MediaCodecCapabilitiesTest-testAvcHigh40");
+        playVideoWithRetries(url, 1920, 1080, PLAY_TIME_MS);
     }
 
     public void testHevcMain1() throws Exception {
diff --git a/tests/tests/media/src/android/media/cts/StreamingMediaPlayerTest.java b/tests/tests/media/src/android/media/cts/StreamingMediaPlayerTest.java
index 7497da2..a6c80ad 100644
--- a/tests/tests/media/src/android/media/cts/StreamingMediaPlayerTest.java
+++ b/tests/tests/media/src/android/media/cts/StreamingMediaPlayerTest.java
@@ -26,6 +26,8 @@
 import android.util.Log;
 import android.webkit.cts.CtsTestServer;
 
+import com.android.compatibility.common.util.DynamicConfigDeviceSide;
+
 import java.io.IOException;
 import java.util.concurrent.atomic.AtomicInteger;
 
@@ -73,49 +75,36 @@
             return; // skip
         }
 
-        playVideoTest("http://redirector.c.youtube.com/videoplayback?id=271de9756065677e"
-                + "&itag=13&source=youtube&ip=0.0.0.0&ipbits=0&expire=19000000000"
-                + "&sparams=ip,ipbits,expire,id,itag,source"
-                + "&signature=5729247E22691EBB3E804DDD523EC42DC17DD8CE"
-                + ".443B81C1E8E6D64E4E1555F568BA46C206507D78"
-                + "&key=ik0&user=android-device-test", 176, 144);
+        String url = new DynamicConfigDeviceSide("CtsMediaTestCases")
+                .getConfig("StreamingMediaPlayerTest-testHTTP_H263_AMR_Video1");
+        playVideoTest(url, 176, 144);
     }
     public void testHTTP_H263_AMR_Video2() throws Exception {
         if (!MediaUtils.checkDecoder(MediaFormat.MIMETYPE_VIDEO_H263, MediaFormat.MIMETYPE_AUDIO_AMR_NB)) {
             return; // skip
         }
 
-        playVideoTest("http://redirector.c.youtube.com/videoplayback?id=c80658495af60617"
-                + "&itag=13&source=youtube&ip=0.0.0.0&ipbits=0&expire=19000000000"
-                + "&sparams=ip,ipbits,expire,id,itag,source"
-                + "&signature=508D82AB36939345BF6B8D0623CB6CABDD9C64C3"
-                + ".9B3336A96846DF38E5343C46AA57F6CF2956E427"
-                + "&key=ik0&user=android-device-test", 176, 144);
+        String url = new DynamicConfigDeviceSide("CtsMediaTestCases")
+                .getConfig("StreamingMediaPlayerTest-testHTTP_H263_AMR_Video2");
+        playVideoTest(url, 176, 144);
     }
 
     public void testHTTP_MPEG4SP_AAC_Video1() throws Exception {
         if (!MediaUtils.checkDecoder(MediaFormat.MIMETYPE_VIDEO_MPEG4)) {
             return; // skip
         }
-
-        playVideoTest("http://redirector.c.youtube.com/videoplayback?id=271de9756065677e"
-                + "&itag=17&source=youtube&ip=0.0.0.0&ipbits=0&expire=19000000000"
-                + "&sparams=ip,ipbits,expire,id,itag,source"
-                + "&signature=837198AAADF6F36BA6B2D324F690A7C5B7AFE3FF"
-                + ".7138CE5E36D718220726C1FC305497FF2D082249"
-                + "&key=ik0&user=android-device-test", 176, 144);
+        String url = new DynamicConfigDeviceSide("CtsMediaTestCases")
+                .getConfig("StreamingMediaPlayerTest-testHTTP_MPEG4SP_AAC_Video1");
+        playVideoTest(url, 176, 144);
     }
     public void testHTTP_MPEG4SP_AAC_Video2() throws Exception {
         if (!MediaUtils.checkDecoder(MediaFormat.MIMETYPE_VIDEO_MPEG4)) {
             return; // skip
         }
 
-        playVideoTest("http://redirector.c.youtube.com/videoplayback?id=c80658495af60617"
-                + "&itag=17&source=youtube&ip=0.0.0.0&ipbits=0&expire=19000000000"
-                + "&sparams=ip,ipbits,expire,id,itag,source"
-                + "&signature=70E979A621001201BC18622BDBF914FA870BDA40"
-                + ".6E78890B80F4A33A18835F775B1FF64F0A4D0003"
-                + "&key=ik0&user=android-device-test", 176, 144);
+        String url = new DynamicConfigDeviceSide("CtsMediaTestCases")
+                .getConfig("StreamingMediaPlayerTest-testHTTP_MPEG4SP_AAC_Video2");
+        playVideoTest(url, 176, 144);
     }
 
     public void testHTTP_H264Base_AAC_Video1() throws Exception {
@@ -123,24 +112,18 @@
             return; // skip
         }
 
-        playVideoTest("http://redirector.c.youtube.com/videoplayback?id=271de9756065677e"
-                + "&itag=18&source=youtube&ip=0.0.0.0&ipbits=0&expire=19000000000"
-                + "&sparams=ip,ipbits,expire,id,itag,source"
-                + "&signature=667AEEF54639926662CE62361400B8F8C1753B3F"
-                + ".15F46C382C68A9F121BA17BF1F56BEDEB4B06091"
-                + "&key=ik0&user=android-device-test", 640, 360);
+        String url = new DynamicConfigDeviceSide("CtsMediaTestCases")
+                .getConfig("StreamingMediaPlayerTest-testHTTP_H264Base_AAC_Video1");
+        playVideoTest(url, 640, 360);
     }
     public void testHTTP_H264Base_AAC_Video2() throws Exception {
         if (!MediaUtils.checkDecoder(MediaFormat.MIMETYPE_VIDEO_AVC)) {
             return; // skip
         }
 
-        playVideoTest("http://redirector.c.youtube.com/videoplayback?id=c80658495af60617"
-                + "&itag=18&source=youtube&ip=0.0.0.0&ipbits=0&expire=19000000000"
-                + "&sparams=ip,ipbits,expire,id,itag,source"
-                + "&signature=46A04ED550CA83B79B60060BA80C79FDA5853D26"
-                + ".49582D382B4A9AFAA163DED38D2AE531D85603C0"
-                + "&key=ik0&user=android-device-test", 640, 360);
+        String url = new DynamicConfigDeviceSide("CtsMediaTestCases")
+                .getConfig("StreamingMediaPlayerTest-testHTTP_H264Base_AAC_Video2");
+        playVideoTest(url, 640, 360);
     }
 
     // Streaming HLS video from YouTube
diff --git a/tests/tests/media/src/android/media/cts/VideoDecoderPerfTest.java b/tests/tests/media/src/android/media/cts/VideoDecoderPerfTest.java
index f5680f6..93563b2 100644
--- a/tests/tests/media/src/android/media/cts/VideoDecoderPerfTest.java
+++ b/tests/tests/media/src/android/media/cts/VideoDecoderPerfTest.java
@@ -36,7 +36,7 @@
 
 import com.android.cts.util.ResultType;
 import com.android.cts.util.ResultUnit;
-import com.android.cts.util.Stat;
+import com.android.compatibility.common.util.Stat;
 
 import java.nio.ByteBuffer;
 import java.util.Arrays;
diff --git a/tests/tests/provider/src/android/provider/cts/ContactsContract_CommonDataKinds_OrganizationTest.java b/tests/tests/provider/src/android/provider/cts/ContactsContract_CommonDataKinds_OrganizationTest.java
index 2de3b05..fa83d4f 100644
--- a/tests/tests/provider/src/android/provider/cts/ContactsContract_CommonDataKinds_OrganizationTest.java
+++ b/tests/tests/provider/src/android/provider/cts/ContactsContract_CommonDataKinds_OrganizationTest.java
@@ -16,9 +16,12 @@
 
 package android.provider.cts;
 
+import junit.framework.Assert;
+
 import android.content.res.Resources;
 import android.provider.ContactsContract.CommonDataKinds.Im;
 import android.provider.ContactsContract.CommonDataKinds.Organization;
+import android.provider.ContactsContract.Data;
 import android.test.AndroidTestCase;
 
 public class ContactsContract_CommonDataKinds_OrganizationTest extends AndroidTestCase {
@@ -51,4 +54,10 @@
         assertTrue(res != 0);
         assertEquals(label, Organization.getTypeLabel(mResources, Im.TYPE_CUSTOM, label));
     }
+
+    public void testPhoneticNameStyleColumnName() throws Exception {
+        // Make sure the column name is data10 and not phonetic_name_style
+        // from the parent class.
+        assertEquals(Data.DATA10, Organization.PHONETIC_NAME_STYLE);
+    }
 }
diff --git a/tests/tests/provider/src/android/provider/cts/ContactsContract_StructuredPhoneticName.java b/tests/tests/provider/src/android/provider/cts/ContactsContract_StructuredPhoneticName.java
index 4efd9a7..42bc6d2 100644
--- a/tests/tests/provider/src/android/provider/cts/ContactsContract_StructuredPhoneticName.java
+++ b/tests/tests/provider/src/android/provider/cts/ContactsContract_StructuredPhoneticName.java
@@ -24,6 +24,7 @@
 import android.provider.ContactsContract.AggregationExceptions;
 import android.provider.ContactsContract.CommonDataKinds.StructuredName;
 import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.Data;
 import android.provider.ContactsContract.DisplayNameSources;
 import android.provider.ContactsContract.RawContacts;
 import android.provider.cts.ContactsContract_TestDataBuilder.TestContact;
@@ -72,6 +73,12 @@
                 DisplayNameSources.STRUCTURED_PHONETIC_NAME);
     }
 
+    public void testPhoneticNameStyleColumnName() throws Exception {
+        // Make sure the column name is data11 and not phonetic_name_style
+        // from the parent class.
+        assertEquals(Data.DATA11, StructuredName.PHONETIC_NAME_STYLE);
+    }
+
     public void testPhoneticStructuredName_phoneticPriority1() throws Exception {
         // Setup: one raw contact has a complex phonetic name and the other a simple given name
         TestRawContact rawContact1 = mBuilder.newRawContact()
diff --git a/tests/tests/security/Android.mk b/tests/tests/security/Android.mk
index ec36d6d..043553b 100644
--- a/tests/tests/security/Android.mk
+++ b/tests/tests/security/Android.mk
@@ -21,7 +21,7 @@
 # Include both the 32 and 64 bit versions
 LOCAL_MULTILIB := both
 
-LOCAL_STATIC_JAVA_LIBRARIES := ctstestserver ctstestrunner ctsdeviceutil guava
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestserver ctstestrunner ctsdeviceutil compatibility-device-util guava
 
 LOCAL_JAVA_LIBRARIES := android.test.runner org.apache.http.legacy
 
diff --git a/tests/tests/security/AndroidManifest.xml b/tests/tests/security/AndroidManifest.xml
index 25bc6ac..911442a 100644
--- a/tests/tests/security/AndroidManifest.xml
+++ b/tests/tests/security/AndroidManifest.xml
@@ -22,6 +22,8 @@
     <uses-permission android:name="android.permission.KILL_BACKGROUND_PROCESSES" />
     <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
     <uses-permission android:name="android.permission.INTERNET" />
+    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
+    <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
 
     <application>
         <uses-library android:name="android.test.runner" />
diff --git a/tests/tests/security/jni/Android.mk b/tests/tests/security/jni/Android.mk
index 2d55fb6..9cd4a5c 100644
--- a/tests/tests/security/jni/Android.mk
+++ b/tests/tests/security/jni/Android.mk
@@ -31,7 +31,9 @@
 		android_security_cts_SELinuxTest.cpp \
 		android_security_cts_MMapExecutableTest.cpp \
 		android_security_cts_AudioPolicyBinderTest.cpp \
-		android_security_cts_EncryptionTest.cpp
+		android_security_cts_EncryptionTest.cpp \
+		android_security_cts_AudioflingerBinderTest.cpp \
+		android_security_cts_AudioEffectBinderTest.cpp
 
 LOCAL_C_INCLUDES := $(JNI_H_INCLUDE)
 
diff --git a/tests/tests/security/jni/CtsSecurityJniOnLoad.cpp b/tests/tests/security/jni/CtsSecurityJniOnLoad.cpp
index c60b866..1c56371 100644
--- a/tests/tests/security/jni/CtsSecurityJniOnLoad.cpp
+++ b/tests/tests/security/jni/CtsSecurityJniOnLoad.cpp
@@ -25,7 +25,9 @@
 extern int register_android_security_cts_SELinuxTest(JNIEnv*);
 extern int register_android_security_cts_MMapExecutableTest(JNIEnv* env);
 extern int register_android_security_cts_AudioPolicyBinderTest(JNIEnv* env);
+extern int register_android_security_cts_AudioFlingerBinderTest(JNIEnv* env);
 extern int register_android_security_cts_EncryptionTest(JNIEnv* env);
+extern int register_android_security_cts_AudioEffectBinderTest(JNIEnv* env);
 
 jint JNI_OnLoad(JavaVM *vm, void *reserved) {
     JNIEnv *env = NULL;
@@ -70,5 +72,13 @@
         return JNI_ERR;
     }
 
+    if (register_android_security_cts_AudioFlingerBinderTest(env)) {
+        return JNI_ERR;
+    }
+
+    if (register_android_security_cts_AudioEffectBinderTest(env)) {
+        return JNI_ERR;
+    }
+
     return JNI_VERSION_1_4;
 }
diff --git a/tests/tests/security/jni/android_security_cts_AudioEffectBinderTest.cpp b/tests/tests/security/jni/android_security_cts_AudioEffectBinderTest.cpp
new file mode 100644
index 0000000..4c27416
--- /dev/null
+++ b/tests/tests/security/jni/android_security_cts_AudioEffectBinderTest.cpp
@@ -0,0 +1,161 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "AudioEffectBinderTest-JNI"
+
+#include <jni.h>
+#include <media/AudioEffect.h>
+#include <media/IEffect.h>
+
+using namespace android;
+
+/*
+ * Native methods used by
+ * cts/tests/tests/security/src/android/security/cts/AudioEffectBinderTest.java
+ */
+
+struct EffectClient : public BnEffectClient {
+    EffectClient() { }
+    virtual void controlStatusChanged(bool controlGranted __unused) { }
+    virtual void enableStatusChanged(bool enabled __unused) { }
+    virtual void commandExecuted(uint32_t cmdCode __unused,
+            uint32_t cmdSize __unused,
+            void *pCmdData __unused,
+            uint32_t replySize __unused,
+            void *pReplyData __unused) { }
+};
+
+struct DeathRecipient : public IBinder::DeathRecipient {
+    DeathRecipient() : mDied(false) { }
+    virtual void binderDied(const wp<IBinder>& who __unused) { mDied = true; }
+    bool died() const { return mDied; }
+    bool mDied;
+};
+
+static bool isIEffectCommandSecure(IEffect *effect)
+{
+    // some magic constants here
+    const int COMMAND_SIZE = 1024 + 12; // different than reply size to get different heap frag
+    char cmdData[COMMAND_SIZE];
+    memset(cmdData, 0xde, sizeof(cmdData));
+
+    const int REPLY_DATA_SIZE = 256;
+    char replyData[REPLY_DATA_SIZE];
+    bool secure = true;
+    for (int k = 0; k < 10; ++k) {
+        Parcel data;
+        data.writeInterfaceToken(effect->getInterfaceDescriptor());
+        data.writeInt32(0);  // 0 is EFFECT_CMD_INIT
+        data.writeInt32(sizeof(cmdData));
+        data.write(cmdData, sizeof(cmdData));
+        data.writeInt32(sizeof(replyData));
+
+        Parcel reply;
+        status_t status = effect->asBinder(effect)->transact(3, data, &reply);  // 3 is COMMAND
+        ALOGV("transact status: %d", status);
+        if (status != NO_ERROR) {
+            ALOGW("invalid transaction status %d", status);
+            continue;
+        }
+
+        ALOGV("reply data avail %zu", reply.dataAvail());
+        status = reply.readInt32();
+        ALOGV("reply status %d", status);
+        if (status == NO_ERROR) {
+            continue;
+        }
+
+        int size = reply.readInt32();
+        ALOGV("reply size %d", size);
+        if (size != sizeof(replyData)) { // we expect 0 or full reply data if command failed
+            ALOGW_IF(size != 0, "invalid reply size: %d", size);
+            continue;
+        }
+
+        // Note that if reply.read() returns success, it should completely fill replyData.
+        status = reply.read(replyData, sizeof(replyData));
+        if (status != NO_ERROR) {
+            ALOGW("invalid reply read - ignoring");
+            continue;
+        }
+        unsigned int *out = (unsigned int *)replyData;
+        for (size_t index = 0; index < sizeof(replyData) / sizeof(*out); ++index) {
+            if (out[index] != 0) {
+                secure = false;
+                ALOGI("leaked data = %#08x", out[index]);
+            }
+        }
+    }
+    ALOGI("secure: %s", secure ? "YES" : "NO");
+    return secure;
+}
+
+static jboolean android_security_cts_AudioEffect_test_isCommandSecure()
+{
+    const sp<IAudioFlinger> &audioFlinger = AudioSystem::get_audio_flinger();
+    if (audioFlinger.get() == NULL) {
+        ALOGE("could not get audioflinger");
+        return JNI_FALSE;
+    }
+
+    static const effect_uuid_t EFFECT_UIID_EQUALIZER =  // type
+        { 0x0bed4300, 0xddd6, 0x11db, 0x8f34, {0x00, 0x02, 0xa5, 0xd5, 0xc5, 0x1b }};
+    sp<EffectClient> effectClient(new EffectClient());
+    effect_descriptor_t descriptor;
+    memset(&descriptor, 0, sizeof(descriptor));
+    descriptor.type = EFFECT_UIID_EQUALIZER;
+    descriptor.uuid = *EFFECT_UUID_NULL;
+    const int32_t priority = 0;
+    const int sessionId = AUDIO_SESSION_OUTPUT_MIX;
+    const audio_io_handle_t io = AUDIO_IO_HANDLE_NONE;
+    const String16 opPackageName("Exploitable");
+    status_t status;
+    int32_t id;
+    int enabled;
+    sp<IEffect> effect = audioFlinger->createEffect(&descriptor, effectClient,
+            priority, io, sessionId, opPackageName, &status, &id, &enabled);
+    if (effect.get() == NULL || status != NO_ERROR) {
+        ALOGW("could not create effect");
+        return JNI_TRUE;
+    }
+
+    sp<DeathRecipient> deathRecipient(new DeathRecipient());
+    IInterface::asBinder(effect)->linkToDeath(deathRecipient);
+
+    // check exploit
+    if (!isIEffectCommandSecure(effect.get())) {
+        ALOGE("not secure!");
+        return JNI_FALSE;
+    }
+
+    sleep(1); // wait to check death
+    if (deathRecipient->died()) {
+        ALOGE("effect binder died");
+        return JNI_FALSE;
+    }
+    return JNI_TRUE;
+}
+
+int register_android_security_cts_AudioEffectBinderTest(JNIEnv *env)
+{
+    static JNINativeMethod methods[] = {
+        { "native_test_isCommandSecure", "()Z",
+                (void *) android_security_cts_AudioEffect_test_isCommandSecure },
+    };
+
+    jclass clazz = env->FindClass("android/security/cts/AudioEffectBinderTest");
+    return env->RegisterNatives(clazz, methods, sizeof(methods) / sizeof(methods[0]));
+}
diff --git a/tests/tests/security/jni/android_security_cts_AudioflingerBinderTest.cpp b/tests/tests/security/jni/android_security_cts_AudioflingerBinderTest.cpp
new file mode 100644
index 0000000..f169d61
--- /dev/null
+++ b/tests/tests/security/jni/android_security_cts_AudioflingerBinderTest.cpp
@@ -0,0 +1,261 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "AudioFlingerBinderTest-JNI"
+
+#include <jni.h>
+#include <binder/IServiceManager.h>
+#include <media/IAudioFlinger.h>
+#include <media/AudioSystem.h>
+#include <system/audio.h>
+#include <utils/Log.h>
+#include <utils/SystemClock.h>
+
+using namespace android;
+
+/*
+ * Native methods used by
+ * cts/tests/tests/security/src/android/security/cts/AudioFlingerBinderTest.java
+ */
+
+#define TEST_ARRAY_SIZE 10000
+#define MAX_ARRAY_SIZE 1024
+#define TEST_PATTERN 0x55
+
+class MyDeathClient: public IBinder::DeathRecipient
+{
+public:
+    MyDeathClient() :
+        mAfIsDead(false) {
+    }
+
+    bool afIsDead() const { return mAfIsDead; }
+
+    // DeathRecipient
+    virtual void binderDied(const wp<IBinder>& who __unused) { mAfIsDead = true; }
+
+private:
+    bool mAfIsDead;
+};
+
+
+static bool connectAudioFlinger(sp<IAudioFlinger>& af, sp<MyDeathClient> &dr)
+{
+    int64_t startTime = 0;
+    while (af == 0) {
+        sp<IBinder> binder = defaultServiceManager()->checkService(String16("media.audio_flinger"));
+        if (binder == 0) {
+            if (startTime == 0) {
+                startTime = uptimeMillis();
+            } else if ((uptimeMillis()-startTime) > 10000) {
+                ALOGE("timeout while getting audio flinger service");
+                return false;
+            }
+            sleep(1);
+        } else {
+            af = interface_cast<IAudioFlinger>(binder);
+            dr = new MyDeathClient();
+            binder->linkToDeath(dr);
+        }
+    }
+    return true;
+}
+
+/*
+ * Checks that AudioSystem::setMasterMute() does not crash mediaserver if a duplicated output
+ * is opened.
+ */
+jboolean android_security_cts_AudioFlinger_test_setMasterMute(JNIEnv* env __unused,
+                                                           jobject thiz __unused)
+{
+    sp<IAudioFlinger> af;
+    sp<MyDeathClient> dr;
+
+    if (!connectAudioFlinger(af, dr)) {
+        return false;
+    }
+
+    // force opening of a duplicating output
+    status_t status = AudioSystem::setDeviceConnectionState(AUDIO_DEVICE_OUT_REMOTE_SUBMIX,
+                                          AUDIO_POLICY_DEVICE_STATE_AVAILABLE,
+                                          "0", "");
+    if (status != NO_ERROR) {
+        return false;
+    }
+
+    bool mute;
+    status = AudioSystem::getMasterMute(&mute);
+    if (status != NO_ERROR) {
+        return false;
+    }
+
+    AudioSystem::setMasterMute(!mute);
+
+    sleep(1);
+
+    // Check that mediaserver did not crash
+    if (dr->afIsDead()) {
+        return false;
+    }
+
+    AudioSystem::setMasterMute(mute);
+
+    AudioSystem::setDeviceConnectionState(AUDIO_DEVICE_OUT_REMOTE_SUBMIX,
+                                          AUDIO_POLICY_DEVICE_STATE_UNAVAILABLE,
+                                          "0", "");
+
+    AudioSystem::setMasterMute(false);
+
+    return true;
+}
+
+jboolean android_security_cts_AudioFlinger_test_setMasterVolume(JNIEnv* env __unused,
+                                                           jobject thiz __unused)
+{
+    sp<IAudioFlinger> af;
+    sp<MyDeathClient> dr;
+
+    if (!connectAudioFlinger(af, dr)) {
+        return false;
+    }
+
+    // force opening of a duplicating output
+    status_t status = AudioSystem::setDeviceConnectionState(AUDIO_DEVICE_OUT_REMOTE_SUBMIX,
+                                          AUDIO_POLICY_DEVICE_STATE_AVAILABLE,
+                                          "0", "");
+    if (status != NO_ERROR) {
+        return false;
+    }
+
+    float vol;
+    status = AudioSystem::getMasterVolume(&vol);
+    if (status != NO_ERROR) {
+        return false;
+    }
+
+    AudioSystem::setMasterVolume(vol < 0.5 ? 1.0 : 0.0);
+
+    sleep(1);
+
+    // Check that mediaserver did not crash
+    if (dr->afIsDead()) {
+        return false;
+    }
+
+    AudioSystem::setMasterMute(vol);
+
+    AudioSystem::setDeviceConnectionState(AUDIO_DEVICE_OUT_REMOTE_SUBMIX,
+                                          AUDIO_POLICY_DEVICE_STATE_UNAVAILABLE,
+                                          "0", "");
+
+    return true;
+}
+
+jboolean android_security_cts_AudioFlinger_test_listAudioPorts(JNIEnv* env __unused,
+                                                           jobject thiz __unused)
+{
+    sp<IAudioFlinger> af;
+    sp<MyDeathClient> dr;
+
+    if (!connectAudioFlinger(af, dr)) {
+        return false;
+    }
+
+    unsigned int num_ports = TEST_ARRAY_SIZE;
+    struct audio_port *ports =
+            (struct audio_port *)calloc(TEST_ARRAY_SIZE, sizeof(struct audio_port));
+
+    memset(ports, TEST_PATTERN, TEST_ARRAY_SIZE * sizeof(struct audio_port));
+
+    status_t status = af->listAudioPorts(&num_ports, ports);
+
+    sleep(1);
+
+    // Check that the memory content above the max allowed array size was not changed
+    char *ptr = (char *)(ports + MAX_ARRAY_SIZE);
+    for (size_t i = 0; i < TEST_ARRAY_SIZE - MAX_ARRAY_SIZE; i++) {
+        if (ptr[i * sizeof(struct audio_port)] != TEST_PATTERN) {
+            free(ports);
+            return false;
+        }
+    }
+
+    free(ports);
+
+    // Check that mediaserver did not crash
+    if (dr->afIsDead()) {
+        return false;
+    }
+
+    return true;
+}
+
+jboolean android_security_cts_AudioFlinger_test_listAudioPatches(JNIEnv* env __unused,
+                                                           jobject thiz __unused)
+{
+    sp<IAudioFlinger> af;
+    sp<MyDeathClient> dr;
+
+    if (!connectAudioFlinger(af, dr)) {
+        return false;
+    }
+
+    unsigned int num_patches = TEST_ARRAY_SIZE;
+    struct audio_patch *patches =
+            (struct audio_patch *)calloc(TEST_ARRAY_SIZE, sizeof(struct audio_patch));
+
+    memset(patches, TEST_PATTERN, TEST_ARRAY_SIZE * sizeof(struct audio_patch));
+
+    status_t status = af->listAudioPatches(&num_patches, patches);
+
+    sleep(1);
+
+    // Check that the memory content above the max allowed array size was not changed
+    char *ptr = (char *)(patches + MAX_ARRAY_SIZE);
+    for (size_t i = 0; i < TEST_ARRAY_SIZE - MAX_ARRAY_SIZE; i++) {
+        if (ptr[i * sizeof(struct audio_patch)] != TEST_PATTERN) {
+            free(patches);
+            return false;
+        }
+    }
+
+    free(patches);
+
+    // Check that mediaserver did not crash
+    if (dr->afIsDead()) {
+        return false;
+    }
+
+    return true;
+}
+
+static JNINativeMethod gMethods[] = {
+    {  "native_test_setMasterMute", "()Z",
+            (void *) android_security_cts_AudioFlinger_test_setMasterMute },
+    {  "native_test_setMasterVolume", "()Z",
+            (void *) android_security_cts_AudioFlinger_test_setMasterVolume },
+    {  "native_test_listAudioPorts", "()Z",
+            (void *) android_security_cts_AudioFlinger_test_listAudioPorts },
+    {  "native_test_listAudioPatches", "()Z",
+            (void *) android_security_cts_AudioFlinger_test_listAudioPatches },
+};
+
+int register_android_security_cts_AudioFlingerBinderTest(JNIEnv* env)
+{
+    jclass clazz = env->FindClass("android/security/cts/AudioFlingerBinderTest");
+    return env->RegisterNatives(clazz, gMethods,
+            sizeof(gMethods) / sizeof(JNINativeMethod));
+}
diff --git a/tests/tests/security/jni/android_security_cts_NativeCodeTest.cpp b/tests/tests/security/jni/android_security_cts_NativeCodeTest.cpp
index 350309b..ec7e4bd 100644
--- a/tests/tests/security/jni/android_security_cts_NativeCodeTest.cpp
+++ b/tests/tests/security/jni/android_security_cts_NativeCodeTest.cpp
@@ -35,6 +35,7 @@
 #include <inttypes.h>
 #include <linux/sysctl.h>
 #include <arpa/inet.h>
+#include <linux/ipc.h>
 
 /*
  * Returns true iff this device is vulnerable to CVE-2013-2094.
@@ -251,6 +252,25 @@
     return true;
 }
 
+#define SHMEMSIZE 0x1 /* request one page */
+static jboolean android_security_cts_NativeCodeTest_doSysVipcTest(JNIEnv*, jobject)
+{
+    key_t key = 0x1a25;
+
+#if defined(__i386__) || (_MIPS_SIM == _MIPS_SIM_ABI32)
+    /* system call does not exist for x86 or mips 32 */
+    return true;
+#else
+    /*
+     * Not supported in bionic. Must directly invoke syscall
+     * Only acceptable errno is ENOSYS: shmget syscall
+     * function not implemented
+     */
+    return ((syscall(SYS_shmget, key, SHMEMSIZE, IPC_CREAT | 0666) == -1)
+                && (errno == ENOSYS));
+#endif
+}
+
 static JNINativeMethod gMethods[] = {
     {  "doPerfEventTest", "()Z",
             (void *) android_security_cts_NativeCodeTest_doPerfEventTest },
@@ -266,6 +286,8 @@
             (void *) android_security_cts_NativeCodeTest_doNvmapIocFromIdTest },
     {  "doPingPongRootTest", "()Z",
             (void *) android_security_cts_NativeCodeTest_doPingPongRootTest },
+    {  "doSysVipcTest", "()Z",
+            (void *) android_security_cts_NativeCodeTest_doSysVipcTest },
 };
 
 int register_android_security_cts_NativeCodeTest(JNIEnv* env)
diff --git a/tests/tests/security/src/android/security/cts/AudioEffectBinderTest.java b/tests/tests/security/src/android/security/cts/AudioEffectBinderTest.java
new file mode 100644
index 0000000..20d9615
--- /dev/null
+++ b/tests/tests/security/src/android/security/cts/AudioEffectBinderTest.java
@@ -0,0 +1,56 @@
+/*
+ * 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 android.security.cts;
+
+import android.media.audiofx.AudioEffect;
+
+import java.util.UUID;
+
+import junit.framework.TestCase;
+
+public class AudioEffectBinderTest extends TestCase {
+
+    static {
+        System.loadLibrary("ctssecurity_jni");
+    }
+
+    /**
+     * Checks that IEffect::command() cannot leak data.
+     */
+    public void test_isCommandSecure() throws Exception {
+        if (isEffectTypeAvailable(AudioEffect.EFFECT_TYPE_EQUALIZER)) {
+            assertTrue(native_test_isCommandSecure());
+        }
+    }
+
+    /* see AudioEffect.isEffectTypeAvailable(), implements hidden function */
+    private static boolean isEffectTypeAvailable(UUID type) {
+        AudioEffect.Descriptor[] desc = AudioEffect.queryEffects();
+        if (desc == null) {
+            return false;
+        }
+
+        for (int i = 0; i < desc.length; i++) {
+            if (desc[i].type.equals(type)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static native boolean native_test_isCommandSecure();
+}
diff --git a/tests/tests/security/src/android/security/cts/AudioFlingerBinderTest.java b/tests/tests/security/src/android/security/cts/AudioFlingerBinderTest.java
new file mode 100644
index 0000000..37c472e
--- /dev/null
+++ b/tests/tests/security/src/android/security/cts/AudioFlingerBinderTest.java
@@ -0,0 +1,63 @@
+/*
+ * 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 android.security.cts;
+
+import junit.framework.TestCase;
+
+public class AudioFlingerBinderTest extends TestCase {
+
+    static {
+        System.loadLibrary("ctssecurity_jni");
+    }
+
+    /**
+     * Checks that AudioSystem::setMasterMute() does not crash mediaserver if a duplicated output
+     * is opened.
+     */
+    public void test_setMasterMute() throws Exception {
+        assertTrue(native_test_setMasterMute());
+    }
+
+    /**
+     * Checks that AudioSystem::setMasterVolume() does not crash mediaserver if a duplicated output
+     * is opened.
+     */
+    public void test_setMasterVolume() throws Exception {
+        assertTrue(native_test_setMasterVolume());
+    }
+
+    /**
+     * Checks that IAudioFlinger::listAudioPorts() does not cause a memory overflow when passed a
+     * large number of ports.
+     */
+    public void test_listAudioPorts() throws Exception {
+        assertTrue(native_test_listAudioPorts());
+    }
+
+    /**
+     * Checks that IAudioFlinger::listAudioPatches() does not cause a memory overflow when passed a
+     * large number of ports.
+     */
+    public void test_listAudioPatches() throws Exception {
+        assertTrue(native_test_listAudioPatches());
+    }
+
+    private static native boolean native_test_setMasterMute();
+    private static native boolean native_test_setMasterVolume();
+    private static native boolean native_test_listAudioPorts();
+    private static native boolean native_test_listAudioPatches();
+}
diff --git a/tests/tests/security/src/android/security/cts/HwRngTest.java b/tests/tests/security/src/android/security/cts/HwRngTest.java
index f9ce6be..7654b6f 100644
--- a/tests/tests/security/src/android/security/cts/HwRngTest.java
+++ b/tests/tests/security/src/android/security/cts/HwRngTest.java
@@ -17,11 +17,10 @@
 package android.security.cts;
 
 import android.cts.util.CtsAndroidTestCase;
-import com.android.cts.util.ReportLog;
-import com.android.cts.util.ResultType;
-import com.android.cts.util.ResultUnit;
 
-import junit.framework.TestCase;
+import com.android.compatibility.common.util.DeviceReportLog;
+import com.android.compatibility.common.util.ResultType;
+import com.android.compatibility.common.util.ResultUnit;
 
 import java.io.BufferedReader;
 import java.io.ByteArrayOutputStream;
@@ -51,18 +50,19 @@
      * Reports whether the {@code /dev/hw_random} device is found. This test always passes.
      */
     public void testDeviceFilePresent() {
-        ReportLog report = getReportLog();
+        DeviceReportLog report = new DeviceReportLog();
         // Need to report at least one value, otherwise summary won't be logged.
-        report.printValue(
+        report.addValue(
                 DEV_HW_RANDOM + " found",
                 DEV_HW_RANDOM.exists() ? 1 : 0,
                 ResultType.WARNING,
                 ResultUnit.NONE);
-        report.printSummary(
+        report.setSummary(
                 "Hardware RNG exposed",
                 DEV_HW_RANDOM.exists() ? 1 : 0,
                 ResultType.WARNING,
                 ResultUnit.NONE);
+        report.submit(getInstrumentation());
     }
 
     /**
diff --git a/tests/tests/security/src/android/security/cts/NativeCodeTest.java b/tests/tests/security/src/android/security/cts/NativeCodeTest.java
index ab41b4f..5ec6ddc 100644
--- a/tests/tests/security/src/android/security/cts/NativeCodeTest.java
+++ b/tests/tests/security/src/android/security/cts/NativeCodeTest.java
@@ -64,6 +64,15 @@
                    + "https://github.com/torvalds/linux/commit/a134f083e79f",
                    doPingPongRootTest());
     }
+
+    public void testSysVipc() throws Exception {
+        assertTrue("Android does not support Sys V IPC, it must "
+                   + "be removed from the kernel. In the kernel config: "
+                   + "Change \"CONFIG_SYSVIPC=y\" to \"# CONFIG_SYSVIPC is not set\" "
+                   + "and rebuild.",
+                   doSysVipcTest());
+    }
+
     /**
      * Returns true iff this device is vulnerable to CVE-2013-2094.
      * A patch for CVE-2013-2094 can be found at
@@ -140,4 +149,25 @@
      */
     private static native boolean doPingPongRootTest();
 
+    /**
+     * Test that SysV IPC has been removed from the kernel.
+     *
+     * Returns true if SysV IPC has been removed.
+     *
+     * System V IPCs are not compliant with Android's application lifecycle because allocated
+     * resources are not freed by the low memory killer. This lead to global kernel resource leakage.
+     *
+     * For example, there is no way to automatically release a SysV semaphore
+     * allocated in the kernel when:
+     * - a buggy or malicious process exits
+     * - a non-buggy and non-malicious process crashes or is explicitly killed.
+     *
+     * Killing processes automatically to make room for new ones is an
+     * important part of Android's application lifecycle implementation. This means
+     * that, even assuming only non-buggy and non-malicious code, it is very likely
+     * that over time, the kernel global tables used to implement SysV IPCs will fill
+     * up.
+     */
+    private static native boolean doSysVipcTest();
+
 }
diff --git a/tests/tests/security/src/android/security/cts/RestrictedInformationTest.java b/tests/tests/security/src/android/security/cts/RestrictedInformationTest.java
new file mode 100644
index 0000000..d0247e9
--- /dev/null
+++ b/tests/tests/security/src/android/security/cts/RestrictedInformationTest.java
@@ -0,0 +1,64 @@
+/*
+ * 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 android.security.cts;
+
+import android.test.AndroidTestCase;
+import java.net.NetworkInterface;
+import java.net.SocketException;
+import java.util.Enumeration;
+import android.content.pm.PackageManager;
+import android.net.wifi.WifiManager;
+import android.content.Context;
+
+/**
+ * Check that restricted information is not available
+ * to the untrusted_app domain
+ */
+public class RestrictedInformationTest extends AndroidTestCase {
+   /*
+    * Test that wifi Mac address is not available through sysfs
+    */
+    public void testWifiMacAddr() throws Exception {
+        /* if wifi does not exist, exit - PASS */
+        PackageManager pm = getContext().getPackageManager();
+        if (!pm.hasSystemFeature(PackageManager.FEATURE_WIFI))
+            return;
+
+        /* Wifi exists, but is not on - FAIL */
+        WifiManager wifi = (WifiManager) getContext().getSystemService(Context.WIFI_SERVICE);
+        assertTrue("Wifi must be enabled to pass this test.", wifi.isWifiEnabled());
+
+        /* Enumerate through the interfaces */
+        Enumeration<NetworkInterface> theInterfaces = NetworkInterface.getNetworkInterfaces();
+
+        while (theInterfaces.hasMoreElements()) {
+            NetworkInterface netif = theInterfaces.nextElement();
+            String name = netif.getName();
+            /* some devices label wifi network interface as eth */
+            if (!name.contains("wlan") && !name.contains("eth"))
+                continue;
+            /* PASS means that getHardwareAddress throws a socket exception */
+            try {
+                byte[] hwAddr = netif.getHardwareAddress();
+                fail("Mac address for " + name + "is accessible: " + new String(hwAddr) +
+                        "\nTo pass this test, label the address with the sysfs_mac_address\n" +
+                        "selinux label. " +
+                        "e.g. https://android-review.googlesource.com/#/c/162180/1\n");
+            } catch (SocketException se) {/* socket exception if MAC blocked - PASS */}
+        }
+    }
+}
diff --git a/tests/tests/text/src/android/text/cts/BidiFormatterTest.java b/tests/tests/text/src/android/text/cts/BidiFormatterTest.java
index 5ace8b2..3e5db4d 100644
--- a/tests/tests/text/src/android/text/cts/BidiFormatterTest.java
+++ b/tests/tests/text/src/android/text/cts/BidiFormatterTest.java
@@ -33,7 +33,7 @@
             new BidiFormatter.Builder(true /* RTL context */).stereoReset(false).build();
 
     private static final String EN = "abba";
-    private static final String HE = "\u05e0\u05e1";
+    private static final String HE = "\u05E0\u05E1";
 
     private static final String LRM = "\u200E";
     private static final String RLM = "\u200F";
@@ -49,6 +49,18 @@
         assertEquals(true, BidiFormatter.getInstance(true).isRtlContext());
     }
 
+    public void testCachedInstances() {
+        // Test that we get the same cached static instances for simple cases
+        BidiFormatter defaultFormatterInstance = BidiFormatter.getInstance();
+        assertTrue(defaultFormatterInstance == LTR_FMT || defaultFormatterInstance == RTL_FMT);
+
+        assertEquals(LTR_FMT, BidiFormatter.getInstance(false));
+        assertEquals(RTL_FMT, BidiFormatter.getInstance(true));
+
+        assertEquals(LTR_FMT, BidiFormatter.getInstance(false));
+        assertEquals(RTL_FMT, BidiFormatter.getInstance(Locale.forLanguageTag("ar")));
+    }
+
     public void testBuilderIsRtlContext() {
         assertEquals(false, new BidiFormatter.Builder(false).build().isRtlContext());
         assertEquals(true, new BidiFormatter.Builder(true).build().isRtlContext());
diff --git a/tests/tests/text/src/android/text/cts/HtmlTest.java b/tests/tests/text/src/android/text/cts/HtmlTest.java
index cf47ab9..0531875 100644
--- a/tests/tests/text/src/android/text/cts/HtmlTest.java
+++ b/tests/tests/text/src/android/text/cts/HtmlTest.java
@@ -198,6 +198,23 @@
                 ret);
     }
 
+    public void testMarkupFromHtml() throws Exception {
+        Spanned s;
+        final int expectedStart = 6;
+        final int expectedEnd = expectedStart + 6;
+
+        String tags[] = {"del", "s", "strike"};
+        for (String tag : tags) {
+            String source = String.format("Hello <%s>struck</%s> world", tag, tag);
+            Spanned spanned = Html.fromHtml(source);
+            Object[] spans = spanned.getSpans(0, spanned.length(), Object.class);
+            assertEquals(1, spans.length);
+            assertEquals(StrikethroughSpan.class, spans[0].getClass());
+            assertEquals(expectedStart, spanned.getSpanStart(spans[0]));
+            assertEquals(expectedEnd, spanned.getSpanEnd(spans[0]));
+        }
+    }
+
     public void testImg() throws Exception {
         Spanned s = Html.fromHtml("yes<img src=\"http://example.com/foo.gif\">no");
         assertEquals("<p dir=\"ltr\">yes<img src=\"http://example.com/foo.gif\">no</p>\n",
diff --git a/tests/tests/text/src/android/text/cts/SpannableStringBuilderTest.java b/tests/tests/text/src/android/text/cts/SpannableStringBuilderTest.java
index 36b081c..4b26eac 100644
--- a/tests/tests/text/src/android/text/cts/SpannableStringBuilderTest.java
+++ b/tests/tests/text/src/android/text/cts/SpannableStringBuilderTest.java
@@ -553,6 +553,11 @@
         UnderlineSpan span2 = new UnderlineSpan();
         builder.setSpan(span1, 1, 2, Spanned.SPAN_POINT_POINT);
         builder.setSpan(span2, 4, 8, Spanned.SPAN_MARK_POINT);
+
+        Object[] emptySpans = builder.getSpans(0, 10, null);
+        assertNotNull(emptySpans);
+        assertEquals(0, emptySpans.length);
+
         UnderlineSpan[] underlineSpans = builder.getSpans(0, 10, UnderlineSpan.class);
         assertEquals(2, underlineSpans.length);
         assertSame(span1, underlineSpans[0]);
diff --git a/tests/tests/text/src/android/text/cts/StaticLayoutTest.java b/tests/tests/text/src/android/text/cts/StaticLayoutTest.java
index c6f4049..f8b3cc4 100644
--- a/tests/tests/text/src/android/text/cts/StaticLayoutTest.java
+++ b/tests/tests/text/src/android/text/cts/StaticLayoutTest.java
@@ -975,4 +975,25 @@
             assertEquals(testLabel, 6, layout.getOffsetToRightOf(7));
         }
     }
+
+    public void testGetOffsetForHorizontal_Multilines() {
+        // Emoticons for surrogate pairs tests.
+        String testString = "\uD83D\uDE00\uD83D\uDE01\uD83D\uDE02\uD83D\uDE03\uD83D\uDE04";
+        final float width = mDefaultPaint.measureText(testString, 0, 6);
+        StaticLayout layout = new StaticLayout(testString, mDefaultPaint, (int)width,
+                DEFAULT_ALIGN, SPACE_MULTI, SPACE_ADD, true);
+        // We expect the line break to be after the third emoticon, but we allow flexibility of the
+        // line break algorithm as long as the break is within the string. These other cases might
+        // happen if for example the font has kerning between emoticons.
+        final int lineBreakOffset = layout.getOffsetForHorizontal(1, 0.0f);
+        assertEquals(0, layout.getLineForOffset(lineBreakOffset - 1));
+
+        assertEquals(0, layout.getOffsetForHorizontal(0, 0.0f));
+        assertEquals(lineBreakOffset - 2, layout.getOffsetForHorizontal(0, width));
+        assertEquals(lineBreakOffset - 2, layout.getOffsetForHorizontal(0, width * 2));
+
+        final int lineCount = layout.getLineCount();
+        assertEquals(testString.length(), layout.getOffsetForHorizontal(lineCount - 1, width));
+        assertEquals(testString.length(), layout.getOffsetForHorizontal(lineCount - 1, width * 2));
+    }
 }
diff --git a/tests/tests/text/src/android/text/cts/TextUtilsTest.java b/tests/tests/text/src/android/text/cts/TextUtilsTest.java
index 9258318..0da1eb4 100644
--- a/tests/tests/text/src/android/text/cts/TextUtilsTest.java
+++ b/tests/tests/text/src/android/text/cts/TextUtilsTest.java
@@ -1330,11 +1330,20 @@
     }
 
     public void testIsDigitsOnly() {
+        assertTrue(TextUtils.isDigitsOnly(""));
         assertFalse(TextUtils.isDigitsOnly("no digit"));
         assertFalse(TextUtils.isDigitsOnly("character and 56 digits"));
         assertTrue(TextUtils.isDigitsOnly("0123456789"));
         assertFalse(TextUtils.isDigitsOnly("1234 56789"));
 
+        // U+104A0 OSMANYA DIGIT ZERO
+        assertTrue(TextUtils.isDigitsOnly(new String(Character.toChars(0x104A0))));
+        // U+10858 IMPERIAL ARAMAIC NUMBER ONE
+        assertFalse(TextUtils.isDigitsOnly(new String(Character.toChars(0x10858))));
+
+        assertFalse(TextUtils.isDigitsOnly("\uD801")); // lonely lead surrogate
+        assertFalse(TextUtils.isDigitsOnly("\uDCA0")); // lonely trailing surrogate
+
         try {
             TextUtils.isDigitsOnly(null);
             fail("Should throw NullPointerException!");
@@ -1352,7 +1361,7 @@
 
     public void testIsGraphicChar() {
         assertTrue(TextUtils.isGraphic('a'));
-        assertTrue(TextUtils.isGraphic("\uBA00"));
+        assertTrue(TextUtils.isGraphic('\uBA00'));
 
         // LINE_SEPARATOR
         assertFalse(TextUtils.isGraphic('\u2028'));
@@ -1387,6 +1396,11 @@
 
         assertTrue(TextUtils.isGraphic("a\u2028\u2029\u0085\u0D00\uD800\u0020"));
 
+        assertTrue(TextUtils.isGraphic("\uD83D\uDC0C")); // U+1F40C SNAIL
+        assertFalse(TextUtils.isGraphic("\uDB40\uDC01")); // U+E0000 (unassigned)
+        assertFalse(TextUtils.isGraphic("\uDB3D")); // unpaired high surrogate
+        assertFalse(TextUtils.isGraphic("\uDC0C")); // unpaired low surrogate
+
         try {
             TextUtils.isGraphic(null);
             fail("Should throw NullPointerException!");
diff --git a/tests/tests/text/src/android/text/format/cts/FormatterTest.java b/tests/tests/text/src/android/text/format/cts/FormatterTest.java
index 9c3c45d..4be3d2d3 100644
--- a/tests/tests/text/src/android/text/format/cts/FormatterTest.java
+++ b/tests/tests/text/src/android/text/format/cts/FormatterTest.java
@@ -32,9 +32,14 @@
         BigDecimal bd = new BigDecimal((long) 1024, mc);
 
         // test different long values with various length
-        assertEquals("0.00 B", Formatter.formatFileSize(mContext, 0));
-
-        assertEquals("899 B", Formatter.formatFileSize(mContext, 899));
+        assertEquals("0 B", Formatter.formatFileSize(mContext, 0));
+        assertEquals("1 B", Formatter.formatFileSize(mContext, 1));
+        assertEquals("9 B", Formatter.formatFileSize(mContext, 9));
+        assertEquals("10 B", Formatter.formatFileSize(mContext, 10));
+        assertEquals("99 B", Formatter.formatFileSize(mContext, 99));
+        assertEquals("100 B", Formatter.formatFileSize(mContext, 100));
+        assertEquals("900 B", Formatter.formatFileSize(mContext, 900));
+        assertEquals("0.90 KB", Formatter.formatFileSize(mContext, 901));
 
         assertEquals("1.00 KB", Formatter.formatFileSize(mContext, bd.pow(1).longValue()));
 
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/ActivityTestBase.java b/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/ActivityTestBase.java
index 526f4f9..69cb688 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/ActivityTestBase.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/ActivityTestBase.java
@@ -123,30 +123,10 @@
         getActivity().runOnUiThread(finishRunnable);
     }
 
-    static int[] getBitmapPixels(Bitmap bitmap) {
-        int[] pixels = new int[bitmap.getWidth() * bitmap.getHeight()];
-        bitmap.getPixels(pixels, 0, bitmap.getWidth(),
-                0, 0, bitmap.getWidth(), bitmap.getHeight());
-        return pixels;
-    }
-
-    private Bitmap takeScreenshotImpl(Point testOffset) {
-        Bitmap source = getInstrumentation().getUiAutomation().takeScreenshot();
-        return Bitmap.createBitmap(source, testOffset.x, testOffset.y, TEST_WIDTH, TEST_HEIGHT);
-    }
-
     public Bitmap takeScreenshot(Point testOffset) {
         getInstrumentation().waitForIdleSync();
-        Bitmap bitmap1 = takeScreenshotImpl(testOffset);
-        Bitmap bitmap2;
-        int count = 0;
-        do  {
-            bitmap2 = bitmap1;
-            bitmap1 = takeScreenshotImpl(testOffset);
-            count++;
-        } while (count < MAX_SCREEN_SHOTS &&
-                !Arrays.equals(getBitmapPixels(bitmap2), getBitmapPixels(bitmap1)));
-        return bitmap1;
+        Bitmap source = getInstrumentation().getUiAutomation().takeScreenshot();
+        return Bitmap.createBitmap(source, testOffset.x, testOffset.y, TEST_WIDTH, TEST_HEIGHT);
     }
 
     /**
diff --git a/tests/tests/util/src/android/util/cts/LocaleListTest.java b/tests/tests/util/src/android/util/cts/LocaleListTest.java
new file mode 100644
index 0000000..1703591
--- /dev/null
+++ b/tests/tests/util/src/android/util/cts/LocaleListTest.java
@@ -0,0 +1,194 @@
+/*
+ * 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 android.util.cts;
+
+import android.util.LocaleList;
+import android.test.AndroidTestCase;
+
+import java.util.Locale;
+
+public class LocaleListTest extends AndroidTestCase {
+    public void testEmptyLocaleList() {
+        LocaleList ll = new LocaleList();
+        assertNotNull(ll);
+        assertTrue(ll.isEmpty());
+        assertEquals(0, ll.size());
+        assertNull(ll.getPrimary());
+        assertNull(ll.get(1));
+        assertNull(ll.get(10));
+
+        ll = new LocaleList((Locale) null);
+        assertNotNull(ll);
+        assertTrue(ll.isEmpty());
+        assertEquals(0, ll.size());
+        assertNull(ll.getPrimary());
+        assertNull(ll.get(1));
+        assertNull(ll.get(10));
+
+        ll = new LocaleList((Locale[]) null);
+        assertNotNull(ll);
+        assertTrue(ll.isEmpty());
+        assertEquals(0, ll.size());
+        assertNull(ll.getPrimary());
+        assertNull(ll.get(1));
+        assertNull(ll.get(10));
+
+        ll = new LocaleList(new Locale[0]);
+        assertNotNull(ll);
+        assertTrue(ll.isEmpty());
+        assertEquals(0, ll.size());
+        assertNull(ll.getPrimary());
+        assertNull(ll.get(1));
+        assertNull(ll.get(10));
+    }
+
+    public void testOneMemberLocaleList() {
+        final LocaleList ll = new LocaleList(Locale.US);
+        assertNotNull(ll);
+        assertFalse(ll.isEmpty());
+        assertEquals(1, ll.size());
+        assertEquals(Locale.US, ll.getPrimary());
+        assertEquals(Locale.US, ll.get(0));
+        assertNull(ll.get(10));
+    }
+
+    public void testTwoMemberLocaleList() {
+        final Locale enPH = Locale.forLanguageTag("en-PH");
+        final Locale[] la = {enPH, Locale.US};
+        final LocaleList ll = new LocaleList(la);
+        assertNotNull(ll);
+        assertFalse(ll.isEmpty());
+        assertEquals(2, ll.size());
+        assertEquals(enPH, ll.getPrimary());
+        assertEquals(enPH, ll.get(0));
+        assertEquals(Locale.US, ll.get(1));
+        assertNull(ll.get(10));
+    }
+
+    public void testNullArguments() {
+        final Locale[] la = {Locale.US, null};
+        LocaleList ll = null;
+        try {
+            ll = new LocaleList(la);
+            fail("Initializing a LocaleList with an array containing null should throw.");
+        } catch (Throwable e) {
+            assertEquals(NullPointerException.class, e.getClass());
+        }
+    }
+
+    public void testRepeatedArguments() {
+        final Locale[] la = {Locale.US, Locale.US};
+        LocaleList ll = null;
+        try {
+            ll = new LocaleList(la);
+            fail("Initializing a LocaleList with an array containing duplicates should throw.");
+        } catch (Throwable e) {
+            assertEquals(IllegalArgumentException.class, e.getClass());
+        }
+    }
+
+    public void testEquals() {
+        final LocaleList empty = new LocaleList();
+        final LocaleList anotherEmpty = new LocaleList();
+        LocaleList oneMember = new LocaleList(Locale.US);
+        LocaleList sameOneMember = new LocaleList(new Locale("en", "US"));
+        LocaleList differentOneMember = new LocaleList(Locale.FRENCH);
+        Locale[] la = {Locale.US, Locale.FRENCH};
+        LocaleList twoMember = new LocaleList(la);
+
+        assertFalse(empty.equals(null));
+        assertFalse(oneMember.equals(null));
+
+        assertFalse(empty.equals(new Object()));
+
+        assertTrue(empty.equals(empty));
+        assertTrue(oneMember.equals(oneMember));
+
+        assertFalse(empty.equals(oneMember));
+        assertFalse(oneMember.equals(twoMember));
+
+        assertFalse(oneMember.equals(differentOneMember));
+
+        assertTrue(empty.equals(anotherEmpty));
+        assertTrue(oneMember.equals(sameOneMember));
+    }
+
+    public void testHashCode() {
+        final LocaleList empty = new LocaleList();
+        final LocaleList anotherEmpty = new LocaleList();
+        Locale[] la1 = {Locale.US};
+        LocaleList oneMember = new LocaleList(la1);
+        LocaleList sameOneMember = new LocaleList(la1);
+
+        assertEquals(empty.hashCode(), anotherEmpty.hashCode());
+        assertEquals(oneMember.hashCode(), sameOneMember.hashCode());
+    }
+
+    public void testToString() {
+        LocaleList ll = new LocaleList();
+        assertEquals("[]", ll.toString());
+
+        final Locale[] la1 = {Locale.US};
+        ll = new LocaleList(la1);
+        assertEquals("["+Locale.US.toString()+"]", ll.toString());
+
+        final Locale[] la2 = {Locale.US, Locale.FRENCH};
+        ll = new LocaleList(la2);
+        assertEquals("["+Locale.US.toString()+","+Locale.FRENCH.toString()+"]", ll.toString());
+    }
+
+    public void testToLanguageTags() {
+        LocaleList ll = new LocaleList();
+        assertEquals("", ll.toLanguageTags());
+
+        final Locale[] la1 = {Locale.US};
+        ll = new LocaleList(la1);
+        assertEquals(Locale.US.toLanguageTag(), ll.toLanguageTags());
+
+        final Locale[] la2 = {Locale.US, Locale.FRENCH};
+        ll = new LocaleList(la2);
+        assertEquals(Locale.US.toLanguageTag()+","+Locale.FRENCH.toLanguageTag(),
+                ll.toLanguageTags());
+    }
+
+    public void testGetEmptyLocaleList() {
+        LocaleList empty = LocaleList.getEmptyLocaleList();
+        LocaleList anotherEmpty = LocaleList.getEmptyLocaleList();
+        LocaleList constructedEmpty = new LocaleList();
+
+        assertEquals(constructedEmpty, empty);
+        assertSame(empty, anotherEmpty);
+    }
+
+    public void testForLanguageTags() {
+        assertEquals(LocaleList.getEmptyLocaleList(), LocaleList.forLanguageTags(null));
+        assertEquals(LocaleList.getEmptyLocaleList(), LocaleList.forLanguageTags(""));
+
+        assertEquals(new LocaleList(Locale.forLanguageTag("en-US")),
+                LocaleList.forLanguageTags("en-US"));
+
+        final Locale[] la = {Locale.forLanguageTag("en-PH"), Locale.forLanguageTag("en-US")};
+        assertEquals(new LocaleList(la), LocaleList.forLanguageTags("en-PH,en-US"));
+    }
+
+    public void testGetDefault() {
+        LocaleList ll = LocaleList.getDefault();
+        assertNotNull(ll);
+        assertTrue(ll.size() >= 1);
+        assertEquals(Locale.getDefault(), ll.getPrimary());
+    }
+}
diff --git a/tests/tests/widget/src/android/widget/cts/CheckedTextViewTest.java b/tests/tests/widget/src/android/widget/cts/CheckedTextViewTest.java
index c5d3bd1..fbcfc6a 100644
--- a/tests/tests/widget/src/android/widget/cts/CheckedTextViewTest.java
+++ b/tests/tests/widget/src/android/widget/cts/CheckedTextViewTest.java
@@ -266,6 +266,17 @@
         assertFalse(checkedTextView.isLayoutRequested());
     }
 
+    public void testSetCheckMarkByMixedTypes() {
+        CheckedTextView checkedTextView = new MockCheckedTextView(mActivity);
+        cleanUpForceLayoutFlags(checkedTextView);
+
+        // Specifically test for b/22626247 (AOSP issue 180455).
+        checkedTextView.setCheckMarkDrawable(R.drawable.scenery);
+        checkedTextView.setCheckMarkDrawable(null);
+        checkedTextView.setCheckMarkDrawable(R.drawable.scenery);
+        assertNotNull(checkedTextView.getCheckMarkDrawable());
+    }
+
     public void testOnDraw() {
         // Do not test. Implementation details.
     }
diff --git a/tests/tests/widget/src/android/widget/cts/RelativeLayout_LayoutParamsTest.java b/tests/tests/widget/src/android/widget/cts/RelativeLayout_LayoutParamsTest.java
index 07c7b77..79b6632 100644
--- a/tests/tests/widget/src/android/widget/cts/RelativeLayout_LayoutParamsTest.java
+++ b/tests/tests/widget/src/android/widget/cts/RelativeLayout_LayoutParamsTest.java
@@ -20,6 +20,7 @@
 
 import android.test.ActivityInstrumentationTestCase2;
 import android.test.ViewAsserts;
+import android.util.LayoutDirection;
 import android.view.View;
 import android.view.ViewGroup.MarginLayoutParams;
 import android.widget.RelativeLayout;
@@ -415,6 +416,29 @@
         assertEquals(Integer.MIN_VALUE, rules[RelativeLayout.ALIGN_LEFT]);
     }
 
+    public void testRemoveRule() {
+        RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(200, 300);
+
+        layoutParams.addRule(RelativeLayout.ALIGN_PARENT_START, RelativeLayout.TRUE);
+        layoutParams.resolveLayoutDirection(LayoutDirection.LTR);
+        assertEquals(0, layoutParams.getRule(RelativeLayout.ALIGN_PARENT_START));
+        assertEquals(RelativeLayout.TRUE, layoutParams.getRule(RelativeLayout.ALIGN_PARENT_LEFT));
+        assertEquals(0, layoutParams.getRule(RelativeLayout.CENTER_HORIZONTAL));
+
+        layoutParams.removeRule(RelativeLayout.ALIGN_PARENT_START);
+        layoutParams.addRule(RelativeLayout.CENTER_HORIZONTAL, RelativeLayout.TRUE);
+        layoutParams.resolveLayoutDirection(LayoutDirection.LTR);
+        assertEquals(0, layoutParams.getRule(RelativeLayout.ALIGN_PARENT_START));
+        assertEquals(0, layoutParams.getRule(RelativeLayout.ALIGN_PARENT_LEFT));
+        assertEquals(RelativeLayout.TRUE, layoutParams.getRule(RelativeLayout.CENTER_HORIZONTAL));
+
+        layoutParams.removeRule(RelativeLayout.CENTER_HORIZONTAL);
+        layoutParams.resolveLayoutDirection(LayoutDirection.LTR);
+        assertEquals(0, layoutParams.getRule(RelativeLayout.ALIGN_PARENT_START));
+        assertEquals(0, layoutParams.getRule(RelativeLayout.ALIGN_PARENT_LEFT));
+        assertEquals(0, layoutParams.getRule(RelativeLayout.CENTER_HORIZONTAL));
+    }
+
     public void testDebug() {
         RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(200, 300);
         assertNotNull(layoutParams.debug("test: "));
diff --git a/tests/tests/widget/src/android/widget/cts/TextViewTest.java b/tests/tests/widget/src/android/widget/cts/TextViewTest.java
index 3130a26..5c75dd4 100644
--- a/tests/tests/widget/src/android/widget/cts/TextViewTest.java
+++ b/tests/tests/widget/src/android/widget/cts/TextViewTest.java
@@ -74,6 +74,7 @@
 import android.text.style.URLSpan;
 import android.text.util.Linkify;
 import android.util.DisplayMetrics;
+import android.util.LocaleList;
 import android.util.TypedValue;
 import android.view.ContextMenu;
 import android.view.ContextMenu.ContextMenuInfo;
@@ -4040,6 +4041,41 @@
         }
     }
 
+    public void testTextLocales() {
+        TextView tv = new TextView(mActivity);
+        assertEquals(Locale.getDefault(), tv.getTextLocale());
+        assertEquals(LocaleList.getDefault(), tv.getTextLocales());
+
+        tv.setTextLocale(Locale.CHINESE);
+        assertEquals(Locale.CHINESE, tv.getTextLocale());
+        assertEquals(new LocaleList(Locale.CHINESE), tv.getTextLocales());
+
+        tv.setTextLocales(LocaleList.forLanguageTags("en,ja"));
+        assertEquals(Locale.forLanguageTag("en"), tv.getTextLocale());
+        assertEquals(LocaleList.forLanguageTags("en,ja"), tv.getTextLocales());
+
+        try {
+            tv.setTextLocale(null);
+            fail("Setting the text locale to null should throw");
+        } catch (Throwable e) {
+            assertEquals(IllegalArgumentException.class, e.getClass());
+        }
+
+        try {
+            tv.setTextLocales(null);
+            fail("Setting the text locales to null should throw");
+        } catch (Throwable e) {
+            assertEquals(IllegalArgumentException.class, e.getClass());
+        }
+
+        try {
+            tv.setTextLocales(new LocaleList());
+            fail("Setting the text locale to an empty list should throw");
+        } catch (Throwable e) {
+            assertEquals(IllegalArgumentException.class, e.getClass());
+        }
+    }
+
     public void testAllCapsLocalization() {
         String testString = "abcdefghijklmnopqrstuvwxyz";
 
diff --git a/tools/cts-device-info/Android.mk b/tools/cts-device-info/Android.mk
index 5f2b223..12e11b8 100644
--- a/tools/cts-device-info/Android.mk
+++ b/tools/cts-device-info/Android.mk
@@ -24,5 +24,7 @@
 
 LOCAL_PACKAGE_NAME := CtsDeviceInfo
 
-include $(BUILD_CTS_DEVICE_INFO_PACKAGE)
+# Tag this module as a cts_v2 test artifact
+LOCAL_COMPATIBILITY_SUITE := cts_v2
 
+include $(BUILD_CTS_DEVICE_INFO_PACKAGE)
diff --git a/tools/cts-device-info/AndroidTest.xml b/tools/cts-device-info/AndroidTest.xml
new file mode 100644
index 0000000..94b052f
--- /dev/null
+++ b/tools/cts-device-info/AndroidTest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<configuration description="Config for CTS Extensible Device Info Collector">
+    <include name="common-config" />
+    <option name="run-command:run-command" value="rm /sdcard/device-info-files" />
+    <option name="apk-installer:test-file-name" value="CtsDeviceInfo.apk" />
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.ResultFilePuller">
+        <option name="src-dir" value="/sdcard/device-info-files/"/>
+        <option name="dest-dir" value="device-info-files/"/>
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.InstrumentationTest" >
+        <!-- TODO(agathaman): Remove when b/23488971 is closed -->
+        <option name="rerun" value="false" />
+        <option name="package" value="com.android.compatibility.common.deviceinfo" />
+        <option name="runner" value="com.android.compatibility.common.deviceinfo.DeviceInfoInstrument" />
+    </test>
+</configuration>
diff --git a/tools/cts-device-info/src/com/android/cts/deviceinfo/SampleDeviceInfo.java b/tools/cts-device-info/src/com/android/cts/deviceinfo/SampleDeviceInfo.java
index 886193c..2bd5959 100644
--- a/tools/cts-device-info/src/com/android/cts/deviceinfo/SampleDeviceInfo.java
+++ b/tools/cts-device-info/src/com/android/cts/deviceinfo/SampleDeviceInfo.java
@@ -60,4 +60,3 @@
         endGroup(); // foo
     }
 }
-
diff --git a/tools/cts-tradefed/Android.mk b/tools/cts-tradefed/Android.mk
new file mode 100644
index 0000000..ee54b65
--- /dev/null
+++ b/tools/cts-tradefed/Android.mk
@@ -0,0 +1,35 @@
+# 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_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_SUITE_BUILD_NUMBER := $(BUILD_NUMBER)
+LOCAL_SUITE_NAME := CTS_V2
+LOCAL_SUITE_FULLNAME := "Compatibility Test Suite"
+LOCAL_SUITE_VERSION := 5.0
+
+LOCAL_MODULE := cts-tradefed_v2
+
+include $(BUILD_COMPATIBILITY_SUITE)
+
+# Build all sub-directories
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tools/cts-tradefed/README b/tools/cts-tradefed/README
new file mode 100644
index 0000000..880b8e6
--- /dev/null
+++ b/tools/cts-tradefed/README
@@ -0,0 +1,83 @@
+CTS Trade Federation
+---------------------
+
+CTS Trade Federation, cts-tradefed for short, is the next
+generation test harness for CTS.
+
+cts-tradefed is built on top of the Android Trade Federation test harness.
+
+It works in a similar manner to the prior CTS harness, but supports some
+advanced features such as:
+
+  - modular, flexible extensible design. cts-tradefed can be extended to
+support running CTS in a continuous test environment.
+  - supports sharding a CTS test run across multiple devices in parallel
+  - automatically continue a CTS test run on another device if connection
+is lost
+
+Configuring cts-tradefed
+------------------------
+
+1. Ensure 'adb' is in your current PATH. adb can be found in the
+Android SDK available from http://developer.android.com
+
+Example:
+  PATH=$PATH:/home/myuser/android-sdk-linux_x86/platform-tools
+
+2. Follow the 'Setting up your device' steps documented in the
+CTS User Manual. The CTS User Manual can be downloaded at
+http://source.android.com/compatibility/downloads.html
+
+3. Connect the device to the host machine.
+
+4. Ensure device is visible via 'adb devices'
+
+Using cts-tradefed
+-------------------
+
+To run a test plan on a single device:
+
+1. Make sure you have at least one device connected
+2. Launch the cts-tradefed console by running the 'cts-tradefed'. If you've
+downloaded and extracted the CTS zip, the script can be found at
+  android-cts/tools/cts-tradefed
+Or else if you are working from the Android source tree and have run make cts,
+the script can be found at
+  out/host/linux-x86/cts/android-cts/tools/cts-tradefed
+3. Type:
+'run cts --plan CTS' to run the default CTS plan
+
+Some other useful commands are
+
+To run a test module:
+'run cts --module <module_name>'
+
+To run a specific test:
+'run cts --test <test_name>'
+
+To shard a plan test run on multiple devices
+'run cts --plan CTS --shards <number of shards>
+note: all connected devices must be running the same build
+
+For more options:
+'run cts --help'
+
+CTS Tradefed Development
+------------------------
+See http://source.android.com for instructions on obtaining the Android
+platform source code and setting up a build environment.
+
+The source for the CTS extensions for tradefed can be found at
+<android source root>/cts/tools/tradefed-host
+
+The source for the tradefed framework can be found on the 'tradefed' branch.
+
+Perform these steps to build and run cts-tradefed from the development
+environment:
+cd <path to android source root>
+make cts
+cts-tradefed
+
+More documentation and details on using and extending trade federation will
+be forthcoming in the near future.
+
diff --git a/common/host-side/scripts/compatibility-tradefed_v2 b/tools/cts-tradefed/etc/Android.mk
old mode 100755
new mode 100644
similarity index 64%
rename from common/host-side/scripts/compatibility-tradefed_v2
rename to tools/cts-tradefed/etc/Android.mk
index f64e273..877f67c
--- a/common/host-side/scripts/compatibility-tradefed_v2
+++ b/tools/cts-tradefed/etc/Android.mk
@@ -1,16 +1,22 @@
-#!/bin/bash
-#
-# Copyright (C) 2014 The Android Open Source Project
+# 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
+#      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.
-echo "TODO(stuartscott): Add the wrapper to launch the executable. This will be done in the next CL"
+
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_PREBUILT_EXECUTABLES := cts-tradefed_v2
+include $(BUILD_HOST_PREBUILT)
+
diff --git a/tools/cts-tradefed/etc/cts-tradefed_v2 b/tools/cts-tradefed/etc/cts-tradefed_v2
new file mode 100755
index 0000000..e501e7f
--- /dev/null
+++ b/tools/cts-tradefed/etc/cts-tradefed_v2
@@ -0,0 +1,108 @@
+#!/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.
+
+# launcher script for cts-tradefed harness
+# can be used from an Android build environment, or a standalone cts zip
+
+checkFile() {
+    if [ ! -f "$1" ]; then
+        echo "Unable to locate $1"
+        exit
+    fi;
+}
+
+checkPath() {
+    if ! type -P $1 &> /dev/null; then
+        echo "Unable to find $1 in path."
+        exit
+    fi;
+}
+
+checkPath adb
+checkPath java
+
+# check java version
+JAVA_VERSION=$(java -version 2>&1 | head -n 2 | grep '[ "]1\.[67][\. "$$]')
+if [ "${JAVA_VERSION}" == "" ]; then
+    echo "Wrong java version. 1.6 or 1.7 is required."
+    exit
+fi
+
+# check debug flag and set up remote debugging
+if [ -n "${TF_DEBUG}" ]; then
+  if [ -z "${TF_DEBUG_PORT}" ]; then
+    TF_DEBUG_PORT=10088
+  fi
+  RDBG_FLAG=-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=${TF_DEBUG_PORT}
+fi
+
+# get OS
+HOST=`uname`
+if [ "$HOST" == "Linux" ]; then
+    OS="linux-x86"
+elif [ "$HOST" == "Darwin" ]; then
+    OS="darwin-x86"
+else
+    echo "Unrecognized OS"
+    exit
+fi
+
+# check if in Android build env
+if [ ! -z "${ANDROID_BUILD_TOP}" ]; then
+    if [ ! -z "${ANDROID_HOST_OUT}" ]; then
+      CTS_V2_ROOT=${ANDROID_HOST_OUT}/cts_v2
+    else
+      CTS_V2_ROOT=${ANDROID_BUILD_TOP}/${OUT_DIR:-out}/host/${OS}/cts_v2
+    fi
+    if [ ! -d ${CTS_V2_ROOT} ]; then
+        echo "Could not find $CTS_V2_ROOT in Android build environment. Try 'make cts_v2'"
+        exit
+    fi;
+fi;
+
+if [ -z ${CTS_V2_ROOT} ]; then
+    # assume we're in an extracted cts install
+    CTS_V2_ROOT="$(dirname $0)/../.."
+fi;
+
+JAR_DIR=${CTS_V2_ROOT}/android-cts_v2/tools
+JARS="tradefed-prebuilt
+  hosttestlib
+  compatibility-host-util
+  cts-tradefed_v2"
+
+for JAR in $JARS; do
+    checkFile ${JAR_DIR}/${JAR}.jar
+    JAR_PATH=${JAR_PATH}:${JAR_DIR}/${JAR}.jar
+done
+
+# load any shared libraries for host-side executables
+LIB_DIR=${CTS_V2_ROOT}/android-cts_v2/lib
+if [ "$HOST" == "Linux" ]; then
+    LD_LIBRARY_PATH=${LIB_DIR}:${LIB_DIR}64:${LD_LIBRARY_PATH}
+    export LD_LIBRARY_PATH
+elif [ "$HOST" == "Darwin" ]; then
+    DYLD_LIBRARY_PATH=${LIB_DIR}:${LIB_DIR}64:${DYLD_LIBRARY_PATH}
+    export DYLD_LIBRARY_PATH
+fi
+
+# include any host-side test jars
+HOST_TEST_JAR_PATH="$(ls -1 ${CTS_V2_ROOT}/android-cts_v2/testcases/*.jar)"
+
+CLASS_PATH=${JAR_PATH}:${HOST_TEST_JAR_PATH}
+
+java $RDBG_FLAG -cp ${CLASS_PATH} -DCTS_V2_ROOT=${CTS_V2_ROOT} com.android.compatibility.common.tradefed.command.CompatibilityConsole "$@"
+
diff --git a/tools/cts-tradefed/res/config/cts.xml b/tools/cts-tradefed/res/config/cts.xml
new file mode 100644
index 0000000..09ad328
--- /dev/null
+++ b/tools/cts-tradefed/res/config/cts.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<configuration description="Runs CTS from a pre-existing CTS installation">
+
+    <include name="everything" />
+
+    <option name="enable-root" value="false" />
+
+    <!-- Example Filters
+    <option name="compatibility-test:include-filter" value="arm64-v8a CtsSampleDeviceTestCases" />
+    <option name="compatibility-test:exclude-filter" value="CtsSampleHostTestCases" />
+    -->
+    <option name="compatibility-test:plan" value="cts" />
+
+</configuration>
diff --git a/tests/tests/acceleration/Android.mk b/tools/cts-tradefed/tests/Android.mk
similarity index 66%
rename from tests/tests/acceleration/Android.mk
rename to tools/cts-tradefed/tests/Android.mk
index d417371..7314fdb 100644
--- a/tests/tests/acceleration/Android.mk
+++ b/tools/cts-tradefed/tests/Android.mk
@@ -1,4 +1,4 @@
-# Copyright (C) 2011 The Android Open Source Project
+# 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.
@@ -12,22 +12,14 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-LOCAL_PATH:= $(call my-dir)
+LOCAL_PATH := $(call my-dir)
 
 include $(CLEAR_VARS)
 
-LOCAL_MODULE_TAGS := optional
-
-LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-
-LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner
-
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
-LOCAL_PACKAGE_NAME := CtsAccelerationTestCases
+LOCAL_MODULE := cts-tradefed-tests_v2
+LOCAL_MODULE_TAGS := optional
+LOCAL_JAVA_LIBRARIES := tradefed-prebuilt cts-tradefed_v2
 
-LOCAL_INSTRUMENTATION_FOR := CtsAccelerationTestStubs
-
-LOCAL_SDK_VERSION := current
-
-include $(BUILD_CTS_PACKAGE)
+include $(BUILD_HOST_JAVA_LIBRARY)
\ No newline at end of file
diff --git a/tools/cts-tradefed/tests/src/com/android/compatibility/tradefed/CtsTradefedTest.java b/tools/cts-tradefed/tests/src/com/android/compatibility/tradefed/CtsTradefedTest.java
new file mode 100644
index 0000000..2dcf1c9
--- /dev/null
+++ b/tools/cts-tradefed/tests/src/com/android/compatibility/tradefed/CtsTradefedTest.java
@@ -0,0 +1,55 @@
+/*
+ * 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.tradefed;
+
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildProvider;
+import com.android.tradefed.build.IFolderBuildInfo;
+import com.android.tradefed.util.FileUtil;
+
+import junit.framework.TestCase;
+
+import java.io.File;
+
+/**
+ * Tests for cts-tradefed.
+ */
+public class CtsTradefedTest extends TestCase {
+
+    private static final String PROPERTY_NAME = "CTS_V2_ROOT";
+    private static final String SUITE_FULL_NAME = "Compatibility Test Suite";
+    private static final String SUITE_NAME = "CTS_V2";
+    private static final String SUITE_PLAN = "cts";
+    private static final String DYNAMIC_CONFIG_URL = "";
+
+    public void testSuiteInfoLoad() throws Exception {
+        // Test the values in the manifest can be loaded
+        File root = FileUtil.createTempDir("root");
+        System.setProperty(PROPERTY_NAME, root.getAbsolutePath());
+        File base = new File(root, "android-cts_v2");
+        base.mkdirs();
+        File tests = new File(base, "testcases");
+        tests.mkdirs();
+        CompatibilityBuildProvider provider = new CompatibilityBuildProvider();
+        IFolderBuildInfo info = (IFolderBuildInfo) provider.getBuild();
+        CompatibilityBuildHelper helper = new CompatibilityBuildHelper(info);
+        helper.init(SUITE_PLAN, DYNAMIC_CONFIG_URL);
+        assertEquals("Incorrect suite full name", SUITE_FULL_NAME, helper.getSuiteFullName());
+        assertEquals("Incorrect suite name", SUITE_NAME, helper.getSuiteName());
+        FileUtil.recursiveDelete(root);
+        System.clearProperty(PROPERTY_NAME);
+    }
+}
diff --git a/tools/cts-xml-generator/src/Android.mk b/tools/cts-xml-generator/src/Android.mk
index a6d85b6..94c561b 100644
--- a/tools/cts-xml-generator/src/Android.mk
+++ b/tools/cts-xml-generator/src/Android.mk
@@ -18,15 +18,13 @@
 # ============================================================
 include $(CLEAR_VARS)
 
-LOCAL_SRC_FILES := \
-    $(call all-subdir-java-files) \
-    ../../../libs/commonutil/src/com/android/cts/util/AbiUtils.java
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
 
 LOCAL_JAR_MANIFEST := MANIFEST.mf
 
 LOCAL_MODULE := cts-xml-generator
 LOCAL_MODULE_TAGS := optional
 
-LOCAL_STATIC_JAVA_LIBRARIES := vogarexpectlib
+LOCAL_STATIC_JAVA_LIBRARIES := vogarexpectlib compatibility-host-util
 
 include $(BUILD_HOST_JAVA_LIBRARY)
diff --git a/tools/cts-xml-generator/src/com/android/cts/xmlgenerator/XmlGenerator.java b/tools/cts-xml-generator/src/com/android/cts/xmlgenerator/XmlGenerator.java
index 328b855..96c83a9 100644
--- a/tools/cts-xml-generator/src/com/android/cts/xmlgenerator/XmlGenerator.java
+++ b/tools/cts-xml-generator/src/com/android/cts/xmlgenerator/XmlGenerator.java
@@ -16,12 +16,12 @@
 
 package com.android.cts.xmlgenerator;
 
-import com.android.cts.util.AbiUtils;
-
 import vogar.Expectation;
 import vogar.ExpectationStore;
 import vogar.Result;
 
+import com.android.compatibility.common.util.AbiUtils;
+
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.IOException;
@@ -259,7 +259,7 @@
         String[] unsupportedAbis = description.split(":")[1].split(",");
         for (String a : unsupportedAbis) {
             String abi = a.trim();
-            if (!AbiUtils.isAbiSupportedByCts(abi)) {
+            if (!AbiUtils.isAbiSupportedByCompatibility(abi)) {
                 throw new RuntimeException(
                         String.format("Unrecognised ABI %s in %s", abi, description));
             }
diff --git a/tools/tradefed-host/Android.mk b/tools/tradefed-host/Android.mk
index 1f73e95..c33a927 100644
--- a/tools/tradefed-host/Android.mk
+++ b/tools/tradefed-host/Android.mk
@@ -25,7 +25,7 @@
 LOCAL_MODULE := cts-tradefed
 LOCAL_MODULE_TAGS := optional
 LOCAL_JAVA_LIBRARIES := tradefed-prebuilt hosttestlib
-LOCAL_STATIC_JAVA_LIBRARIES := ctsdeviceinfolib
+LOCAL_STATIC_JAVA_LIBRARIES := ctsdeviceinfolib compatibility-host-util
 
 LOCAL_JAR_MANIFEST := MANIFEST.mf
 
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/build/CtsBuildProvider.java b/tools/tradefed-host/src/com/android/cts/tradefed/build/CtsBuildProvider.java
index fa930df..ca67746 100644
--- a/tools/tradefed-host/src/com/android/cts/tradefed/build/CtsBuildProvider.java
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/build/CtsBuildProvider.java
@@ -31,7 +31,7 @@
     @Option(name="cts-install-path", description="the path to the cts installation to use")
     private String mCtsRootDirPath = System.getProperty("CTS_ROOT");
 
-    public static final String CTS_BUILD_VERSION = "6.0_r0";
+    public static final String CTS_BUILD_VERSION = "5.0_r1.91";
     public static final String CTS_PACKAGE = "com.android.cts.tradefed.testtype";
 
     /**
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/command/CtsConsole.java b/tools/tradefed-host/src/com/android/cts/tradefed/command/CtsConsole.java
index 24239e6..4cb9e16 100644
--- a/tools/tradefed-host/src/com/android/cts/tradefed/command/CtsConsole.java
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/command/CtsConsole.java
@@ -15,6 +15,7 @@
  */
 package com.android.cts.tradefed.command;
 
+import com.android.compatibility.common.util.AbiUtils;
 import com.android.cts.tradefed.build.CtsBuildHelper;
 import com.android.cts.tradefed.build.CtsBuildProvider;
 import com.android.cts.tradefed.result.ITestResultRepo;
@@ -23,7 +24,6 @@
 import com.android.cts.tradefed.result.TestResultRepo;
 import com.android.cts.tradefed.testtype.ITestPackageRepo;
 import com.android.cts.tradefed.testtype.TestPackageRepo;
-import com.android.cts.util.AbiUtils;
 import com.android.tradefed.build.IFolderBuildInfo;
 import com.android.tradefed.command.Console;
 import com.android.tradefed.config.ArgsOptionParser;
@@ -120,7 +120,7 @@
                 CtsBuildHelper ctsBuild = getCtsBuildHelper();
                 if (ctsBuild != null) {
                     // FIXME may want to only add certain ABIs
-                    addDerivedPlan(ctsBuild, AbiUtils.getAbisSupportedByCts(), flatArgs);
+                    addDerivedPlan(ctsBuild, AbiUtils.getAbisSupportedByCompatibility(), flatArgs);
                 }
             }
         };
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/device/DeviceInfoCollector.java b/tools/tradefed-host/src/com/android/cts/tradefed/device/DeviceInfoCollector.java
index 61561a5..6dee016 100644
--- a/tools/tradefed-host/src/com/android/cts/tradefed/device/DeviceInfoCollector.java
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/device/DeviceInfoCollector.java
@@ -15,7 +15,7 @@
  */
 package com.android.cts.tradefed.device;
 
-import com.android.cts.util.AbiUtils;
+import com.android.compatibility.common.util.AbiUtils;
 import com.android.cts.tradefed.result.CtsXmlResultReporter;
 import com.android.ddmlib.IDevice;
 import com.android.ddmlib.Log;
@@ -55,7 +55,7 @@
     public static final Set<String> EXTENDED_IDS = new HashSet<String>();
 
     static {
-        for (String abi : AbiUtils.getAbisSupportedByCts()) {
+        for (String abi : AbiUtils.getAbisSupportedByCompatibility()) {
             IDS.add(AbiUtils.createId(abi, APP_PACKAGE_NAME));
             EXTENDED_IDS.add(AbiUtils.createId(abi, EXTENDED_APP_PACKAGE_NAME));
         }
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/result/CtsTestLogReporter.java b/tools/tradefed-host/src/com/android/cts/tradefed/result/CtsTestLogReporter.java
index b7f064f..71eb8f5 100644
--- a/tools/tradefed-host/src/com/android/cts/tradefed/result/CtsTestLogReporter.java
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/result/CtsTestLogReporter.java
@@ -16,8 +16,8 @@
 
 package com.android.cts.tradefed.result;
 
+import com.android.compatibility.common.util.AbiUtils;
 import com.android.cts.tradefed.device.DeviceInfoCollector;
-import com.android.cts.util.AbiUtils;
 import com.android.ddmlib.Log;
 import com.android.ddmlib.Log.LogLevel;
 import com.android.ddmlib.testrunner.TestIdentifier;
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/result/CtsXmlResultReporter.java b/tools/tradefed-host/src/com/android/cts/tradefed/result/CtsXmlResultReporter.java
index 51b457d..75ff138 100644
--- a/tools/tradefed-host/src/com/android/cts/tradefed/result/CtsXmlResultReporter.java
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/result/CtsXmlResultReporter.java
@@ -61,6 +61,8 @@
         implements ITestInvocationListener, ITestSummaryListener, ILogSaverListener {
 
     private static final String LOG_TAG = "CtsXmlResultReporter";
+    private static final String DEVICE_INFO = "DEVICE_INFO_";
+    private static final String DEVICE_INFO_EXT = ".deviceinfo.json";
 
     public static final String CTS_RESULT_DIR = "cts-result-dir";
     static final String TEST_RESULT_FILE_NAME = "testResult.xml";
@@ -238,6 +240,7 @@
     @Override
     public void testLogSaved(String dataName, LogDataType dataType, InputStreamSource dataStream,
             LogFile logFile) {
+        CLog.i("Got log for %s %s %s", dataName, dataType, logFile.getUrl());
         if (mIncludeTestLogTags && mCurrentTest != null) {
             TestLog log = TestLog.fromDataName(dataName, logFile.getUrl());
             if (log != null) {
@@ -336,7 +339,7 @@
     private void checkExtendedDeviceInfoMetrics(Map<String, String> runMetrics) {
         for (Map.Entry<String, String> metricEntry : runMetrics.entrySet()) {
             String value = metricEntry.getValue();
-            if (!value.endsWith(".deviceinfo.json")) {
+            if (!value.startsWith(DEVICE_INFO) && !value.endsWith(DEVICE_INFO_EXT)) {
                 CLog.e(String.format("%s failed: %s", metricEntry.getKey(), value));
             }
         }
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/result/PlanCreator.java b/tools/tradefed-host/src/com/android/cts/tradefed/result/PlanCreator.java
index 3881c0e..0926635 100644
--- a/tools/tradefed-host/src/com/android/cts/tradefed/result/PlanCreator.java
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/result/PlanCreator.java
@@ -22,7 +22,6 @@
 import com.android.cts.tradefed.testtype.ITestPlan;
 import com.android.cts.tradefed.testtype.TestPackageRepo;
 import com.android.cts.tradefed.testtype.TestPlan;
-import com.android.cts.util.AbiUtils;
 import com.android.ddmlib.Log;
 import com.android.ddmlib.Log.LogLevel;
 import com.android.ddmlib.testrunner.TestIdentifier;
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/result/Test.java b/tools/tradefed-host/src/com/android/cts/tradefed/result/Test.java
index 12a2b29..e25ea5a 100644
--- a/tools/tradefed-host/src/com/android/cts/tradefed/result/Test.java
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/result/Test.java
@@ -15,7 +15,8 @@
  */
 package com.android.cts.tradefed.result;
 
-import com.android.ddmlib.Log;
+import com.android.compatibility.common.util.MetricsXmlSerializer;
+import com.android.compatibility.common.util.ReportLog;
 import com.android.cts.tradefed.result.TestLog.TestLogType;
 
 import org.kxml2.io.KXmlSerializer;
@@ -38,27 +39,13 @@
     private static final String RESULT_ATTR = "result";
     private static final String SCENE_TAG = "FailedScene";
     private static final String STACK_TAG = "StackTrace";
-    private static final String SUMMARY_TAG = "Summary";
-    private static final String DETAILS_TAG = "Details";
-    private static final String VALUEARRAY_TAG = "ValueArray";
-    private static final String VALUE_TAG = "Value";
-    private static final String TARGET_ATTR = "target";
-    private static final String SCORETYPE_ATTR = "scoreType";
-    private static final String UNIT_ATTR = "unit";
-    private static final String SOURCE_ATTR = "source";
-    // separators for the message
-    private static final String LOG_SEPARATOR = "\\+\\+\\+";
-    private static final String LOG_ELEM_SEPARATOR = "\\|";
-
     private String mName;
     private CtsTestStatus mResult;
     private String mStartTime;
     private String mEndTime;
     private String mMessage;
     private String mStackTrace;
-    // summary and details passed from cts
-    private String mSummary;
-    private String mDetails;
+    private ReportLog mReport;
 
     /**
      * Log info for this test like a logcat dump or bugreport.
@@ -73,7 +60,7 @@
     }
 
     /**
-     * Create a {@link Test} from a {@link TestResult}.
+     * Create a {@link Test}.
      *
      * @param name
      */
@@ -135,20 +122,12 @@
         mMessage = getFailureMessageFromStackTrace(mStackTrace);
     }
 
-    public String getSummary() {
-        return mSummary;
+    public ReportLog getReportLog() {
+        return mReport;
     }
 
-    public void setSummary(String summary) {
-        mSummary = summary;
-    }
-
-    public String getDetails() {
-        return mDetails;
-    }
-
-    public void setDetails(String details) {
-        mDetails = details;
+    public void setReportLog(ReportLog report) {
+        mReport = report;
     }
 
     public void updateEndTime() {
@@ -185,113 +164,12 @@
             }
             serializer.endTag(CtsXmlResultReporter.ns, SCENE_TAG);
         }
-        if (mSummary != null) {
-            // <Summary message = "screen copies per sec" scoretype="higherBetter" unit="fps">
-            // 23938.82978723404</Summary>
-            PerfResultSummary summary = parseSummary(mSummary);
-            if (summary != null) {
-                serializer.startTag(CtsXmlResultReporter.ns, SUMMARY_TAG);
-                serializer.attribute(CtsXmlResultReporter.ns, MESSAGE_ATTR, summary.mMessage);
-                if (summary.mTarget.length() != 0 && !summary.mTarget.equals(" ")) {
-                    serializer.attribute(CtsXmlResultReporter.ns, TARGET_ATTR, summary.mTarget);
-                }
-                serializer.attribute(CtsXmlResultReporter.ns, SCORETYPE_ATTR, summary.mType);
-                serializer.attribute(CtsXmlResultReporter.ns, UNIT_ATTR, summary.mUnit);
-                serializer.text(summary.mValue);
-                serializer.endTag(CtsXmlResultReporter.ns, SUMMARY_TAG);
-                // add details only if summary is present
-                // <Details>
-                //   <ValueArray source=”com.android.cts.dram.BandwidthTest#doRunMemcpy:98”
-                //                    message=”measure1” unit="ms" scoretype="higherBetter">
-                //     <Value>0.0</Value>
-                //     <Value>0.1</Value>
-                //   </ValueArray>
-                // </Details>
-                if (mDetails != null) {
-                    PerfResultDetail[] ds = parseDetails(mDetails);
-                    serializer.startTag(CtsXmlResultReporter.ns, DETAILS_TAG);
-                        for (PerfResultDetail d : ds) {
-                            if (d == null) {
-                                continue;
-                            }
-                            serializer.startTag(CtsXmlResultReporter.ns, VALUEARRAY_TAG);
-                            serializer.attribute(CtsXmlResultReporter.ns, SOURCE_ATTR, d.mSource);
-                            serializer.attribute(CtsXmlResultReporter.ns, MESSAGE_ATTR,
-                                    d.mMessage);
-                            serializer.attribute(CtsXmlResultReporter.ns, SCORETYPE_ATTR, d.mType);
-                            serializer.attribute(CtsXmlResultReporter.ns, UNIT_ATTR, d.mUnit);
-                            for (String v : d.mValues) {
-                                if (v == null) {
-                                    continue;
-                                }
-                                serializer.startTag(CtsXmlResultReporter.ns, VALUE_TAG);
-                                serializer.text(v);
-                                serializer.endTag(CtsXmlResultReporter.ns, VALUE_TAG);
-                            }
-                            serializer.endTag(CtsXmlResultReporter.ns, VALUEARRAY_TAG);
-                        }
-                    serializer.endTag(CtsXmlResultReporter.ns, DETAILS_TAG);
-                }
-            }
-        }
+        MetricsXmlSerializer metricsXmlSerializer = new MetricsXmlSerializer(serializer);
+        metricsXmlSerializer.serialize(mReport);
         serializer.endTag(CtsXmlResultReporter.ns, TAG);
     }
 
     /**
-     *  class containing performance result.
-     */
-    public static class PerfResultCommon {
-        public String mMessage;
-        public String mType;
-        public String mUnit;
-    }
-
-    private class PerfResultSummary extends PerfResultCommon {
-        public String mTarget;
-        public String mValue;
-    }
-
-    private class PerfResultDetail extends PerfResultCommon {
-        public String mSource;
-        public String[] mValues;
-    }
-
-    private PerfResultSummary parseSummary(String summary) {
-        String[] elems = summary.split(LOG_ELEM_SEPARATOR);
-        PerfResultSummary r = new PerfResultSummary();
-        if (elems.length < 5) {
-            Log.w(TAG, "wrong message " + summary);
-            return null;
-        }
-        r.mMessage = elems[0];
-        r.mTarget = elems[1];
-        r.mType = elems[2];
-        r.mUnit = elems[3];
-        r.mValue = elems[4];
-        return r;
-    }
-
-    private PerfResultDetail[] parseDetails(String details) {
-        String[] arrays = details.split(LOG_SEPARATOR);
-        PerfResultDetail[] rs = new PerfResultDetail[arrays.length];
-        for (int i = 0; i < arrays.length; i++) {
-            String[] elems = arrays[i].split(LOG_ELEM_SEPARATOR);
-            if (elems.length < 5) {
-                Log.w(TAG, "wrong message " + arrays[i]);
-                continue;
-            }
-            PerfResultDetail r = new PerfResultDetail();
-            r.mSource = elems[0];
-            r.mMessage = elems[1];
-            r.mType = elems[2];
-            r.mUnit = elems[3];
-            r.mValues = elems[4].split(" ");
-            rs[i] = r;
-        }
-        return rs;
-    }
-
-    /**
      * Strip out any invalid XML characters that might cause the report to be unviewable.
      * http://www.w3.org/TR/REC-xml/#dt-character
      */
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/result/TestPackageResult.java b/tools/tradefed-host/src/com/android/cts/tradefed/result/TestPackageResult.java
index f5a3d02..2229671 100644
--- a/tools/tradefed-host/src/com/android/cts/tradefed/result/TestPackageResult.java
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/result/TestPackageResult.java
@@ -15,9 +15,10 @@
  */
 package com.android.cts.tradefed.result;
 
+import com.android.compatibility.common.util.AbiUtils;
+import com.android.compatibility.common.util.MetricsStore;
+import com.android.compatibility.common.util.ReportLog;
 import com.android.cts.tradefed.testtype.CtsTest;
-import com.android.cts.tradefed.util.CtsHostStore;
-import com.android.cts.util.AbiUtils;
 import com.android.ddmlib.testrunner.TestIdentifier;
 import com.android.tradefed.log.LogUtil.CLog;
 
@@ -33,8 +34,6 @@
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
 
 /**
  * Data structure for a CTS test package result.
@@ -45,7 +44,7 @@
 
     static final String TAG = "TestPackage";
 
-    public static final String CTS_RESULT_KEY = "CTS_TEST_RESULT";
+    public static final String RESULT_KEY = "COMPATIBILITY_TEST_RESULT";
 
     private static final String DIGEST_ATTR = "digest";
     private static final String APP_PACKAGE_NAME_ATTR = "appPackageName";
@@ -54,8 +53,6 @@
     private static final String ns = CtsXmlResultReporter.ns;
     private static final String SIGNATURE_TEST_PKG = "android.tests.sigtest";
 
-    private static final Pattern mCtsLogPattern = Pattern.compile("(.*)\\+\\+\\+\\+(.*)");
-
     private String mDeviceSerial;
     private String mAppPackageName;
     private String mName;
@@ -249,23 +246,22 @@
         // Collect performance results
         for (TestIdentifier test : mTestMetrics.keySet()) {
             // device test can have performance results in test metrics
-            String perfResult = mTestMetrics.get(test).get(CTS_RESULT_KEY);
-            // host test should be checked in CtsHostStore.
-            if (perfResult == null) {
-                perfResult = CtsHostStore.removeCtsResult(mDeviceSerial, mAbi, test.toString());
-            }
+            String perfResult = mTestMetrics.get(test).get(RESULT_KEY);
+            ReportLog report = null;
             if (perfResult != null) {
-                // CTS result is passed in Summary++++Details format.
-                // Extract Summary and Details, and pass them.
-                Matcher m = mCtsLogPattern.matcher(perfResult);
-                if (m.find()) {
-                    Test result = findTest(test);
-                    result.setResultStatus(CtsTestStatus.PASS);
-                    result.setSummary(m.group(1));
-                    result.setDetails(m.group(2));
-                } else {
-                    CLog.e("CTS Result unrecognizable:" + perfResult);
+                try {
+                    report = ReportLog.parse(perfResult);
+                } catch (XmlPullParserException | IOException e) {
+                    e.printStackTrace();
                 }
+            } else {
+                // host test should be checked into MetricsStore.
+                report = MetricsStore.removeResult(mDeviceSerial, getAbi(), test.toString());
+            }
+            Test result = findTest(test);
+            if (report != null && !result.getResult().equals(CtsTestStatus.FAIL)) {
+                result.setResultStatus(CtsTestStatus.PASS);
+                result.setReportLog(report);
             }
         }
     }
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/result/TestResults.java b/tools/tradefed-host/src/com/android/cts/tradefed/result/TestResults.java
index 9f67f2d..a3f1591 100644
--- a/tools/tradefed-host/src/com/android/cts/tradefed/result/TestResults.java
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/result/TestResults.java
@@ -15,8 +15,8 @@
  */
 package com.android.cts.tradefed.result;
 
+import com.android.compatibility.common.util.AbiUtils;
 import com.android.cts.tradefed.build.CtsBuildProvider;
-import com.android.cts.util.AbiUtils;
 import com.android.tradefed.log.LogUtil.CLog;
 
 import org.kxml2.io.KXmlSerializer;
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/CtsInstrumentationApkTest.java b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/CtsInstrumentationApkTest.java
index 66d1135f..1fa4e7b 100644
--- a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/CtsInstrumentationApkTest.java
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/CtsInstrumentationApkTest.java
@@ -15,8 +15,8 @@
  */
 package com.android.cts.tradefed.testtype;
 
+import com.android.compatibility.common.util.AbiUtils;
 import com.android.cts.tradefed.build.CtsBuildHelper;
-import com.android.cts.util.AbiUtils;
 import com.android.ddmlib.Log;
 import com.android.tradefed.build.IBuildInfo;
 import com.android.tradefed.device.DeviceNotAvailableException;
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/CtsTest.java b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/CtsTest.java
index d74cce5..dc0d040 100644
--- a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/CtsTest.java
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/CtsTest.java
@@ -16,11 +16,11 @@
 
 package com.android.cts.tradefed.testtype;
 
+import com.android.compatibility.common.util.AbiUtils;
 import com.android.cts.tradefed.build.CtsBuildHelper;
 import com.android.cts.tradefed.device.DeviceInfoCollector;
 import com.android.cts.tradefed.result.CtsTestStatus;
 import com.android.cts.tradefed.result.PlanCreator;
-import com.android.cts.util.AbiUtils;
 import com.android.ddmlib.Log;
 import com.android.ddmlib.Log.LogLevel;
 import com.android.ddmlib.testrunner.TestIdentifier;
@@ -1077,7 +1077,7 @@
      * Exposed for unit testing
      */
     ITestPlan createPlan(String planName) {
-        return new TestPlan(planName, AbiUtils.getAbisSupportedByCts());
+        return new TestPlan(planName, AbiUtils.getAbisSupportedByCompatibility());
     }
 
     /**
@@ -1091,7 +1091,7 @@
         String bitness = (mForceAbi == null) ? "" : mForceAbi;
         Set<String> abis = new HashSet<>();
         for (String abi : AbiFormatter.getSupportedAbis(mDevice, bitness)) {
-            if (AbiUtils.isAbiSupportedByCts(abi)) {
+            if (AbiUtils.isAbiSupportedByCompatibility(abi)) {
                 abis.add(abi);
             }
         }
@@ -1106,7 +1106,7 @@
      */
     ITestPlan createPlan(PlanCreator planCreator)
             throws ConfigurationException {
-        return planCreator.createDerivedPlan(mCtsBuild, AbiUtils.getAbisSupportedByCts());
+        return planCreator.createDerivedPlan(mCtsBuild, AbiUtils.getAbisSupportedByCompatibility());
     }
 
     /**
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/DeqpTestRunner.java b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/DeqpTestRunner.java
index 43aaf98..edcc516 100644
--- a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/DeqpTestRunner.java
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/DeqpTestRunner.java
@@ -1,7 +1,7 @@
 package com.android.cts.tradefed.testtype;
 
+import com.android.compatibility.common.util.AbiUtils;
 import com.android.cts.tradefed.build.CtsBuildHelper;
-import com.android.cts.util.AbiUtils;
 import com.android.ddmlib.AdbCommandRejectedException;
 import com.android.ddmlib.IShellOutputReceiver;
 import com.android.ddmlib.MultiLineReceiver;
@@ -161,7 +161,7 @@
     }
 
     private static final class CapabilityQueryFailureException extends Exception {
-    };
+    }
 
     /**
      * Test configuration of dEPQ test instance execution.
@@ -241,13 +241,13 @@
         private boolean mGotTestResult;
         private String mCurrentTestLog;
 
-        private class PendingResult
-        {
+        private class PendingResult {
             boolean allInstancesPassed;
             Map<BatchRunConfiguration, String> testLogs;
             Map<BatchRunConfiguration, String> errorMessages;
             Set<BatchRunConfiguration> remainingConfigs;
-        };
+        }
+
         private final Map<TestIdentifier, PendingResult> mPendingResults = new HashMap<>();
 
         public void setSink(ITestInvocationListener sink) {
@@ -726,7 +726,7 @@
      */
     public static interface ISleepProvider {
         public void sleep(int milliseconds);
-    };
+    }
 
     private static class SleepProvider implements ISleepProvider {
         public void sleep(int milliseconds) {
@@ -735,7 +735,7 @@
             } catch (InterruptedException ex) {
             }
         }
-    };
+    }
 
     /**
      * Interface for failure recovery.
@@ -993,7 +993,7 @@
         private void rebootDevice() throws DeviceNotAvailableException {
             mDevice.reboot();
         }
-    };
+    }
 
     /**
      * Parse map of instance arguments to map of BatchRunConfigurations
@@ -1315,12 +1315,13 @@
         public AdbComLinkOpenError(String description, Throwable inner) {
             super(description, inner);
         }
-    };
+    }
+
     private static final class AdbComLinkKilledError extends Exception {
         public AdbComLinkKilledError(String description, Throwable inner) {
             super(description, inner);
         }
-    };
+    }
 
     /**
      * Executes a given command in adb shell
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/ITestPackageRepo.java b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/ITestPackageRepo.java
index 234f437..893758f 100644
--- a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/ITestPackageRepo.java
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/ITestPackageRepo.java
@@ -16,7 +16,7 @@
 
 package com.android.cts.tradefed.testtype;
 
-import com.android.cts.util.AbiUtils;
+import com.android.compatibility.common.util.AbiUtils;
 
 import java.util.List;
 import java.util.Map;
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/TestPackageDef.java b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/TestPackageDef.java
index 12c3ddd..cd2a69f 100644
--- a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/TestPackageDef.java
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/TestPackageDef.java
@@ -16,7 +16,7 @@
 
 package com.android.cts.tradefed.testtype;
 
-import com.android.cts.util.AbiUtils;
+import com.android.compatibility.common.util.AbiUtils;
 import com.android.ddmlib.Log.LogLevel;
 import com.android.ddmlib.testrunner.TestIdentifier;
 import com.android.tradefed.log.LogUtil.CLog;
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/TestPackageRepo.java b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/TestPackageRepo.java
index 7e16170..9857105b 100644
--- a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/TestPackageRepo.java
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/TestPackageRepo.java
@@ -15,7 +15,7 @@
  */
 package com.android.cts.tradefed.testtype;
 
-import com.android.cts.util.AbiUtils;
+import com.android.compatibility.common.util.AbiUtils;
 import com.android.ddmlib.Log;
 import com.android.tradefed.config.ConfigurationException;
 import com.android.tradefed.config.ConfigurationFactory;
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/TestPackageXmlParser.java b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/TestPackageXmlParser.java
index 649dd9e..acd977e 100644
--- a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/TestPackageXmlParser.java
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/TestPackageXmlParser.java
@@ -15,7 +15,7 @@
  */
 package com.android.cts.tradefed.testtype;
 
-import com.android.cts.util.AbiUtils;
+import com.android.compatibility.common.util.AbiUtils;
 import com.android.ddmlib.Log;
 import com.android.ddmlib.testrunner.TestIdentifier;
 import com.android.tradefed.util.xml.AbstractXmlParser;
@@ -87,7 +87,7 @@
                 final String runTimeArgs = attributes.getValue("runtimeArgs");
                 final String testType = getTestType(attributes);
 
-                for (String abiName : AbiUtils.getAbisSupportedByCts()) {
+                for (String abiName : AbiUtils.getAbisSupportedByCompatibility()) {
                     Abi abi = new Abi(abiName, AbiUtils.getBitness(abiName));
                     TestPackageDef packageDef = new TestPackageDef();
                     packageDef.setAppPackageName(appPackageName);
@@ -154,7 +154,7 @@
                         Set<String> abis = new HashSet<String>();
                         if (abiList == null) {
                             // If no specification, add all supported abis
-                            abis.addAll(AbiUtils.getAbisSupportedByCts());
+                            abis.addAll(AbiUtils.getAbisSupportedByCompatibility());
                         } else {
                             for (String abi : abiList.split(",")) {
                                 // Else only add the abi which are supported
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/TestPlan.java b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/TestPlan.java
index 2419784..6b02db9 100644
--- a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/TestPlan.java
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/TestPlan.java
@@ -16,7 +16,7 @@
 
 package com.android.cts.tradefed.testtype;
 
-import com.android.cts.util.AbiUtils;
+import com.android.compatibility.common.util.AbiUtils;
 import com.android.ddmlib.testrunner.TestIdentifier;
 import com.android.tradefed.util.ArrayUtil;
 import com.android.tradefed.util.xml.AbstractXmlParser;
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/WrappedGTest.java b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/WrappedGTest.java
index e3ff8257..4f40c89 100644
--- a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/WrappedGTest.java
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/WrappedGTest.java
@@ -16,8 +16,8 @@
 
 package com.android.cts.tradefed.testtype;
 
+import com.android.compatibility.common.util.AbiUtils;
 import com.android.cts.tradefed.build.CtsBuildHelper;
-import com.android.cts.util.AbiUtils;
 import com.android.ddmlib.testrunner.ITestRunListener;
 import com.android.tradefed.build.IBuildInfo;
 import com.android.tradefed.device.DeviceNotAvailableException;
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/util/CtsHostStore.java b/tools/tradefed-host/src/com/android/cts/tradefed/util/CtsHostStore.java
deleted file mode 100644
index 288b48c..0000000
--- a/tools/tradefed-host/src/com/android/cts/tradefed/util/CtsHostStore.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.cts.tradefed.util;
-
-import java.util.concurrent.ConcurrentHashMap;
-
-/**
- * Utility class for storing Cts Results.
- * This is necessary for host tests where test metrics cannot be passed.
- */
-public class CtsHostStore {
-
-    // needs concurrent version as there can be multiple client accessing this.
-    // But there is no additional protection for the same key as that should not happen.
-    private static final ConcurrentHashMap<String, String> mMap =
-            new ConcurrentHashMap<String, String>();
-
-    /**
-     * Stores CTS result. Existing result with the same key will be replaced.
-     * Note that key is generated in the form of device_serial#class#method name.
-     * So there should be no concurrent test for the same (serial, class, method).
-     * @param deviceSerial
-     * @param abi
-     * @param classMethodName
-     * @param result CTS result string
-     */
-    public static void storeCtsResult(String deviceSerial, String abi, String classMethodName, String result) {
-        mMap.put(generateTestKey(deviceSerial, abi, classMethodName), result);
-    }
-
-    /**
-     * retrieves a CTS result for the given condition and remove it from the internal
-     * storage. If there is no result for the given condition, it will return null.
-     */
-    public static String removeCtsResult(String deviceSerial, String abi, String classMethodName) {
-        return mMap.remove(generateTestKey(deviceSerial, abi, classMethodName));
-    }
-
-    /**
-     * @return test key in the form of device_serial#abi#class_name#method_name
-     */
-    private static String generateTestKey(String deviceSerial, String abi, String classMethodName) {
-        return String.format("%s#%s#%s", deviceSerial, abi, classMethodName);
-
-    }
-}
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/util/HostReportLog.java b/tools/tradefed-host/src/com/android/cts/tradefed/util/HostReportLog.java
index 645dbb9..f72b097 100644
--- a/tools/tradefed-host/src/com/android/cts/tradefed/util/HostReportLog.java
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/util/HostReportLog.java
@@ -16,17 +16,17 @@
 
 package com.android.cts.tradefed.util;
 
+import com.android.compatibility.common.util.MetricsReportLog;
 import com.android.cts.util.ReportLog;
 
 /**
  * ReportLog for host tests
  * Note that setTestInfo should be set before throwing report
+ *
+ * This class is deprecated, use {@link MetricsReportLog} instead.
  */
+@Deprecated
 public class HostReportLog extends ReportLog {
-    private final String mDeviceSerial;
-    private final String mAbiName;
-    private final String mClassMethodName;
-
     /**
      * @param deviceSerial serial number of the device
      * @param abiName the name of the ABI on which the test was run
@@ -34,12 +34,10 @@
      *        Note that ReportLog.getClassMethodNames() provide this.
      */
     public HostReportLog(String deviceSerial, String abiName, String classMethodName) {
-        mDeviceSerial = deviceSerial;
-        mAbiName = abiName;
-        mClassMethodName = classMethodName;
+        super(new MetricsReportLog(deviceSerial, abiName, classMethodName));
     }
 
     public void deliverReportToHost() {
-        CtsHostStore.storeCtsResult(mDeviceSerial, mAbiName, mClassMethodName, generateReport());
+        ((MetricsReportLog) mReportLog).submit();
     }
-}
+}
\ No newline at end of file
diff --git a/tools/tradefed-host/tests/src/com/android/cts/tradefed/result/CtsXmlResultReporterTest.java b/tools/tradefed-host/tests/src/com/android/cts/tradefed/result/CtsXmlResultReporterTest.java
index 51a6153..1441f8f 100644
--- a/tools/tradefed-host/tests/src/com/android/cts/tradefed/result/CtsXmlResultReporterTest.java
+++ b/tools/tradefed-host/tests/src/com/android/cts/tradefed/result/CtsXmlResultReporterTest.java
@@ -17,11 +17,10 @@
 
 import static com.android.cts.tradefed.result.CtsXmlResultReporter.CTS_RESULT_FILE_VERSION;
 
+import com.android.compatibility.common.util.AbiUtils;
 import com.android.cts.tradefed.UnitTests;
-import com.android.cts.util.AbiUtils;
 import com.android.ddmlib.testrunner.TestIdentifier;
 import com.android.tradefed.build.IFolderBuildInfo;
-import com.android.tradefed.log.LogUtil.CLog;
 import com.android.tradefed.result.LogDataType;
 import com.android.tradefed.result.LogFile;
 import com.android.tradefed.result.TestSummary;
@@ -36,8 +35,8 @@
 import java.io.File;
 import java.io.IOException;
 import java.io.OutputStream;
-import java.util.Arrays;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
diff --git a/tools/tradefed-host/tests/src/com/android/cts/tradefed/testtype/CtsTestTest.java b/tools/tradefed-host/tests/src/com/android/cts/tradefed/testtype/CtsTestTest.java
index 98caad1..19204b7 100644
--- a/tools/tradefed-host/tests/src/com/android/cts/tradefed/testtype/CtsTestTest.java
+++ b/tools/tradefed-host/tests/src/com/android/cts/tradefed/testtype/CtsTestTest.java
@@ -15,10 +15,10 @@
  */
 package com.android.cts.tradefed.testtype;
 
+import com.android.compatibility.common.util.AbiUtils;
 import com.android.cts.tradefed.UnitTests;
 import com.android.cts.tradefed.build.StubCtsBuildHelper;
 import com.android.cts.tradefed.result.PlanCreator;
-import com.android.cts.util.AbiUtils;
 import com.android.ddmlib.testrunner.TestIdentifier;
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.device.ITestDevice;
diff --git a/tools/tradefed-host/tests/src/com/android/cts/tradefed/testtype/DeqpTestRunnerTest.java b/tools/tradefed-host/tests/src/com/android/cts/tradefed/testtype/DeqpTestRunnerTest.java
index 7ec09c9..2c8a816 100644
--- a/tools/tradefed-host/tests/src/com/android/cts/tradefed/testtype/DeqpTestRunnerTest.java
+++ b/tools/tradefed-host/tests/src/com/android/cts/tradefed/testtype/DeqpTestRunnerTest.java
@@ -15,18 +15,16 @@
  */
 package com.android.cts.tradefed.testtype;
 
-import com.android.cts.tradefed.build.StubCtsBuildHelper;
+import com.android.compatibility.common.util.AbiUtils;
 import com.android.cts.tradefed.UnitTests;
-import com.android.cts.util.AbiUtils;
+import com.android.cts.tradefed.build.StubCtsBuildHelper;
 import com.android.ddmlib.IDevice;
 import com.android.ddmlib.IShellOutputReceiver;
 import com.android.ddmlib.ShellCommandUnresponsiveException;
-import com.android.ddmlib.testrunner.ITestRunListener;
 import com.android.ddmlib.testrunner.TestIdentifier;
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.result.ITestInvocationListener;
-import com.android.tradefed.testtype.IAbi;
 import com.android.tradefed.util.IRunUtil;
 import com.android.tradefed.util.RunInterruptedException;
 
@@ -38,7 +36,6 @@
 
 import java.io.File;
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.List;
diff --git a/tools/tradefed-host/tests/src/com/android/cts/tradefed/testtype/TestPackageXmlParserTest.java b/tools/tradefed-host/tests/src/com/android/cts/tradefed/testtype/TestPackageXmlParserTest.java
index bd48c51..8655885 100644
--- a/tools/tradefed-host/tests/src/com/android/cts/tradefed/testtype/TestPackageXmlParserTest.java
+++ b/tools/tradefed-host/tests/src/com/android/cts/tradefed/testtype/TestPackageXmlParserTest.java
@@ -16,7 +16,7 @@
 
 package com.android.cts.tradefed.testtype;
 
-import com.android.cts.util.AbiUtils;
+import com.android.compatibility.common.util.AbiUtils;
 import com.android.ddmlib.testrunner.TestIdentifier;
 import com.android.tradefed.util.xml.AbstractXmlParser.ParseException;
 
@@ -100,7 +100,7 @@
             assertEquals("com.example", def.getAppNameSpace());
             assertEquals("android.example", def.getAppPackageName());
             assertEquals("android.test.InstrumentationTestRunner", def.getRunner());
-            assertTrue(AbiUtils.isAbiSupportedByCts(def.getAbi().getName()));
+            assertTrue(AbiUtils.isAbiSupportedByCompatibility(def.getAbi().getName()));
         }
     }
 
diff --git a/tools/tradefed-host/tests/src/com/android/cts/tradefed/testtype/TestPlanTest.java b/tools/tradefed-host/tests/src/com/android/cts/tradefed/testtype/TestPlanTest.java
index 5b28539..be260ea 100644
--- a/tools/tradefed-host/tests/src/com/android/cts/tradefed/testtype/TestPlanTest.java
+++ b/tools/tradefed-host/tests/src/com/android/cts/tradefed/testtype/TestPlanTest.java
@@ -16,7 +16,7 @@
 
 package com.android.cts.tradefed.testtype;
 
-import com.android.cts.util.AbiUtils;
+import com.android.compatibility.common.util.AbiUtils;
 import com.android.ddmlib.testrunner.TestIdentifier;
 import com.android.tradefed.util.xml.AbstractXmlParser.ParseException;
 
@@ -75,7 +75,7 @@
     @Override
     protected void setUp() throws Exception {
         super.setUp();
-        mPlan = new TestPlan("plan", AbiUtils.getAbisSupportedByCts());
+        mPlan = new TestPlan("plan", AbiUtils.getAbisSupportedByCompatibility());
     }
 
     /**
@@ -91,7 +91,7 @@
      * @param plan
      */
     private void assertTestData(TestPlan plan) {
-        Set<String> abis = AbiUtils.getAbisSupportedByCts();
+        Set<String> abis = AbiUtils.getAbisSupportedByCompatibility();
         assertEquals(2 * abis.size(), plan.getTestIds().size());
         List<String> sortedAbis = new ArrayList<String>(abis);
         Collections.sort(sortedAbis);
@@ -112,7 +112,7 @@
      */
     public void testParse_exclude() throws ParseException  {
         mPlan.parse(getStringAsStream(TEST_EXCLUDED_DATA));
-        Set<String> abis = AbiUtils.getAbisSupportedByCts();
+        Set<String> abis = AbiUtils.getAbisSupportedByCompatibility();
         assertEquals(abis.size(), mPlan.getTestIds().size());
 
         for (String abi : abis) {
@@ -136,7 +136,7 @@
      * @param plan
      */
     private void assertMultiExcluded(TestPlan plan) {
-        Set<String> abis = AbiUtils.getAbisSupportedByCts();
+        Set<String> abis = AbiUtils.getAbisSupportedByCompatibility();
         assertEquals(abis.size(), plan.getTestIds().size());
 
         for (String abi : abis) {
@@ -154,7 +154,7 @@
      */
     public void testParse_classExclude() throws ParseException  {
         mPlan.parse(getStringAsStream(TEST_CLASS_EXCLUDED_DATA));
-        Set<String> abis = AbiUtils.getAbisSupportedByCts();
+        Set<String> abis = AbiUtils.getAbisSupportedByCompatibility();
         assertEquals(abis.size(), mPlan.getTestIds().size());
 
         for (String abi : abis) {
@@ -179,14 +179,14 @@
      * @throws IOException
      */
     public void testSerialize_packages() throws ParseException, IOException  {
-        Set<String> abis = AbiUtils.getAbisSupportedByCts();
+        Set<String> abis = AbiUtils.getAbisSupportedByCompatibility();
         for (String abi : abis) {
             mPlan.addPackage(AbiUtils.createId(abi, TEST_NAME1));
             mPlan.addPackage(AbiUtils.createId(abi, TEST_NAME2));
         }
         ByteArrayOutputStream outStream = new ByteArrayOutputStream();
         mPlan.serialize(outStream);
-        TestPlan parsedPlan = new TestPlan("parsed", AbiUtils.getAbisSupportedByCts());
+        TestPlan parsedPlan = new TestPlan("parsed", AbiUtils.getAbisSupportedByCompatibility());
         parsedPlan.parse(getStringAsStream(outStream.toString()));
         // parsedPlan should contain same contents as TEST_DATA
         assertTestData(parsedPlan);
@@ -196,7 +196,7 @@
      * Test serializing and deserializing plan with multiple excluded tests
      */
     public void testSerialize_multiExclude() throws ParseException, IOException  {
-        Set<String> abis = AbiUtils.getAbisSupportedByCts();
+        Set<String> abis = AbiUtils.getAbisSupportedByCompatibility();
 
         for (String abi : abis) {
             String test1Id = AbiUtils.createId(abi, TEST_NAME1);
@@ -208,7 +208,7 @@
         }
         ByteArrayOutputStream outStream = new ByteArrayOutputStream();
         mPlan.serialize(outStream);
-        TestPlan parsedPlan = new TestPlan("parsed", AbiUtils.getAbisSupportedByCts());
+        TestPlan parsedPlan = new TestPlan("parsed", AbiUtils.getAbisSupportedByCompatibility());
         parsedPlan.parse(getStringAsStream(outStream.toString()));
         // parsedPlan should contain same contents as TEST_DATA
         assertMultiExcluded(parsedPlan);
diff --git a/tools/utils/Android.mk b/tools/utils/Android.mk
index 0ba5cf4..d26abb1 100644
--- a/tools/utils/Android.mk
+++ b/tools/utils/Android.mk
@@ -25,6 +25,6 @@
 LOCAL_CLASSPATH := $(HOST_JDK_TOOLS_JAR)
 
 LOCAL_JAVA_LIBRARIES := junit
-LOCAL_STATIC_JAVA_LIBRARIES := ctsabiutilslib vogarexpectlib
+LOCAL_STATIC_JAVA_LIBRARIES := compatibility-host-util vogarexpectlib
 
 include $(BUILD_HOST_JAVA_LIBRARY)
diff --git a/tools/utils/CollectAllTests.java b/tools/utils/CollectAllTests.java
index 83c451e..e1ff982 100644
--- a/tools/utils/CollectAllTests.java
+++ b/tools/utils/CollectAllTests.java
@@ -13,7 +13,8 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import com.android.cts.util.AbiUtils;
+
+import com.android.compatibility.common.util.AbiUtils;
 
 import org.junit.runner.RunWith;
 import org.w3c.dom.Document;
diff --git a/tools/utils/VogarUtils.java b/tools/utils/VogarUtils.java
index 8e77e7c..77c62da 100644
--- a/tools/utils/VogarUtils.java
+++ b/tools/utils/VogarUtils.java
@@ -14,13 +14,13 @@
  * limitations under the License.
  */
 
-import com.android.cts.util.AbiUtils;
-
 import vogar.Expectation;
 import vogar.ExpectationStore;
 import vogar.ModeId;
 import vogar.Result;
 
+import com.android.compatibility.common.util.AbiUtils;
+
 import java.io.File;
 import java.io.FilenameFilter;
 import java.io.IOException;
diff --git a/tools/vm-tests-tf/Android.mk b/tools/vm-tests-tf/Android.mk
index b1cbe37..ded63bf 100644
--- a/tools/vm-tests-tf/Android.mk
+++ b/tools/vm-tests-tf/Android.mk
@@ -69,7 +69,6 @@
 vmteststf_dep_jars += $(addprefix $(HOST_OUT_JAVA_LIBRARIES)/, jack.jar)
 vmteststf_dep_jars += $(private_jill_jarjar_asm)
 
-$(vmteststf_jar): PRIVATE_JACK_VM_ARGS := $(LOCAL_JACK_VM_ARGS)
 $(vmteststf_jar): PRIVATE_JACK_EXTRA_ARGS := $(LOCAL_JACK_EXTRA_ARGS)
 
 ifdef LOCAL_JACK_ENABLED
@@ -113,7 +112,7 @@
 		$(addprefix -C $(PRIVATE_INTERMEDIATES_CLASSES) , dot/junit/DxUtil.class dot/junit/DxAbstractMain.class)
 	$(hide) $(JILL) --output $(PRIVATE_INTERMEDIATES_DEXCORE_JAR).jack $(PRIVATE_INTERMEDIATES_DEXCORE_JAR)-class.jar
 	$(hide) mkdir -p $(PRIVATE_INTERMEDIATES_DEXCORE_JAR).tmp
-	$(hide) $(call call-jack,$(PRIVATE_JACK_VM_ARGS),$(PRIVATE_JACK_EXTRA_ARGS)) --output-dex $(PRIVATE_INTERMEDIATES_DEXCORE_JAR).tmp \
+	$(hide) $(call call-jack,$(PRIVATE_JACK_EXTRA_ARGS)) --output-dex $(PRIVATE_INTERMEDIATES_DEXCORE_JAR).tmp \
 		$(if $(NO_OPTIMIZE_DX), -D jack.dex.optimize "false") --import $(PRIVATE_INTERMEDIATES_DEXCORE_JAR).jack && rm -f $(PRIVATE_INTERMEDIATES_DEXCORE_JAR).jack
 	$(hide) cd $(PRIVATE_INTERMEDIATES_DEXCORE_JAR).tmp && zip -q -r $(abspath $(PRIVATE_INTERMEDIATES_DEXCORE_JAR)) .
 	$(hide) cd $(PRIVATE_INTERMEDIATES_HOSTJUNIT_FILES)/classes && zip -q -r ../../android.core.vm-tests-tf.jar .
diff --git a/tools/vm-tests-tf/src/util/build/JackBuildStep.java b/tools/vm-tests-tf/src/util/build/JackBuildStep.java
index 1e1ede6..dabef17 100644
--- a/tools/vm-tests-tf/src/util/build/JackBuildStep.java
+++ b/tools/vm-tests-tf/src/util/build/JackBuildStep.java
@@ -16,10 +16,11 @@
 
 package util.build;
 
+import com.android.jack.CLILogConfiguration;
+import com.android.jack.CLILogConfiguration.LogConfigurationException;
 import com.android.jack.Jack;
 import com.android.jack.Main;
 import com.android.jack.Options;
-
 import java.io.File;
 import java.util.ArrayList;
 import java.util.HashSet;
@@ -28,6 +29,14 @@
 
 public class JackBuildStep extends SourceBuildStep {
 
+    static {
+        try {
+              CLILogConfiguration.setupLogs();
+            } catch (LogConfigurationException e) {
+              throw new Error("Failed to setup logs", e);
+            }
+    }
+
     private final String destPath;
     private final String classPath;
     private final Set<String> sourceFiles = new HashSet<String>();