am fe91f772: am 28a108a9: Merge "CTSVerifier tests for audio (re)routing callbacks." into mnc-dev

* commit 'fe91f7724a39b640108cf1c653d8c6488d1640d6':
  CTSVerifier tests for audio (re)routing callbacks.
diff --git a/apps/CtsVerifier/Android.mk b/apps/CtsVerifier/Android.mk
index 7f43962..a7af08e 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 \
diff --git a/common/host-side/scripts/compatibility-tradefed_v2 b/common/device-side/device-info/Android.mk
old mode 100755
new mode 100644
similarity index 65%
rename from common/host-side/scripts/compatibility-tradefed_v2
rename to common/device-side/device-info/Android.mk
index f64e273..2f7168d
--- a/common/host-side/scripts/compatibility-tradefed_v2
+++ b/common/device-side/device-info/Android.mk
@@ -1,5 +1,3 @@
-#!/bin/bash
-#
 # Copyright (C) 2014 The Android Open Source Project
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -13,4 +11,19 @@
 # WITHOUT 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_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE := compatibility-device-info
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/common/device-side/util/tests/src/com/android/compatibility/common/util/DeviceUtilTest.java b/common/device-side/device-info/src/com/android/compatibility/common/deviceinfo/DeviceInfo.java
similarity index 73%
copy from common/device-side/util/tests/src/com/android/compatibility/common/util/DeviceUtilTest.java
copy to common/device-side/device-info/src/com/android/compatibility/common/deviceinfo/DeviceInfo.java
index a7e81d7..fc559fd 100644
--- a/common/device-side/util/tests/src/com/android/compatibility/common/util/DeviceUtilTest.java
+++ b/common/device-side/device-info/src/com/android/compatibility/common/deviceinfo/DeviceInfo.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,11 +13,11 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package com.android.compatibility.common.deviceinfo;
 
-package com.android.compatibility.common.util;
-
-import junit.framework.TestCase;
-
-public class DeviceUtilTest extends TestCase {
+/**
+ * TODO: Insert description here. (generated by stuartscott)
+ */
+public class DeviceInfo {
 
 }
diff --git a/common/host-side/scripts/compatibility-tradefed_v2 b/common/device-side/device-info/tests/Android.mk
old mode 100755
new mode 100644
similarity index 61%
copy from common/host-side/scripts/compatibility-tradefed_v2
copy to common/device-side/device-info/tests/Android.mk
index f64e273..6cf5ab3
--- a/common/host-side/scripts/compatibility-tradefed_v2
+++ b/common/device-side/device-info/tests/Android.mk
@@ -1,6 +1,4 @@
-#!/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.
@@ -13,4 +11,17 @@
 # WITHOUT 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_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_STATIC_JAVA_LIBRARIES := compatibility-device-info
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE := compatibility-device-info-tests
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/common/device-side/util/tests/src/com/android/compatibility/common/util/DeviceUtilTest.java b/common/device-side/device-info/tests/src/com/android/compatibility/common/deviceinfo/DeviceInfoTest.java
similarity index 70%
copy from common/device-side/util/tests/src/com/android/compatibility/common/util/DeviceUtilTest.java
copy to common/device-side/device-info/tests/src/com/android/compatibility/common/deviceinfo/DeviceInfoTest.java
index a7e81d7..fcaacc8 100644
--- a/common/device-side/util/tests/src/com/android/compatibility/common/util/DeviceUtilTest.java
+++ b/common/device-side/device-info/tests/src/com/android/compatibility/common/deviceinfo/DeviceInfoTest.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,13 +11,15 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT 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;
+package com.android.compatibility.common.deviceinfo;
 
 import junit.framework.TestCase;
 
-public class DeviceUtilTest extends TestCase {
+/**
+ * Tests for {@link DeviceInfo}.
+ */
+public class DeviceInfoTest extends TestCase {
 
 }
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..85dd00c
--- /dev/null
+++ b/common/device-side/test-app/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<?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">
+
+    <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.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..aba83ef 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
@@ -30,13 +30,19 @@
  */
 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);
+        String encoded = toEncodedString();
+        if (encoded != null) {
+            Bundle output = new Bundle();
+            output.putString(RESULT, encoded);
+            instrumentation.sendStatus(INST_STATUS_IN_PROGRESS, output);
+        } else {
+            instrumentation.sendStatus(INST_STATUS_ERROR, null);
+        }
     }
 }
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/common/device-side/util/tests/Android.mk b/common/device-side/util/tests/Android.mk
new file mode 100644
index 0000000..475dd39
--- /dev/null
+++ b/common/device-side/util/tests/Android.mk
@@ -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.
+
+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 compatibility-device-util
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE := compatibility-device-util-tests
+
+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..462e2b7
--- /dev/null
+++ b/common/device-side/util/tests/src/com/android/compatibility/common/util/DeviceReportTest.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 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.fromEncodedString(metrics);
+        assertNotNull("Metrics could not be decoded", result);
+        // Tests for the contents of DeviceReportLog are in ReportLogTest
+    }
+}
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/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..d4f68bb 100644
--- a/common/host-side/tradefed/Android.mk
+++ b/common/host-side/tradefed/Android.mk
@@ -12,40 +12,20 @@
 # 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_JAVA_RESOURCE_DIRS := res
 
-LOCAL_MODULE := compatibility-tradefed_v2
+LOCAL_MODULE := compatibility-tradefed
 
 LOCAL_MODULE_TAGS := optional
 
-LOCAL_JAVA_LIBRARIES := tradefed-prebuilt hosttestlib compatibility-common-util-hostsidelib_v2
+LOCAL_JAVA_LIBRARIES := tradefed-prebuilt hosttestlib compatibility-common-util-hostsidelib
 
 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-makefiles-under,$(LOCAL_PATH))
\ 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..a3af5aa
--- /dev/null
+++ b/common/host-side/tradefed/res/config/common-compatibility-config.xml
@@ -0,0 +1,25 @@
+<?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.tradefed.result.TextResultReporter" />
+    <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/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/BuildHelper.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/build/BuildHelper.java
new file mode 100644
index 0000000..8328614
--- /dev/null
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/build/BuildHelper.java
@@ -0,0 +1,155 @@
+/*
+ * 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 java.io.File;
+import java.io.FileNotFoundException;
+
+/**
+ * Helper class for retrieving files from the Compatibility installation.
+ */
+public class BuildHelper {
+
+    /** The root location of the extracted Compatibility package */
+    private final File mRootDir;
+    private final File mDir;
+    private String mBuildId;
+    private String mSuiteName;
+    private String mSuiteFullName;
+    private String mSuiteVersion;
+
+    /**
+     * Creates a {@link BuildHelper}.
+     * @param buildId
+     * @param suiteName
+     * @param suiteFullName
+     * @param suiteVersion
+     * @param rootDir the parent folder that contains the compatibility installation.
+     * @throws IllegalArgumentException if provided directory does not contain a valid Compatibility
+     * installation.
+     */
+    public BuildHelper(String buildId, String suiteName, String suiteFullName, String suiteVersion,
+            File rootDir) {
+        mBuildId = buildId;
+        mSuiteName = suiteName;
+        mSuiteFullName = suiteFullName;
+        mSuiteVersion = suiteVersion;
+        mRootDir = rootDir;
+        mDir = new File(mRootDir, String.format("android-%s", mSuiteName.toLowerCase()));
+        try {
+            validateStructure();
+        } catch (FileNotFoundException e) {
+            throw new IllegalArgumentException("Invalid build provided.", e);
+        }
+    }
+
+    /**
+     * Creates a {@link BuildHelper}.
+     * @param build
+     * @throws IllegalArgumentException if provided directory does not contain a valid Compatibility
+     * installation.
+     */
+    public BuildHelper(CompatibilityBuildInfo build) {
+        this(build.getBuildId(), build.getSuiteName(), build.getSuiteFullName(),
+                build.getSuiteVersion(), build.getRootDir());
+    }
+
+    /**
+     * @return a {@link File} representing the parent folder of the Compatibility installation
+     */
+    public File getRootDir() {
+        return mRootDir;
+    }
+
+    /**
+     * @return a {@link File} representing the "android-<suite>" folder of the Compatibility
+     * installation
+     */
+    public File getDir() {
+        return mDir;
+    }
+
+    private File getRepositoryDir() {
+        return new File(getDir(), "repository");
+    }
+
+    /**
+     * @return a {@link File} representing the results directory.
+     */
+    public File getResultsDir() {
+        return new File(getRepositoryDir(), "results");
+    }
+
+    /**
+     * @return a {@link File} representing the directory to store result logs.
+     */
+    public File getLogsDir() {
+        return new File(getRepositoryDir(), "logs");
+    }
+
+    /**
+     * @return a {@link File} representing the test modules directory
+     */
+    public File getTestsDir() {
+        return new File(getRepositoryDir(), "testcases");
+    }
+
+    /**
+     * @return a {@link File} representing the test plan directory
+     */
+    public File getPlansDir() {
+        return new File(getRepositoryDir(), "plans");
+    }
+
+    /**
+     * Check the validity of the Compatibility build file system structure.
+     * @throws FileNotFoundException if any major directories are missing
+     */
+    public void validateStructure() throws FileNotFoundException {
+        if (!getDir().exists()) {
+            throw new FileNotFoundException(String.format(
+                    "Compatibility install folder %s does not exist",
+                    getDir().getAbsolutePath()));
+        }
+        if (!getTestsDir().exists()) {
+            throw new FileNotFoundException(String.format(
+                    "Compatibility tests folder %s does not exist",
+                    getTestsDir().getAbsolutePath()));
+        }
+        if (!getPlansDir().exists()) {
+            throw new FileNotFoundException(String.format(
+                    "Compatibility plans folder %s does not exist",
+                    getPlansDir().getAbsolutePath()));
+        }
+    }
+
+    public String getBuildId() {
+        return mBuildId;
+    }
+
+    public String getSuiteName() {
+        return mSuiteName;
+    }
+
+    public String getSuiteFullName() {
+        return mSuiteFullName;
+    }
+
+    public String getSuiteVersion() {
+        return mSuiteVersion;
+    }
+
+}
\ No newline at end of file
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/build/CompatibilityBuildInfo.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/build/CompatibilityBuildInfo.java
new file mode 100644
index 0000000..dc14714
--- /dev/null
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/build/CompatibilityBuildInfo.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.common.tradefed.build;
+
+import com.android.tradefed.build.FolderBuildInfo;
+
+/**
+ * A simple {@link FolderBuildInfo} that uses a pre-existing Compatibility install.
+ */
+public class CompatibilityBuildInfo extends FolderBuildInfo {
+
+    private final String mSuiteName;
+    private final String mSuiteFullName;
+    private final String mSuiteVersion;
+
+    /**
+     * @param buildId
+     * @param suiteName
+     * @param suiteFullName
+     * @param suiteVersion
+     */
+    public CompatibilityBuildInfo(String buildId, String suiteName, String suiteFullName,
+            String suiteVersion) {
+        super(buildId, suiteName, ""/* buildName */);
+        mSuiteName = suiteName;
+        mSuiteFullName = suiteFullName;
+        mSuiteVersion = suiteVersion;
+    }
+
+    public String getSuiteName() {
+        return mSuiteName;
+    }
+
+    public String getSuiteFullName() {
+        return mSuiteFullName;
+    }
+
+    public String getSuiteVersion() {
+        return mSuiteVersion;
+    }
+
+}
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..6f4c131
--- /dev/null
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/build/CompatibilityBuildProvider.java
@@ -0,0 +1,85 @@
+/*
+ * 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.IBuildInfo;
+import com.android.tradefed.build.IBuildProvider;
+
+import java.io.File;
+
+/**
+ * A simple {@link IBuildProvider} that uses a pre-existing Compatibility install.
+ */
+public class CompatibilityBuildProvider implements IBuildProvider {
+
+    private final String mSuiteBuildId;
+    private final String mSuiteName;
+    private final String mSuiteFullName;
+    private final String mSuiteVersion;
+
+    /**
+     * Creates a new {@link CompatibilityBuildProvider} which reads Test Suite-specific information
+     * from the jar's manifest file.
+     */
+    public CompatibilityBuildProvider() {
+        Package pkg = Package.getPackage("com.android.compatibility.tradefed.command");
+        mSuiteFullName = pkg.getSpecificationTitle();
+        mSuiteName = pkg.getSpecificationVendor();
+        mSuiteVersion = pkg.getSpecificationVersion();
+        mSuiteBuildId = pkg.getImplementationVersion();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public IBuildInfo getBuild() {
+        return getCompatibilityBuild();
+    }
+
+    /**
+     * Returns the {@link CompatibilityBuildInfo} for this test suite.
+     *
+     * Note: this is a convenience method for {@code (CompatibilityBuildInfo) getBuild()}
+     */
+    public CompatibilityBuildInfo getCompatibilityBuild() {
+        String mRootDirPath = System.getProperty(String.format("%s_ROOT", mSuiteName));
+        if (mRootDirPath == null || mRootDirPath.equals("")) {
+            throw new IllegalArgumentException(
+                    String.format("Missing install path property %s_ROOT", mSuiteName));
+        }
+        CompatibilityBuildInfo build = new CompatibilityBuildInfo(mSuiteBuildId, mSuiteName,
+                mSuiteFullName, mSuiteVersion);
+        build.setRootDir(new File(mRootDirPath));
+        return build;
+    }
+
+    /**
+     * {@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..9b9f68d 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,227 @@
  * 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.BuildHelper;
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildProvider;
+import com.android.compatibility.common.tradefed.result.IInvocationResult;
+import com.android.compatibility.common.tradefed.result.IInvocationResultRepo;
+import com.android.compatibility.common.tradefed.result.InvocationResultRepo;
+import com.android.compatibility.common.tradefed.result.TestStatus;
 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.FilenameFilter;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+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 {
+public abstract class CompatibilityConsole extends Console {
 
-    public static void main(String[] args) throws InterruptedException, ConfigurationException {
-        CompatibilityConsole console = new CompatibilityConsole();
-        Console.startConsole(console, args);
+    private BuildHelper mBuild = null;
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void run() {
+        BuildHelper build = getCompatibilityBuild();
+        printLine(String.format("Android %s %s (%s)", build.getSuiteName(), build.getSuiteVersion(),
+                build.getBuildId()));
+        super.run();
     }
-}
+
+    /**
+     * Adds the 'list modules' and 'list plans' commands
+     */
+    @Override
+    protected void setCustomCommands(RegexTrie<Runnable> trie, List<String> genericHelp,
+            Map<String, String> commandHelp) {
+        trie.put(new Runnable() {
+            @Override
+            public void run() {
+                BuildHelper build = getCompatibilityBuild();
+                if (build != null) {
+                    listPlans(build);
+                }
+            }
+        }, LIST_PATTERN, "p(?:lans)?");
+        trie.put(new Runnable() {
+            @Override
+            public void run() {
+                BuildHelper build = getCompatibilityBuild();
+                if (build != null) {
+                    listModules(build);
+                }
+            }
+        }, LIST_PATTERN, "m(?:modules)?");
+        trie.put(new Runnable() {
+            @Override
+            public void run() {
+                BuildHelper build = getCompatibilityBuild();
+                if (build != null) {
+                    listResults(build);
+                }
+            }
+        }, 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]\t\tList all test plans" + LINE_SEPARATOR +
+                "\tm[modules]\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 > ", getCompatibilityBuild().getSuiteName().toLowerCase());
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected String getGenericHelpString(List<String> genericHelp) {
+        BuildHelper build = getCompatibilityBuild();
+        StringBuilder helpBuilder = new StringBuilder();
+        helpBuilder.append(build.getSuiteFullName());
+        helpBuilder.append("\n\n");
+        helpBuilder.append(build.getSuiteName());
+        helpBuilder.append(" is the test harness for running the Android Compatibility");
+        helpBuilder.append("Suite, built on top of the Trade Federation framework.\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 = String.format("  run %s ", build.getSuiteName());
+        helpBuilder.append(runPrompt);
+        helpBuilder.append("--plan/-p <plan_name>: run a test plan\n");
+        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("--continue-session <session_id>: run all not executed tests from a ");
+        helpBuilder.append("previous session\n");
+        helpBuilder.append(runPrompt);
+        helpBuilder.append("--retry-session <session_id>: run all failed tests from a previous ");
+        helpBuilder.append("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(build.getSuiteFullName());
+        helpBuilder.append("\n");
+        helpBuilder.append("List:\n");
+        helpBuilder.append("  l/list d/devices: list connected devices and their state\n");
+        helpBuilder.append("  l/list p/plans: list test plans\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 listPlans(BuildHelper build) {
+        FilenameFilter xmlFilter = new FilenameFilter() {
+            @Override
+            public boolean accept(File dir, String name) {
+                return name.endsWith(".xml");
+            }
+        };
+        File[] files = build.getPlansDir().listFiles(xmlFilter);
+        if (files.length > 0) {
+            for (File planFile : files) {
+                printLine(FileUtil.getBaseName(planFile.getName()));
+            }
+        } else {
+            printLine(String.format("No plans found in %s", build.getPlansDir()));
+        }
+    }
+
+    private void listModules(BuildHelper build) {
+        FilenameFilter xmlFilter = new FilenameFilter() {
+            @Override
+            public boolean accept(File dir, String name) {
+                return name.endsWith(".config");
+            }
+        };
+        File[] files = build.getTestsDir().listFiles(xmlFilter);
+        if (files.length > 0) {
+            for (File moduleFile : files) {
+                printLine(FileUtil.getBaseName(moduleFile.getName()));
+            }
+        } else {
+            printLine(String.format("No modules found in %s", build.getTestsDir()));
+        }
+    }
+
+    private void listResults(BuildHelper build) {
+        TableFormatter tableFormatter = new TableFormatter();
+        List<List<String>> table = new ArrayList<>();
+        table.add(Arrays.asList("Session","Pass", "Fail", "Not Executed", "Start time", "Plan name",
+                "Device serial(s)"));
+        IInvocationResultRepo testResultRepo = new InvocationResultRepo(build.getResultsDir());
+        List<IInvocationResult> results = testResultRepo.getResults();
+        if (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 in %s", build.getResultsDir()));
+        }
+    }
+
+    public BuildHelper getCompatibilityBuild() {
+        if (mBuild == null) {
+            mBuild = new BuildHelper(new CompatibilityBuildProvider().getCompatibilityBuild());
+        }
+        return mBuild;
+    }
+}
\ No newline at end of file
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/DeviceInfoResult.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/DeviceInfoResult.java
new file mode 100644
index 0000000..411d037
--- /dev/null
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/DeviceInfoResult.java
@@ -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.
+ */
+package com.android.compatibility.common.tradefed.result;
+
+import java.util.Map;
+
+/**
+ * Holds information collected from the Device Under Test (DUT).
+ *
+ */
+public class DeviceInfoResult {
+
+    /**
+     * Populates this object with the DUT's information.
+     *
+     * @param metrics the metrics collected from the device.
+     */
+    public void populateMetrics(Map<String, String> runMetrics) {
+        // TODO(stuartscott): Auto-generated method stub
+    }
+
+}
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/HostStore.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/HostStore.java
new file mode 100644
index 0000000..0ef0e90
--- /dev/null
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/HostStore.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.tradefed.result;
+
+/**
+ * Holds test metrics from host tests
+ */
+public class HostStore {
+
+    /**
+     * @param deviceSerial
+     * @param abi
+     * @param name
+     * @return
+     */
+    public static String removeResult(String deviceSerial, String abi, String name) {
+        // TODO(stuartscott): Auto-generated method stub
+        return null;
+    }
+
+}
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/IInvocationResult.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/IInvocationResult.java
new file mode 100644
index 0000000..8885de9
--- /dev/null
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/IInvocationResult.java
@@ -0,0 +1,66 @@
+/*
+ * 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 java.io.File;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Interface for a the result of a single Compatibility invocation.
+ */
+public interface IInvocationResult {
+
+    /**
+     * @return the starting timestamp.
+     */
+    long getStartTime();
+
+    /**
+     * Count the number of results with given status.
+     */
+    int countResults(TestStatus result);
+
+    /**
+     * @return the test plan associated with result.
+     */
+    String getTestPlan();
+
+    /**
+     * @return the device serials associated with result.
+     */
+    List<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.
+     */
+    List<IModuleResult> getModules();
+
+    /**
+     * @return the directory containing this result.
+     */
+    File getResultDir();
+
+    /**
+     * Populate the results with collected device info metrics.
+     */
+    void populateDeviceInfoMetrics(Map<String, String> metrics);
+}
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..ab7344b
--- /dev/null
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/IInvocationResultRepo.java
@@ -0,0 +1,39 @@
+/*
+ * 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 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/IModuleResult.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/IModuleResult.java
new file mode 100644
index 0000000..f140e14
--- /dev/null
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/IModuleResult.java
@@ -0,0 +1,92 @@
+/*
+ * 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.ddmlib.testrunner.TestIdentifier;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 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 Result} for the given {@link TestIdentifier}, creating it if it doesn't exist.
+     *
+     * @param testId
+     * @return the {@link Result} or <code>null</code>
+     */
+    IResult getOrCreateResult(TestIdentifier testId);
+
+    /**
+     * Gets the test result for given {@link TestIdentifier}.
+     *
+     * @param testId
+     * @return the {@link IResult} or <code>null</code>
+     */
+    IResult getResult(TestIdentifier testId);
+
+    /**
+     * Gets all results.
+     */
+    List<IResult> getResults();
+
+    /**
+     * Gets all results which have the given status.
+     */
+    List<IResult> getResults(TestStatus status);
+
+    /**
+     * Populate values in this module result from run metrics
+     * @param metrics A map of metrics from the completed test run.
+     */
+    void populateMetrics(Map<String, String> metrics);
+
+    /**
+     * Report the given test as a failure.
+     *
+     * @param test
+     * @param status
+     * @param trace
+     */
+    void reportTestFailure(TestIdentifier test, TestStatus status, String trace);
+
+    /**
+     * Report that the given test has completed.
+     *
+     * @param test The {@link TestIdentifier} of the completed test.
+     * @param testMetrics A map holding metrics about the completed test, if any.
+     */
+    void reportTestEnded(TestIdentifier test, Map<String, String> testMetrics);
+
+    /**
+     * Counts the number of results which have the given status.
+     */
+    int countResults(TestStatus status);
+
+}
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/IResult.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/IResult.java
new file mode 100644
index 0000000..dcfa437
--- /dev/null
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/IResult.java
@@ -0,0 +1,110 @@
+/*
+ * 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;
+
+/**
+ * Represents a single test result.
+ */
+public interface IResult {
+
+    /**
+     * @return The name of this test result.
+     */
+    String getName();
+
+    /**
+     * @return The {@link TestStatus} of this result.
+     */
+    TestStatus getResultStatus();
+
+    /**
+     * Sets the {@link TestStatus} of the result and udates 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);
+
+    /**
+     * @return The time the test started
+     */
+    long getStartTime();
+
+    /**
+     * @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 headline metric..
+     */
+    String getSummary();
+
+    /**
+     * @param summary the headline metric.
+     */
+    void setSummary(String summary);
+
+    /**
+     * @return the detailed metrics.
+     */
+    String getDetails();
+
+    /**
+     * @param details the detailed metrics.
+     */
+    void setDetails(String details);
+
+    /**
+     * @return the uri of the bug report generated of the failure.
+     */
+    String getBugReport();
+
+    /**
+     * @param uri the uri of the bug report generated of the failure.
+     */
+    void setBugReport(String uri);
+
+    /**
+     * @return the uri of the log file generated of the failure.
+     */
+    String getLog();
+
+    /**
+     * @param uri the uri of the log file generated of the failure.
+     */
+    void setLog(String uri);
+
+}
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/InvocationResult.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/InvocationResult.java
new file mode 100644
index 0000000..82de0c7
--- /dev/null
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/InvocationResult.java
@@ -0,0 +1,118 @@
+/*
+ * 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 java.io.File;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Data structure for the detailed Compatibility test results.
+ */
+public class InvocationResult implements IInvocationResult {
+
+    private final long mTimestamp;
+    private Map<String, IModuleResult> mModuleResults = new LinkedHashMap<>();
+    private DeviceInfoResult mDeviceInfo = new DeviceInfoResult();
+    private String mTestPlan;
+    private List<String> mSerials;
+    private File mResultDir;
+
+    /**
+     * @param timestamp
+     * @param resultDir
+     */
+    public InvocationResult(long timestamp, File resultDir) {
+        mTimestamp = timestamp;
+        mResultDir = resultDir;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public List<IModuleResult> getModules() {
+        return new ArrayList<IModuleResult>(mModuleResults.values());
+    }
+
+    /**
+     * {@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 populateDeviceInfoMetrics(Map<String, String> metrics) {
+        mDeviceInfo.populateMetrics(metrics);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public long getStartTime() {
+        return mTimestamp;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public String getTestPlan() {
+        return mTestPlan;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public List<String> getDeviceSerials() {
+        return mSerials;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public File getResultDir() {
+        return mResultDir;
+    }
+
+}
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..9c9261c
--- /dev/null
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/InvocationResultRepo.java
@@ -0,0 +1,62 @@
+/*
+ * 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.xml.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/ModuleResult.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/ModuleResult.java
new file mode 100644
index 0000000..9dbae7c
--- /dev/null
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/ModuleResult.java
@@ -0,0 +1,212 @@
+/*
+ * 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.AbiUtils;
+import com.android.ddmlib.testrunner.TestIdentifier;
+import com.android.tradefed.log.LogUtil.CLog;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Data structure for a Compatibility test module result.
+ */
+public class ModuleResult implements IModuleResult {
+
+    public static final String RESULT_KEY = "COMPATIBILITY_TEST_RESULT";
+
+    private static final Pattern mLogPattern = Pattern.compile("(.*)\\+\\+\\+\\+(.*)");
+
+    private String mDeviceSerial;
+    private String mId;
+
+    private Map<TestIdentifier, IResult> mResults = new HashMap<>();
+    private Map<IResult, Map<String, String>> mMetrics = 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 IResult getOrCreateResult(TestIdentifier testId) {
+        IResult result = mResults.get(testId);
+        if (result == null) {
+            result = new Result(String.format("%s#%s", testId.getClassName(), testId.getTestName()));
+            mResults.put(testId, result);
+        }
+        return result;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public IResult getResult(TestIdentifier testId) {
+        return mResults.get(testId);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public List<IResult> getResults(TestStatus status) {
+        List<IResult> results = new ArrayList<>();
+        for (IResult result : mResults.values()) {
+            if (result.getResultStatus() == status) {
+                results.add(result);
+            }
+        }
+        return results;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public List<IResult> getResults() {
+        return new ArrayList<>(mResults.values());
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int countResults(TestStatus status) {
+        int total = 0;
+        for (IResult result : mResults.values()) {
+            if (result.getResultStatus() == status) {
+                total++;
+            }
+        }
+        return total;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void populateMetrics(Map<String, String> metrics) {
+        // Collect performance results
+        for (Entry<IResult, Map<String, String>> entry : mMetrics.entrySet()) {
+            IResult result = entry.getKey();
+            // device test can have performance results in test metrics
+            String perfResult = entry.getValue().get(RESULT_KEY);
+            // host test should be checked in HostStore.
+            if (perfResult == null) {
+                perfResult = HostStore.removeResult(mDeviceSerial, getAbi(), result.getName());
+            }
+            if (perfResult != null) {
+                // Compatibility result is passed in Summary++++Details format.
+                // Extract Summary and Details, and pass them.
+                Matcher m = mLogPattern.matcher(perfResult);
+                if (m.find()) {
+                    result.setResultStatus(TestStatus.PASS);
+                    result.setSummary(m.group(1));
+                    result.setDetails(m.group(2));
+                } else {
+                    CLog.e("Compatibility Result unrecognizable:" + perfResult);
+                }
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void reportTestFailure(TestIdentifier test, TestStatus status, String trace) {
+        IResult result = getResult(test);
+        result.setResultStatus(status);
+        result.setStackTrace(trace);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void reportTestEnded(TestIdentifier test, Map<String, String> testMetrics) {
+        IResult result = getResult(test);
+        if (!result.getResultStatus().equals(TestStatus.FAIL)) {
+            result.setResultStatus(TestStatus.PASS);
+        }
+        if (mMetrics.containsKey(test)) {
+            CLog.e("Test metrics already contains key: " + test);
+        }
+        mMetrics.put(result, testMetrics);
+        CLog.i("Test metrics:" + testMetrics);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int compareTo(IModuleResult another) {
+        return getId().compareTo(another.getId());
+    }
+
+}
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/PlanCreator.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/PlanCreator.java
new file mode 100644
index 0000000..42bbc5a
--- /dev/null
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/PlanCreator.java
@@ -0,0 +1,114 @@
+/*
+ * 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.BuildHelper;
+import com.android.compatibility.common.tradefed.testtype.ITestPlan;
+import com.android.compatibility.common.tradefed.testtype.TestPlan;
+import com.android.tradefed.config.ConfigurationException;
+import com.android.tradefed.config.Option;
+import com.android.tradefed.config.Option.Importance;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Class for creating test plans from Compatibility result XML.
+ */
+public class PlanCreator {
+
+    @Option (name = "plan", shortName = 'p', description = "the name of the plan to create",
+            importance=Importance.IF_UNSET)
+    private String mPlanName = null;
+
+    @Option (name = "session", shortName = 's', description = "the session id to derive from",
+            importance=Importance.IF_UNSET)
+    private Integer mSessionId = null;
+
+    private List<TestStatus> mResultFilters = null;
+
+    private IInvocationResult mResult = null;
+
+    private File mPlanFile;
+
+    /**
+     * Create an empty {@link PlanCreator}.
+     * <p/>
+     * All {@link Option} fields must be populated via
+     * {@link com.android.tradefed.config.ArgsOptionParser}
+     */
+    public PlanCreator() {
+    }
+
+    /**
+     * Create a {@link PlanCreator} using the specified option values.
+     */
+    public PlanCreator(String planName, int session, TestStatus... result) {
+        mPlanName = planName;
+        mSessionId = session;
+        mResultFilters = Arrays.asList(result);
+    }
+
+    /**
+     * Create a test plan derived from a result.
+     * <p/>
+     * {@link Option} values must all be set before this is called.
+     *
+     * @param build
+     * @return test plan
+     * @throws ConfigurationException
+     */
+    public ITestPlan createDerivedPlan(BuildHelper build) throws ConfigurationException {
+        checkFields(build);
+        ITestPlan derivedPlan = new TestPlan(mPlanName);
+        for (IModuleResult module : mResult.getModules()) {
+            String moduleId = module.getId();
+            derivedPlan.addModule(moduleId);
+            for (TestStatus status : mResultFilters) {
+                for (IResult result : module.getResults(status)) {
+                    derivedPlan.addModuleOption(moduleId, "test", result.getName());
+                }
+            }
+        }
+        return derivedPlan;
+    }
+
+    /**
+     * Check that all {@Option}s have been populated with valid values.
+     * @param build
+     * @throws ConfigurationException if any option has an invalid value
+     */
+    private void checkFields(BuildHelper build) throws ConfigurationException {
+        if (mSessionId == null) {
+            throw new ConfigurationException("Missing --session argument");
+        }
+        IInvocationResultRepo repo = new InvocationResultRepo(build.getResultsDir());
+        mResult = repo.getResult(mSessionId);
+        if (mResult == null) {
+            throw new ConfigurationException(String.format("Could not find session with id %d",
+                    mSessionId));
+        }
+        if (mPlanName == null) {
+            throw new ConfigurationException("Missing --plan argument");
+        }
+        mPlanFile = new File(build.getPlansDir(), mPlanName);
+        if (mPlanFile.exists()) {
+            throw new ConfigurationException(String.format("Test plan %s already exists",
+                    mPlanName));
+        }
+    }
+}
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/Result.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/Result.java
new file mode 100644
index 0000000..4e2f96c
--- /dev/null
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/Result.java
@@ -0,0 +1,193 @@
+/*
+ * 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;
+
+/**
+ * Represents a single test result.
+ */
+public class Result implements IResult {
+
+    private final String mTestName;
+    private final long mStartTime;
+    private long mEndTime;
+    private TestStatus mResult;
+    private String mMessage;
+    private String mStackTrace;
+    private String mSummary;
+    private String mDetails;
+    private String mBugReport;
+    private String mLog;
+
+    /**
+     * Create a {@link Result} for the given test name.
+     */
+    public Result(String name) {
+        mTestName = name;
+        mResult = TestStatus.NOT_EXECUTED;
+        mStartTime = System.currentTimeMillis();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public String getName() {
+        return mTestName;
+    }
+
+    /**
+     * {@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 long getStartTime() {
+        return mStartTime;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public long getEndTime() {
+        return mEndTime;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public String getStackTrace() {
+        return mStackTrace;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void setStackTrace(String stackTrace) {
+        mStackTrace = sanitizeStackTrace(stackTrace);
+        mMessage = mStackTrace;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public String getSummary() {
+        return mSummary;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void setSummary(String summary) {
+        mSummary = summary;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public String getDetails() {
+        return mDetails;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void setDetails(String details) {
+        mDetails = details;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public String getBugReport() {
+        return mBugReport;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void setBugReport(String uri) {
+        mBugReport = uri;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public String getLog() {
+        return mLog;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void setLog(String uri) {
+        mLog = uri;
+    }
+
+    /**
+     * 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/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..e76ceea
--- /dev/null
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/ResultReporter.java
@@ -0,0 +1,285 @@
+package com.android.compatibility.common.tradefed.result;
+
+import com.android.compatibility.common.tradefed.build.BuildHelper;
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildInfo;
+import com.android.compatibility.common.tradefed.testtype.CompatibilityTest;
+import com.android.compatibility.common.util.AbiUtils;
+import com.android.compatibility.common.xml.XmlResultHandler;
+import com.android.ddmlib.Log.LogLevel;
+import com.android.ddmlib.testrunner.TestIdentifier;
+import com.android.tradefed.build.IBuildInfo;
+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.ITestInvocationListener;
+import com.android.tradefed.result.InputStreamSource;
+import com.android.tradefed.result.LogDataType;
+import com.android.tradefed.result.LogFileSaver;
+import com.android.tradefed.result.TestSummary;
+
+import java.io.File;
+import java.io.IOException;
+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 ITestInvocationListener {
+
+    private static final String DEVICE_INFO_COLLECTOR = "com.android.compatibility.deviceinfo";
+
+    @Option(name = CompatibilityTest.PLAN_OPTION,
+            shortName = 'p',
+            description = "the test plan to run.",
+            importance = Importance.IF_UNSET)
+    private String mPlanName = null;
+
+    @Option(name = CompatibilityTest.CONTINUE_OPTION,
+            shortName = 'c',
+            description = "continue a previous session.",
+            importance = Importance.IF_UNSET)
+    private Integer mContinueSessionId = null;
+
+    @Option(name = CompatibilityTest.RETRY_OPTION,
+            shortName = 'r',
+            description = "retry a previous session.",
+            importance = Importance.IF_UNSET)
+    private Integer mRetrySessionId = null;
+
+    private String mDeviceSerial;
+
+    private boolean mInitialized;
+    private IInvocationResult mResult;
+    private File mResultDir;
+    private File mLogDir;
+    private long mStartTime;
+    private boolean mIsDeviceInfoRun;
+    private IModuleResult mCurrentModuleResult;
+    private IResult mCurrentResult;
+    private BuildHelper mBuildHelper;
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void invocationStarted(IBuildInfo buildInfo) {
+        CLog.d("ResultReporter.invocationStarted(%s)", buildInfo.toString());
+        mInitialized = false;
+        mBuildHelper = new BuildHelper((CompatibilityBuildInfo) buildInfo);
+        mDeviceSerial = buildInfo.getDeviceSerial();
+        if (mDeviceSerial == null) {
+            mDeviceSerial = "unknown_device";
+        }
+        long time = System.currentTimeMillis();
+        String dirSuffix = getDirSuffix(time);
+        if (mContinueSessionId != null) {
+            CLog.d("Continuing session %d", mContinueSessionId);
+            initializeFromSession(mContinueSessionId);
+        } else if (mRetrySessionId != null) {
+            CLog.d("Retrying session %d", mRetrySessionId);
+            initializeFromSession(mRetrySessionId);
+        } else {
+            mStartTime = time;
+            mResultDir = new File(mBuildHelper.getResultsDir(), dirSuffix);
+            if (mResultDir.mkdirs()) {
+                CLog.logAndDisplay(LogLevel.INFO, "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);
+        }
+        mLogDir = new File(mBuildHelper.getLogsDir(), dirSuffix);
+        if (mLogDir.mkdirs()) {
+            CLog.logAndDisplay(LogLevel.INFO, "Created log dir %s", mLogDir.getAbsolutePath());
+        } else {
+            throw new IllegalArgumentException(String.format("Could not create log dir %s",
+                    mLogDir.getAbsolutePath()));
+        }
+        mInitialized = true;
+    }
+
+    /**
+     * Initializes this {@link ResultReporter} from the given session.
+     */
+    private void initializeFromSession(Integer session) {
+        List<IInvocationResult> results = XmlResultHandler.getResults(mBuildHelper.getResultsDir());
+        if (session >= 0 && session < results.size()) {
+            mResult = results.get(session);
+        } else {
+            throw new IllegalArgumentException(String.format("Could not find session %d",session));
+        }
+        mPlanName = mResult.getTestPlan();
+        mStartTime = mResult.getStartTime();
+        mResultDir = mResult.getResultDir();
+    }
+
+    /**
+     * @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) {
+        CLog.d("ResultReporter.testRunStarted(%s, %d)", id, numTests);
+        mIsDeviceInfoRun = AbiUtils.parseTestName(id).equals(DEVICE_INFO_COLLECTOR);
+        if (!mIsDeviceInfoRun) {
+            CLog.logAndDisplay(LogLevel.INFO, "Starting %s with %s tests", id, numTests);
+            mCurrentModuleResult = mResult.getOrCreateModule(id);
+            mCurrentModuleResult.setDeviceSerial(mDeviceSerial);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void testStarted(TestIdentifier test) {
+        CLog.d("ResultReporter.testStarted(%s)", test.toString());
+        if (!mIsDeviceInfoRun) {
+            mCurrentResult = mCurrentModuleResult.getOrCreateResult(test);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void testEnded(TestIdentifier test, Map<String, String> metrics) {
+        CLog.d("ResultReporter.testEnded(%s, %s)", test.toString(), metrics.toString());
+        if (!mIsDeviceInfoRun) {
+            mCurrentModuleResult.reportTestEnded(test, metrics);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void testIgnored(TestIdentifier test) {
+        CLog.d("ResultReporter.testIgnored(%s)", test.toString());
+        // ignore
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void testFailed(TestIdentifier test, String trace) {
+        CLog.d("ResultReporter.testFailed(%s, %s)", test.toString(), trace);
+        if (!mIsDeviceInfoRun) {
+            mCurrentModuleResult.reportTestFailure(test, TestStatus.FAIL, trace);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void testAssumptionFailure(TestIdentifier test, String trace) {
+        CLog.d("ResultReporter.testAssumptionFailure(%s, %s)", test.toString(), trace);
+        if (!mIsDeviceInfoRun) {
+            mCurrentModuleResult.reportTestFailure(test, TestStatus.FAIL, trace);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void testRunStopped(long elapsedTime) {
+        CLog.d("ResultReporter.testRunStopped(%d)", elapsedTime);
+        // ignore
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void testRunEnded(long elapsedTime, Map<String, String> metrics) {
+        CLog.d("ResultReporter.testRunEnded(%d, %s)", elapsedTime, metrics.toString());
+        if (mIsDeviceInfoRun) {
+            mResult.populateDeviceInfoMetrics(metrics);
+        } else {
+            CLog.logAndDisplay(LogLevel.INFO, "%s completed in %dms", mCurrentModuleResult.getId(),
+                    elapsedTime);
+            mCurrentModuleResult.populateMetrics(metrics);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void testRunFailed(String id) {
+        CLog.d("ResultReporter.testRunFailed(%s)", id);
+        // ignore
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public TestSummary getSummary() {
+        // ignore
+        return null;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void invocationEnded(long elapsedTime) {
+        CLog.d("ResultReporter.invocationEnded(%d)", elapsedTime);
+        if (mInitialized) {
+            CLog.logAndDisplay(LogLevel.INFO,
+                    "Invocation finished. Passed %d, Failed %d, Not Executed %d",
+                    mResult.countResults(TestStatus.PASS),
+                    mResult.countResults(TestStatus.FAIL),
+                    mResult.countResults(TestStatus.NOT_EXECUTED));
+            XmlResultHandler.writeResults(mBuildHelper.getSuiteName(),
+                    mBuildHelper.getSuiteVersion(), mPlanName, mResult, mResultDir, mLogDir,
+                    mStartTime, elapsedTime + mStartTime);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void invocationFailed(Throwable cause) {
+        CLog.d("ResultReporter.invocationFailed(%s)", cause.toString());
+        mInitialized = false;
+        // Clean up
+        mResultDir.delete();
+        mLogDir.delete();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void testLog(String name, LogDataType type, InputStreamSource stream) {
+        CLog.d("ResultReporter.testLog(%s, %s, %s)", name, type.toString(), stream.toString());
+        try {
+            LogFileSaver saver = new LogFileSaver(mLogDir);
+            File logFile = saver.saveAndZipLogData(name, type, stream.createInputStream());
+            CLog.i("Saved logs for %s in %s", name, logFile.getAbsolutePath());
+        } catch (IOException e) {
+            CLog.e("Failed to write log for %s", name);
+            e.printStackTrace();
+        }
+    }
+
+}
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/TestStatus.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/TestStatus.java
new file mode 100644
index 0000000..8f4c21d
--- /dev/null
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/TestStatus.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.tradefed.result;
+
+/**
+ * An enum of possible test statuses.
+ *
+ * TODO should use TestStatus in ddmlib
+ */
+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/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..c307418
--- /dev/null
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/ApkInstaller.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.BuildHelper;
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildInfo;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.config.OptionClass;
+import com.android.tradefed.targetprep.TargetSetupError;
+import com.android.tradefed.targetprep.TestAppInstallSetup;
+
+import java.io.File;
+
+/**
+ * Installs specified APKs from Compatibility repository.
+ */
+@OptionClass(alias="apk-installer")
+public class ApkInstaller extends TestAppInstallSetup {
+
+    private BuildHelper mBuildHelper = null;
+
+    protected BuildHelper getBuildHelper(IBuildInfo buildInfo) {
+        if (mBuildHelper == null) {
+            mBuildHelper = new BuildHelper((CompatibilityBuildInfo) buildInfo);
+        }
+        return mBuildHelper;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected File getLocalPathForFilename(IBuildInfo buildInfo, String apkFileName)
+            throws TargetSetupError {
+        File apkFile = new File(getBuildHelper(buildInfo).getTestsDir(), apkFileName);
+        if (!apkFile.isFile()) {
+            throw new TargetSetupError(String.format("%s not found", apkFileName));
+        }
+        return apkFile;
+    }
+}
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..ba8b318
--- /dev/null
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/FilePusher.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.targetprep;
+
+import com.android.compatibility.common.tradefed.build.BuildHelper;
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildInfo;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.config.OptionClass;
+import com.android.tradefed.targetprep.PushFilePreparer;
+
+import java.io.File;
+
+/**
+ * Pushes specified testing artifacts from Compatibility repository.
+ */
+@OptionClass(alias="file-pusher")
+public class FilePusher extends PushFilePreparer {
+
+    private BuildHelper mBuildHelper;
+
+    protected BuildHelper getBuildHelper(IBuildInfo buildInfo) {
+        if (mBuildHelper == null) {
+            mBuildHelper = new BuildHelper((CompatibilityBuildInfo) buildInfo);
+        }
+        return mBuildHelper;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public File resolveRelativeFilePath(IBuildInfo buildInfo, String fileName) {
+        return new File(getBuildHelper(buildInfo).getTestsDir(), fileName);
+    }
+}
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..18da9e2
--- /dev/null
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/CompatibilityTest.java
@@ -0,0 +1,398 @@
+/*
+ * 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.BuildHelper;
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildInfo;
+import com.android.compatibility.common.tradefed.result.IModuleListener;
+import com.android.compatibility.common.tradefed.result.ModuleListener;
+import com.android.compatibility.common.tradefed.result.PlanCreator;
+import com.android.compatibility.common.tradefed.result.TestStatus;
+import com.android.compatibility.common.util.AbiUtils;
+import com.android.compatibility.common.xml.XmlPlanHandler;
+import com.android.ddmlib.Log.LogLevel;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.config.ConfigurationException;
+import com.android.tradefed.config.Option;
+import com.android.tradefed.config.Option.Importance;
+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.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.IShardableTest;
+import com.android.tradefed.util.AbiFormatter;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A Test for running Compatibility Suites
+ */
+public class CompatibilityTest implements IDeviceTest, IShardableTest, IBuildReceiver {
+
+    public static final String PLAN_OPTION = "plan";
+    public static final String MODULE_OPTION = "module";
+    public static final String CONTINUE_OPTION = "continue-session";
+    public static final String RETRY_OPTION = "retry-session";
+    public static final String ABI_OPTION = "abi";
+    public static final String SHARD_OPTION = "shard";
+
+    @Option(name = PLAN_OPTION,
+            shortName = 'p',
+            description = "the test plan to run.",
+            importance = Importance.IF_UNSET)
+    private String mPlanName = null;
+
+    @Option(name = MODULE_OPTION,
+            shortName = 'm',
+            description = "the test module to run.",
+            importance = Importance.IF_UNSET)
+    private String mModuleName = null;
+
+    @Option(name = CONTINUE_OPTION,
+            shortName = 'c',
+            description = "continue a previous session.",
+            importance = Importance.IF_UNSET)
+    private Integer mContinueSessionId = 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;
+
+    private int mShardAssignment;
+    private int mTotalShards;
+    private ITestDevice mDevice;
+    private BuildHelper mBuildHelper;
+    private IBuildInfo mBuildInfo;
+    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) {
+        mBuildHelper = new BuildHelper((CompatibilityBuildInfo) buildInfo);
+        mBuildInfo = buildInfo;
+    }
+
+    /**
+     * {@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 not support %s",
+                            getDevice().getSerialNumber(), mAbiName));
+                }
+            }
+            CLog.logAndDisplay(LogLevel.INFO, "ABIs: %s", abiSet);
+            setupTestModules(abiSet);
+
+            // Always collect the device info, even for resumed runs, since test will likely be
+            // running on a different device
+            //collectDeviceInfo(getDevice(), mBuildHelper, listener);
+
+            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);
+                IModuleListener moduleListener = new ModuleListener(module, listener);
+                CLog.logAndDisplay(LogLevel.INFO, "Module: %s", module.getId());
+                List<ITargetPreparer> preparers = module.getPreparers();
+                List<IRemoteTest> tests = module.getTests();
+                IAbi abi = module.getAbi();
+
+                List<ITargetCleaner> cleaners = new ArrayList<>();
+                // Setup
+                for (ITargetPreparer preparer : preparers) {
+                    CLog.d("Preparer: %s", preparer.getClass().getSimpleName());
+                    if (preparer instanceof IAbiReceiver) {
+                        ((IAbiReceiver) preparer).setAbi(abi);
+                    }
+                    if (preparer instanceof ITargetCleaner) {
+                        cleaners.add((ITargetCleaner) preparer);
+                    }
+                    try {
+                        preparer.setUp(getDevice(), mBuildInfo);
+                    } 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 : tests) {
+                    CLog.d("Test: %s", test.getClass().getSimpleName());
+                    if (test instanceof IBuildReceiver) {
+                        ((IBuildReceiver) test).setBuild(mBuildInfo);
+                    }
+                    if (test instanceof IDeviceTest) {
+                        ((IDeviceTest) test).setDevice(getDevice());
+                    }
+                    if (test instanceof IAbiReceiver) {
+                        ((IAbiReceiver) test).setAbi(abi);
+                    }
+                    test.run(moduleListener);
+                }
+                // Tear down - in reverse order
+                Collections.reverse(cleaners);
+                for (ITargetCleaner cleaner : cleaners) {
+                    CLog.d("Cleaner: %s", cleaner.getClass().getSimpleName());
+                    cleaner.tearDown(getDevice(), mBuildInfo, null);
+                }
+                // 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());
+        } catch (Error e) {
+            CLog.logAndDisplay(LogLevel.ERROR, "Error: %s", e.getMessage());
+        }
+    }
+
+    /**
+     * 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;
+        }
+        // Collect ALL tests
+        IModuleRepo testRepo = new ModuleRepo(mBuildHelper.getTestsDir(), abis);
+        List<IModuleDef> modules = new ArrayList<>(getModules(testRepo));
+        // Note: run() relies on the fact that the list is reliably sorted for sharding purposes
+        Collections.sort(modules);
+        // 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));
+        }
+    }
+
+    /**
+     * @return the {@link Set} of {@link IModuleDef}s to run
+     */
+    private Set<IModuleDef> getModules(IModuleRepo testRepo) {
+        // use LinkedHashSet to have predictable iteration order
+        Set<IModuleDef> moduleDefs = new LinkedHashSet<>();
+        if (mPlanName != null) {
+            CLog.i("Executing test plan %s", mPlanName);
+            File planFile = new File(mBuildHelper.getPlansDir(), mPlanName);
+            ITestPlan plan = XmlPlanHandler.parsePlan(mPlanName, planFile);
+            if (plan == null) {
+                throw new IllegalArgumentException(String.format(
+                        "Could not find plan %s. Use 'list plans' to see available plans.",
+                        mPlanName));
+            }
+            Map<String, List<IModuleDef>> modules = testRepo.getModulesByName();
+            for (String test : plan.getModuleNames()) {
+                if (!modules.containsKey(test)) {
+                    CLog.e("Could not find test %s referenced in plan %s", test, mPlanName);
+                } else {
+                    moduleDefs.addAll(modules.get(test));
+                }
+            }
+        } else if (mModuleName != null){
+            CLog.i("Executing test module %s", mModuleName);
+            Map<String, List<IModuleDef>> modules = testRepo.getModulesByName();
+            if (!modules.containsKey(mModuleName)) {
+                throw new IllegalArgumentException(String.format(
+                        "Could not find module %s. Use 'list modules' to see available modules.",
+                        mModuleName));
+            }
+            moduleDefs.addAll(modules.get(mModuleName));
+        } else if (mContinueSessionId != null) {
+            // create an in-memory derived plan that contains the notExecuted tests from previous
+            // session use timestamp as plan name so it will hopefully be unique
+            String uniquePlanName = Long.toString(System.currentTimeMillis());
+            ITestPlan plan;
+            try {
+                plan = new PlanCreator(uniquePlanName, mContinueSessionId,
+                        TestStatus.NOT_EXECUTED).createDerivedPlan(mBuildHelper);
+                Map<String, List<IModuleDef>> modules = testRepo.getModulesByName();
+                for (String test : plan.getModuleNames()) {
+                    if (!modules.containsKey(test)) {
+                        CLog.e("Could not find test %s", test);
+                    } else {
+                        moduleDefs.addAll(modules.get(test));
+                    }
+                }
+            } catch (ConfigurationException e) {
+                throw new IllegalStateException(String.format(
+                        "Could not load session %s. Use 'list results' to see available sessions.",
+                        mContinueSessionId));
+            }
+        } else if (mRetrySessionId != null) {
+            // create an in-memory derived plan that contains the failed tests from previous
+            // session use timestamp as plan name so it will hopefully be unique
+            String uniquePlanName = Long.toString(System.currentTimeMillis());
+            ITestPlan plan;
+            try {
+                plan = new PlanCreator(uniquePlanName, mRetrySessionId,
+                        TestStatus.FAIL, TestStatus.NOT_EXECUTED).createDerivedPlan(mBuildHelper);
+                Map<String, List<IModuleDef>> modules = testRepo.getModulesByName();
+                for (String test : plan.getModuleNames()) {
+                    if (!modules.containsKey(test)) {
+                        CLog.e("Could not find test %s", test);
+                    } else {
+                        moduleDefs.addAll(modules.get(test));
+                    }
+                }
+            } catch (ConfigurationException e) {
+                throw new IllegalStateException(String.format(
+                        "Could not load session %s. Use 'list results' to see available sessions.",
+                        mRetrySessionId));
+            }
+        } else {
+            throw new IllegalStateException(
+                    "Nothing to do. Use 'list plans' to see available plans, 'list modules' to see "
+                    + "available modules, and 'list results' to see available sessions to re-run.");
+        }
+        return moduleDefs;
+    }
+
+    /**
+     * 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/IModuleDef.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/IModuleDef.java
new file mode 100644
index 0000000..04bf6e5
--- /dev/null
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/IModuleDef.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.testtype;
+
+import com.android.tradefed.targetprep.ITargetPreparer;
+import com.android.tradefed.testtype.IAbi;
+import com.android.tradefed.testtype.IRemoteTest;
+
+import java.util.List;
+
+/**
+ * Container for Compatibility test info.
+ */
+public interface IModuleDef extends Comparable<IModuleDef> {
+
+    /**
+     * @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();
+
+}
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..50cac52
--- /dev/null
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/IModuleRepo.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.testtype;
+
+import com.android.compatibility.common.util.AbiUtils;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 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 module in repo.
+     */
+    Map<String, IModuleDef> getModules();
+
+    /**
+     * @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 sorted {@link List} of module ids
+     */
+    List<String> getModuleIds();
+}
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/ITestPlan.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/ITestPlan.java
new file mode 100644
index 0000000..8e1d15a
--- /dev/null
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/ITestPlan.java
@@ -0,0 +1,45 @@
+/*
+ * 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 java.util.Collection;
+
+/**
+ * Interface for accessing test plan data.
+ */
+public interface ITestPlan {
+
+    /**
+     * @return The name of this test plan.
+     */
+    String getName();
+
+    /**
+     * Gets a sorted list of module names contained in this plan.
+     */
+    Collection<String> getModuleNames();
+
+    /**
+     * Add a module to this test plan
+     */
+    void addModule(String name);
+
+    /**
+     * Adds the option to pass to the module referenced by the given name.
+     */
+    void addModuleOption(String name, String key, String value);
+
+}
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..6daf7ae
--- /dev/null
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/ModuleDef.java
@@ -0,0 +1,92 @@
+/*
+ * 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.tradefed.targetprep.ITargetPreparer;
+import com.android.tradefed.testtype.IAbi;
+import com.android.tradefed.testtype.IRemoteTest;
+
+import java.util.List;
+
+/**
+ * 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;
+
+    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 int compareTo(IModuleDef moduleDef) {
+        return getName().compareTo(moduleDef.getName());
+    }
+}
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..911fc59
--- /dev/null
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/ModuleRepo.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.testtype;
+
+import com.android.compatibility.common.util.AbiUtils;
+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;
+
+/**
+ * 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 repo files
+     *
+     * @param testCaseDir directory containing all module config xmls
+     */
+    public ModuleRepo(File testCaseDir, Set<IAbi> abis) {
+        this(new HashMap<String, IModuleDef>(), abis);
+        parse(testCaseDir);
+    }
+
+    /**
+     * 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.
+     */
+    private 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;
+    }
+}
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/TestPlan.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/TestPlan.java
new file mode 100644
index 0000000..ccaa3c1
--- /dev/null
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/TestPlan.java
@@ -0,0 +1,90 @@
+/*
+ * 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 java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Implementation of {@link ITestPlan}.
+ */
+public class TestPlan implements ITestPlan {
+
+    /**
+     * Map of modules found in plan, and their options
+     */
+    private Map<String, Map<String, List<String>>> mModuleOptionsMap;
+
+    private final String mName;
+
+    public TestPlan(String name) {
+        this(name, new LinkedHashMap<String, Map<String, List<String>>>());
+    }
+
+    public TestPlan(String name, Map<String, Map<String, List<String>>> moduleOptions) {
+        mName = name;
+        mModuleOptionsMap = moduleOptions;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public String getName() {
+        return mName;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public List<String> getModuleNames() {
+        List<String> names = new ArrayList<String>(mModuleOptionsMap.keySet());
+        Collections.sort(names);
+        return names;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void addModule(String name) {
+        mModuleOptionsMap.put(name, new HashMap<String, List<String>>());
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void addModuleOption(String name, String key, String value) {
+        Map<String, List<String>> options = mModuleOptionsMap.get(name);
+        if (options != null) {
+            List<String> values = options.get(key);
+            if (values == null) {
+                values = new ArrayList<String>();
+                options.put(key, values);
+            }
+            values.add(value);
+        } else {
+            throw new IllegalArgumentException(String.format("Could not find module %s", name));
+        }
+    }
+
+}
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/xml/XmlPlanHandler.java b/common/host-side/tradefed/src/com/android/compatibility/common/xml/XmlPlanHandler.java
new file mode 100644
index 0000000..5d552aa
--- /dev/null
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/xml/XmlPlanHandler.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.xml;
+
+import com.android.compatibility.common.tradefed.testtype.ITestPlan;
+
+import java.io.File;
+
+/**
+ * Handles parsing of plans from XML.
+ */
+public class XmlPlanHandler {
+
+    /**
+     * @param name
+     * @param planFile
+     * @return
+     */
+    public static ITestPlan parsePlan(String name, File planFile) {
+        // TODO(stuartscott): Auto-generated method stub
+        return null;
+    }
+
+}
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/xml/XmlResultHandler.java b/common/host-side/tradefed/src/com/android/compatibility/common/xml/XmlResultHandler.java
new file mode 100644
index 0000000..3174ef0
--- /dev/null
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/xml/XmlResultHandler.java
@@ -0,0 +1,252 @@
+/*
+ * 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.xml;
+
+import com.android.compatibility.common.tradefed.result.IInvocationResult;
+import com.android.compatibility.common.tradefed.result.IModuleResult;
+import com.android.compatibility.common.tradefed.result.IResult;
+import com.android.compatibility.common.tradefed.result.TestStatus;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.util.FileUtil;
+import com.android.tradefed.util.TimeUtil;
+
+import org.kxml2.io.KXmlSerializer;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Handles conversion of results to/from XML.
+ */
+public class XmlResultHandler {
+
+    private static final String NS = null;
+    private static final String TEST_RESULT_FILE_NAME = "test-result.xml";
+    private static final String RESULT_FILE_VERSION = "5.0";
+    private static final String[] RESULT_RESOURCES = {"compatibility_result.css",
+        "compatibility-result.xsd", "compatibility-result.xsl", "logo.png", "newrule-green.png"};
+
+    // XML constants
+    private static final String ABI_ATTR = "abi";
+    private static final String DETAILS_TAG = "Details";
+    private static final String DEVICE_ATTR = "device";
+    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 MESSAGE_ATTR = "message";
+    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 PLAN_ATTR = "plan";
+    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 SCORETYPE_ATTR = "score-type";
+    private static final String SOURCE_ATTR = "source";
+    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_VERSION_ATTR = "suite-version";
+    private static final String SUMMARY_TAG = "Summary";
+    private static final String TARGET_ATTR = "target";
+    private static final String UNIT_ATTR = "unit";
+    private static final String VALUEARRAY_TAG = "ValueArray";
+    private static final String VALUE_TAG = "Value";
+
+    // separators for the test result metrics
+    private static final String LOG_SEPARATOR = "\\+\\+\\+";
+    private static final String LOG_ELEM_SEPARATOR = "\\|";
+
+    /**
+     * @param resultDir
+     * @return
+     */
+    public static List<IInvocationResult> getResults(File resultDir) {
+        // TODO(stuartscott): Auto-generated method stub
+        return new ArrayList<IInvocationResult>();
+    }
+
+    /**
+     * @param planName
+     * @param result
+     * @param resultDir
+     * @param logDir
+     * @param startTime
+     */
+    public static void writeResults(String suiteName, String suiteVersion, String planName,
+            IInvocationResult result, File resultDir, File logDir, long startTime, long endTime) {
+        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 = null;
+        try {
+            stream = new FileOutputStream(resultFile);
+            KXmlSerializer serializer = new KXmlSerializer();
+            serializer.setOutput(stream, "UTF-8");
+            serializer.startDocument("UTF-8", 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, PLAN_ATTR, planName == null ? "" : planName);
+            serializer.attribute(NS, START_TIME_ATTR, TimeUtil.formatTimeStamp(startTime));
+            serializer.attribute(NS, END_TIME_ATTR, TimeUtil.formatTimeStamp(endTime));
+            serializer.attribute(NS, SUITE_NAME_ATTR, suiteName);
+            serializer.attribute(NS, SUITE_VERSION_ATTR, suiteVersion);
+            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"));
+
+            // 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");
+                serializer.attribute(NS, NAME_ATTR, module.getName());
+                serializer.attribute(NS, ABI_ATTR, module.getAbi());
+                serializer.attribute(NS, DEVICE_ATTR, module.getDeviceSerial());
+                for (IResult r : module.getResults()) {
+                    serializer.startTag(NS, "Test");
+                    serializer.attribute(NS, RESULT_ATTR, r.getResultStatus().getValue());
+                    serializer.attribute(NS, NAME_ATTR, r.getName());
+                    serializer.attribute(NS, START_TIME_ATTR, TimeUtil.formatTimeStamp(r.getStartTime()));
+                    serializer.attribute(NS, END_TIME_ATTR, TimeUtil.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 summary = r.getSummary();
+                    if (summary != null) {
+                        String[] summaryElems = summary.split(LOG_ELEM_SEPARATOR);
+                        serializer.startTag(NS, SUMMARY_TAG);
+                        serializer.attribute(NS, MESSAGE_ATTR, summaryElems[0]);
+                        if (summaryElems[1].trim().length() > 0) {
+                            serializer.attribute(NS, TARGET_ATTR, summaryElems[1].trim());
+                        }
+                        serializer.attribute(NS, SCORETYPE_ATTR, summaryElems[2]);
+                        serializer.attribute(NS, UNIT_ATTR, summaryElems[3]);
+                        serializer.text(summaryElems[4]);
+                        serializer.endTag(NS, SUMMARY_TAG);
+                        String details = r.getDetails();
+                        if (details != null) {
+                            serializer.startTag(NS, DETAILS_TAG);
+                            String[] arrays = details.split(LOG_SEPARATOR);
+                            for (int i = 0; i < arrays.length; i++) {
+                                String[] elems = arrays[i].split(LOG_ELEM_SEPARATOR);
+                                serializer.startTag(NS, VALUEARRAY_TAG);
+                                serializer.attribute(NS, SOURCE_ATTR, elems[0]);
+                                serializer.attribute(NS, MESSAGE_ATTR, elems[1]);
+                                serializer.attribute(NS, SCORETYPE_ATTR, elems[2]);
+                                serializer.attribute(NS, UNIT_ATTR, elems[3]);
+                                for (String v : elems[4].split(" ")) {
+                                    serializer.startTag(NS, VALUE_TAG);
+                                    serializer.text(v);
+                                    serializer.endTag(NS, VALUE_TAG);
+                                }
+                                serializer.endTag(NS, VALUEARRAY_TAG);
+                            }
+                            serializer.endTag(NS, DETAILS_TAG);
+                        }
+                    }
+                    serializer.endTag(NS, "Test");
+                }
+                serializer.endTag(NS, "Module");
+            }
+            serializer.endDocument();
+            copyFormattingFiles(resultDir);
+            zipResults(resultDir);
+            CLog.i("Saved results in %s", resultFile.getAbsolutePath());
+        } catch (IOException e) {
+            CLog.e(e);
+        }
+    }
+
+    /**
+     * Copy the xml formatting files stored in this jar to the results directory
+     *
+     * @param resultsDir
+     */
+    private 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) {
+                    CLog.w(String.format("Failed to write %s to file", resultFileName));
+                }
+            } else {
+                CLog.w(String.format("Failed to load %s from jar", resultFileName));
+            }
+        }
+    }
+
+    /**
+     * 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) {
+            CLog.w(String.format("Failed to create zip for %s", resultsDir.getName()));
+        }
+    }
+}
diff --git a/common/host-side/tradefed/tests/Android.mk b/common/host-side/tradefed/tests/Android.mk
new file mode 100644
index 0000000..a08a973
--- /dev/null
+++ b/common/host-side/tradefed/tests/Android.mk
@@ -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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_MODULE := compatibility-tradefed-tests
+
+LOCAL_JAR_MANIFEST := MANIFEST.mf
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_JAVA_LIBRARIES := tradefed-prebuilt compatibility-tradefed junit
+
+include $(BUILD_HOST_JAVA_LIBRARY)
diff --git a/common/host-side/tradefed/tests/MANIFEST.mf b/common/host-side/tradefed/tests/MANIFEST.mf
new file mode 100644
index 0000000..017dc9f
--- /dev/null
+++ b/common/host-side/tradefed/tests/MANIFEST.mf
@@ -0,0 +1,6 @@
+Manifest-Version: 1.0
+Main-Class: com.android.compatibility.tradefed.command.MockConsole
+Specification-Title: Compatibility Tests
+Specification-Vendor: TESTS
+Specification-Version: 1
+Implementation-Version: 2
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/build/BuildHelperTest.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/build/BuildHelperTest.java
new file mode 100644
index 0000000..53be8dd
--- /dev/null
+++ b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/build/BuildHelperTest.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.util.FileUtil;
+
+import junit.framework.TestCase;
+
+import java.io.File;
+
+public class BuildHelperTest extends TestCase {
+
+    public void testBuildHelper() throws Exception {
+        File root = FileUtil.createTempDir("root");
+        try {
+            new BuildHelper("", "blah", "", "", root);
+            fail("Build helper validation succeeded on an invalid installation");
+        } catch (IllegalArgumentException e) {
+            // Expected
+        }
+        File base = new File(root, "android-blah");
+        base.mkdirs();
+        File repo = new File(base, "repository");
+        repo.mkdirs();
+        File tests = new File(repo, "testcases");
+        tests.mkdirs();
+        File plans = new File(repo, "plans");
+        plans.mkdirs();
+        try {
+            new BuildHelper("", "blah", "", "", root);
+        } catch (IllegalArgumentException e) {
+            e.printStackTrace();
+            fail("Build helper validation failed on a valid installation");
+        }
+
+        FileUtil.recursiveDelete(root);
+    }
+
+}
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/build/CompatibilityBuildProviderTest.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/build/CompatibilityBuildProviderTest.java
new file mode 100644
index 0000000..d6c4bef
--- /dev/null
+++ b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/build/CompatibilityBuildProviderTest.java
@@ -0,0 +1,83 @@
+/*
+ * 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.tradefed.command.MockConsole;
+
+import junit.framework.TestCase;
+
+public class CompatibilityBuildProviderTest extends TestCase {
+
+    private static final String ROOT_PROPERTY = "TESTS_ROOT";
+    private static final String SUITE_FULL_NAME = "Compatibility Tests";
+    private static final String SUITE_NAME = "TESTS";
+    private static final String SUITE_VERSION = "1";
+    private static final String SUITE_BUILD_ID = "2";
+
+    // Make sure the mock is in the ClassLoader
+    private MockConsole mMockConsole;
+
+    @Override
+    public void setUp() throws Exception {
+        mMockConsole = new MockConsole();
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+        setProperty(null);
+        mMockConsole = null;
+    }
+
+    public void testManifestLoad() throws Exception {
+        setProperty("/tmp/foobar");
+        CompatibilityBuildProvider provider = new CompatibilityBuildProvider();
+        CompatibilityBuildInfo info = provider.getCompatibilityBuild();
+        assertEquals("Incorrect suite full name", SUITE_FULL_NAME, info.getSuiteFullName());
+        assertEquals("Incorrect suite name", SUITE_NAME, info.getSuiteName());
+        assertEquals("Incorrect suite version", SUITE_VERSION, info.getSuiteVersion());
+        assertEquals("Incorrect suite build id", SUITE_BUILD_ID, info.getBuildId());
+    }
+    
+    public void testProperty() throws Exception {
+        setProperty(null);
+        CompatibilityBuildProvider provider = new CompatibilityBuildProvider();
+        try {
+            // Should fail with root unset
+            provider.getCompatibilityBuild();
+            fail("Expected fail for unset root property");
+        } catch (IllegalArgumentException e) {
+            /* expected */
+        }
+        setProperty("/tmp/foobar");
+        // Shouldn't fail with root set
+        provider.getCompatibilityBuild();
+    }
+
+    /**
+     * Sets the *_ROOT property of the build's installation location.
+     *
+     * @param value the value to set, or null to clear the property.
+     */
+    private void setProperty(String value) {
+        if (value == null) {
+            System.clearProperty(ROOT_PROPERTY);
+        } else {
+            System.setProperty(ROOT_PROPERTY, value);
+        }
+    }
+
+}
diff --git a/common/device-side/util/tests/src/com/android/compatibility/common/util/DeviceUtilTest.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/command/CompatibilityConsoleTest.java
similarity index 77%
rename from common/device-side/util/tests/src/com/android/compatibility/common/util/DeviceUtilTest.java
rename to common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/command/CompatibilityConsoleTest.java
index a7e81d7..aa3f914 100644
--- a/common/device-side/util/tests/src/com/android/compatibility/common/util/DeviceUtilTest.java
+++ b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/command/CompatibilityConsoleTest.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,10 +14,10 @@
  * limitations under the License.
  */
 
-package com.android.compatibility.common.util;
+package com.android.compatibility.common.tradefed.command;
 
 import junit.framework.TestCase;
 
-public class DeviceUtilTest extends TestCase {
+public class CompatibilityConsoleTest extends TestCase {
 
 }
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/tradefed/command/MockConsole.java
similarity index 62%
rename from common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/TradefedTest.java
rename to common/host-side/tradefed/tests/src/com/android/compatibility/tradefed/command/MockConsole.java
index ab19369..20fab72 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/tradefed/command/MockConsole.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,13 +13,12 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package com.android.compatibility.tradefed.command;
 
-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.
+/**
+ * This console is not used for any purpose other than creating the package name space from which
+ * the suite-specific values in the MANIFEST.mf can be read; replicating what a test suite.
+ */
+public class MockConsole {
 
 }
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 db4fd07..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 d0a3a37..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.Expectation;
-import vogar.ExpectationStore;
-import vogar.ModeId;
-
-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) != Expectation.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/common/util/src/com/android/compatibility/common/util/AbiUtils.java b/common/util/src/com/android/compatibility/common/util/AbiUtils.java
new file mode 100644
index 0000000..ef42a00
--- /dev/null
+++ b/common/util/src/com/android/compatibility/common/util/AbiUtils.java
@@ -0,0 +1,200 @@
+/*
+ * 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 java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Utility class for handling device ABIs
+ */
+public class AbiUtils {
+
+    /**
+     * The set of 32Bit ABIs.
+     */
+    private static final Set<String> ABIS_32BIT = new HashSet<String>();
+
+    /**
+     * The set of 64Bit ABIs.
+     */
+    private static final Set<String> ABIS_64BIT = new HashSet<String>();
+
+    /**
+     * The set of ARM ABIs.
+     */
+    private static final Set<String> ARM_ABIS = new HashSet<String>();
+
+    /**
+     * The set of Intel ABIs.
+     */
+    private static final Set<String> INTEL_ABIS = new HashSet<String>();
+
+    /**
+     * The set of Mips ABIs.
+     */
+    private static final Set<String> MIPS_ABIS = new HashSet<String>();
+
+    /**
+     * The set of ABI names which Compatibility supports.
+     */
+    private static final Set<String> ABIS_SUPPORTED_BY_COMPATIBILITY = new HashSet<String>();
+
+    /**
+     * The map of architecture to ABI.
+     */
+    private static final Map<String, Set<String>> ARCH_TO_ABIS = new HashMap<String, Set<String>>();
+    static {
+        ABIS_32BIT.add("armeabi-v7a");
+        ABIS_32BIT.add("x86");
+        ABIS_32BIT.add("mips");
+
+        ABIS_64BIT.add("arm64-v8a");
+        ABIS_64BIT.add("x86_64");
+        ABIS_64BIT.add("mips64");
+
+        ARM_ABIS.add("armeabi-v7a");
+        ARM_ABIS.add("arm64-v8a");
+
+        INTEL_ABIS.add("x86");
+        INTEL_ABIS.add("x86_64");
+
+        MIPS_ABIS.add("mips");
+        MIPS_ABIS.add("mips64");
+
+        ARCH_TO_ABIS.put("arm", ARM_ABIS);
+        ARCH_TO_ABIS.put("arm64", ARM_ABIS);
+        ARCH_TO_ABIS.put("x86", INTEL_ABIS);
+        ARCH_TO_ABIS.put("x86_64", INTEL_ABIS);
+        ARCH_TO_ABIS.put("mips", MIPS_ABIS);
+        ARCH_TO_ABIS.put("mips64", MIPS_ABIS);
+
+        ABIS_SUPPORTED_BY_COMPATIBILITY.addAll(ARM_ABIS);
+        ABIS_SUPPORTED_BY_COMPATIBILITY.addAll(INTEL_ABIS);
+        ABIS_SUPPORTED_BY_COMPATIBILITY.addAll(MIPS_ABIS);
+    }
+
+    /**
+     * Private constructor to avoid instantiation.
+     */
+    private AbiUtils() {}
+
+    /**
+     * Returns the set of ABIs associated with the given architecture.
+     * @param arch The architecture to look up.
+     * @return a new Set containing the ABIs.
+     */
+    public static Set<String> getAbisForArch(String arch) {
+        if (arch == null || arch.isEmpty() || !ARCH_TO_ABIS.containsKey(arch)) {
+            return getAbisSupportedByCompatibility();
+        }
+        return new HashSet<String>(ARCH_TO_ABIS.get(arch));
+    }
+
+    /**
+     * Returns the set of ABIs supported by Compatibility.
+     * @return a new Set containing the supported ABIs.
+     */
+    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 Compatibility.
+     */
+    public static boolean isAbiSupportedByCompatibility(String abi) {
+        return ABIS_SUPPORTED_BY_COMPATIBILITY.contains(abi);
+    }
+
+    /**
+     * Creates a flag for the given ABI.
+     * @param abi the ABI to create the flag for.
+     * @return a string which can be add to a command sent to ADB.
+     */
+    public static String createAbiFlag(String abi) {
+        if (abi == null || abi.isEmpty() || !isAbiSupportedByCompatibility(abi)) {
+            return "";
+        }
+        return String.format("--abi %s ", abi);
+    }
+
+    /**
+     * Creates a unique id from the given ABI and name.
+     * @param abi The ABI to use.
+     * @param name The name to use.
+     * @return a string which uniquely identifies a run.
+     */
+    public static String createId(String abi, String name) {
+        return String.format("%s %s", abi, name);
+    }
+
+    /**
+     * Parses a unique id into the ABI and name.
+     * @param id The id to parse.
+     * @return a string array containing the ABI and name.
+     */
+    public static String[] parseId(String id) {
+        if (id == null || !id.contains(" ")) {
+            return new String[] {"", ""};
+        }
+        return id.split(" ");
+    }
+
+    /**
+     * @return the test name portion of the test id.
+     *         e.g. armeabi-v7a android.mytest = android.mytest
+     */
+    public static String parseTestName(String id) {
+        return parseId(id)[1];
+    }
+
+    /**
+     * @return the abi portion of the test id.
+     *         e.g. armeabi-v7a android.mytest = armeabi-v7a
+     */
+    public static String parseAbi(String id) {
+        return parseId(id)[0];
+    }
+
+    /**
+     * @param name The name of the ABI.
+     * @return The bitness of the ABI with the given name
+     */
+    public static String getBitness(String name) {
+        return ABIS_32BIT.contains(name) ? "32" : "64";
+    }
+
+    /**
+     * @param unsupportedAbiDescription A comma separated string containing abis.
+     * @return A List of Strings containing valid ABIs.
+     */
+    public static Set<String> parseAbiList(String unsupportedAbiDescription) {
+        Set<String> abiSet = new HashSet<>();
+        String[] descSegments = unsupportedAbiDescription.split(":");
+        if (descSegments.length == 2) {
+            for (String abi : descSegments[1].split(",")) {
+                String trimmedAbi = abi.trim();
+                if (isAbiSupportedByCompatibility(trimmedAbi)) {
+                    abiSet.add(trimmedAbi);
+                }
+            }
+        }
+        return abiSet;
+    }
+}
diff --git a/common/util/tests/Android.mk b/common/util/tests/Android.mk
new file mode 100644
index 0000000..9b0ef71
--- /dev/null
+++ b/common/util/tests/Android.mk
@@ -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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_STATIC_JAVA_LIBRARIES := junit kxml2-2.3.0 compatibility-common-util-hostsidelib
+
+LOCAL_MODULE := compatibility-common-util-tests
+
+LOCAL_MODULE_TAGS := optional
+
+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/UnitTests.java b/common/util/tests/src/com/android/compatibility/common/util/UnitTests.java
deleted file mode 100644
index 348c680..0000000
--- a/common/util/tests/src/com/android/compatibility/common/util/UnitTests.java
+++ /dev/null
@@ -1,33 +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.TestSuite;
-
-/**
- * A {@link TestSuite} for the common.util package.
- */
-public class UnitTests extends TestSuite {
-
-    public UnitTests() {
-        super();
-
-        addTestSuite(MetricsStoreTest.class);
-        addTestSuite(MetricsXmlSerializerTest.class);
-        addTestSuite(ReportLogTest.class);
-    }
-}
diff --git a/run_unit_tests.sh b/run_unit_tests.sh
new file mode 100755
index 0000000..63ae099
--- /dev/null
+++ b/run_unit_tests.sh
@@ -0,0 +1,77 @@
+#!/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;
+
+JAR_DIR=${ANDROID_HOST_OUT}/framework
+JARS="
+    compatibility-common-util-hostsidelib\
+    compatibility-common-util-tests\
+    compatibility-tradefed-tests\
+    compatibility-tradefed\
+    cts-tradefed-tests_v2\
+    cts-tradefed_v2\
+    ddmlib-prebuilt\
+    hosttestlib\
+    tradefed-prebuilt"
+
+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 ${APK}
+java $RDBG_FLAG -cp ${JAR_PATH} ${TF_CONSOLE} run singleCommand instrument --package ${COMMON_PACKAGE} --runner ${RUNNER}
+adb uninstall ${COMMON_PACKAGE}
+
+TEST_CLASSES="
+    com.android.compatibility.common.tradefed.build.BuildHelperTest\
+    com.android.compatibility.common.tradefed.build.CompatibilityBuildProviderTest\
+    com.android.compatibility.common.util.AbiUtilsTest\
+    com.android.compatibility.common.util.MetricsStoreTest\
+    com.android.compatibility.common.util.MetricsXmlSerializerTest\
+    com.android.compatibility.common.util.ReportLogTest\
+    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/tests/tests/display/AndroidTest.xml b/tests/tests/display/AndroidTest.xml
index dd42984..8fe3efe 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="com.android.cts.display" />
+        <option name="runner" value="android.support.test.runner.AndroidJUnitRunner" />
+    </test>
 </configuration>
diff --git a/tests/tests/gesture/AndroidTest.xml b/tests/tests/gesture/AndroidTest.xml
new file mode 100644
index 0000000..f4d0fed
--- /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="com.android.cts.gesture" />
+        <option name="runner" value="android.support.test.runner.AndroidJUnitRunner" />
+    </test>
+</configuration>
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 31a8d98..e5aa91d 100644
--- a/tests/tests/hardware/src/android/hardware/camera2/cts/PerformanceTest.java
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/PerformanceTest.java
@@ -275,7 +275,7 @@
                     // simulate real scenario (preview runs a bit)
                     waitForNumResults(previewResultListener, NUM_RESULTS_WAIT);
 
-                    blockingStopPreview();
+                    stopPreview();
 
                 }
                 mReportLog.printArray("Camera " + id
diff --git a/tests/tests/media/src/android/media/cts/CodecState.java b/tests/tests/media/src/android/media/cts/CodecState.java
index 8f62227..8494a74 100644
--- a/tests/tests/media/src/android/media/cts/CodecState.java
+++ b/tests/tests/media/src/android/media/cts/CodecState.java
@@ -328,12 +328,10 @@
         if (mAudioTrack != null) {
             ByteBuffer buffer = mCodecOutputBuffers[index];
             buffer.clear();
-            buffer.position(0 /* offset */);
+            ByteBuffer audioBuffer = ByteBuffer.allocate(buffer.remaining());
+            audioBuffer.put(buffer);
 
-            byte[] audioCopy = new byte[info.size];
-            buffer.get(audioCopy, 0, info.size);
-
-            mAudioTrack.write(audioCopy, info.size);
+            mAudioTrack.write(audioBuffer, info.size, info.presentationTimeUs*1000);
 
             mCodec.releaseOutputBuffer(index, false /* render */);
 
diff --git a/tests/tests/media/src/android/media/cts/NonBlockingAudioTrack.java b/tests/tests/media/src/android/media/cts/NonBlockingAudioTrack.java
index 20c1dff..3847252 100644
--- a/tests/tests/media/src/android/media/cts/NonBlockingAudioTrack.java
+++ b/tests/tests/media/src/android/media/cts/NonBlockingAudioTrack.java
@@ -21,6 +21,7 @@
 import android.media.AudioAttributes;
 import android.util.Log;
 
+import java.nio.ByteBuffer;
 import java.util.LinkedList;
 
 /**
@@ -32,20 +33,16 @@
 public class NonBlockingAudioTrack {
     private static final String TAG = NonBlockingAudioTrack.class.getSimpleName();
 
-    class QueueElem {
-        byte[] data;
-        int offset;
+    class QueueElement {
+        ByteBuffer data;
         int size;
+        long pts;
     }
 
     private AudioTrack mAudioTrack;
-    private boolean mWriteMorePending = false;
     private int mSampleRate;
-    private int mFrameSize;
-    private int mBufferSizeInFrames;
-    private int mNumFramesSubmitted = 0;
     private int mNumBytesQueued = 0;
-    private LinkedList<QueueElem> mQueue = new LinkedList<QueueElem>();
+    private LinkedList<QueueElement> mQueue = new LinkedList<QueueElement>();
 
     public NonBlockingAudioTrack(int sampleRate, int channelCount, boolean hwAvSync,
                     int audioSessionId) {
@@ -97,8 +94,6 @@
         }
 
         mSampleRate = sampleRate;
-        mFrameSize = 2 * channelCount;
-        mBufferSizeInFrames = bufferSize / mFrameSize;
     }
 
     public long getAudioTimeUs() {
@@ -116,18 +111,13 @@
     }
 
     public void stop() {
-        cancelWriteMore();
-
         mAudioTrack.stop();
 
-        mNumFramesSubmitted = 0;
         mQueue.clear();
         mNumBytesQueued = 0;
     }
 
     public void pause() {
-        cancelWriteMore();
-
         mAudioTrack.pause();
     }
 
@@ -136,95 +126,48 @@
             return;
         }
         mAudioTrack.flush();
-        mNumFramesSubmitted = 0;
         mQueue.clear();
         mNumBytesQueued = 0;
     }
 
     public void release() {
-        cancelWriteMore();
-
+        mQueue.clear();
+        mNumBytesQueued = 0;
         mAudioTrack.release();
         mAudioTrack = null;
     }
 
     public void process() {
-        mWriteMorePending = false;
-        writeMore();
+        while (!mQueue.isEmpty()) {
+            QueueElement element = mQueue.peekFirst();
+            int written = mAudioTrack.write(element.data, element.size,
+                                            AudioTrack.WRITE_NON_BLOCKING, element.pts);
+            if (written < 0) {
+                throw new RuntimeException("Audiotrack.write() failed.");
+            }
+
+            mNumBytesQueued -= written;
+            element.size -= written;
+            if (element.size != 0) {
+                break;
+            }
+            mQueue.removeFirst();
+        }
     }
 
     public int getPlayState() {
         return mAudioTrack.getPlayState();
     }
 
-    private void writeMore() {
-        if (mQueue.isEmpty()) {
-            return;
-        }
-
-        int numFramesPlayed = mAudioTrack.getPlaybackHeadPosition();
-        int numFramesPending = mNumFramesSubmitted - numFramesPlayed;
-        int numFramesAvailableToWrite = mBufferSizeInFrames - numFramesPending;
-        int numBytesAvailableToWrite = numFramesAvailableToWrite * mFrameSize;
-
-        while (numBytesAvailableToWrite > 0) {
-            QueueElem elem = mQueue.peekFirst();
-
-            int numBytes = elem.size;
-            if (numBytes > numBytesAvailableToWrite) {
-                numBytes = numBytesAvailableToWrite;
-            }
-
-            int written = mAudioTrack.write(elem.data, elem.offset, numBytes);
-            assert(written == numBytes);
-
-            mNumFramesSubmitted += written / mFrameSize;
-
-            elem.size -= numBytes;
-            numBytesAvailableToWrite -= numBytes;
-            mNumBytesQueued -= numBytes;
-
-            if (elem.size == 0) {
-                mQueue.removeFirst();
-
-                if (mQueue.isEmpty()) {
-                    break;
-                }
-            } else {
-                elem.offset += numBytes;
-            }
-        }
-
-        if (!mQueue.isEmpty()) {
-            scheduleWriteMore();
-        }
-    }
-
-    private void scheduleWriteMore() {
-        if (mWriteMorePending) {
-            return;
-        }
-
-        int numFramesPlayed = mAudioTrack.getPlaybackHeadPosition();
-        int numFramesPending = mNumFramesSubmitted - numFramesPlayed;
-        int pendingDurationMs = 1000 * numFramesPending / mSampleRate;
-
-        mWriteMorePending = true;
-    }
-
-    private void cancelWriteMore() {
-        mWriteMorePending = false;
-    }
-
-    public void write(byte[] data, int size) {
-        QueueElem elem = new QueueElem();
-        elem.data = data;
-        elem.offset = 0;
-        elem.size = size;
+    public void write(ByteBuffer data, int size, long pts) {
+        QueueElement element = new QueueElement();
+        element.data = data;
+        element.size = size;
+        element.pts  = pts;
 
         // accumulate size written to queue
         mNumBytesQueued += size;
-        mQueue.add(elem);
+        mQueue.add(element);
     }
 }
 
diff --git a/tools/cts-tradefed/Android.mk b/tools/cts-tradefed/Android.mk
new file mode 100644
index 0000000..1cf7599
--- /dev/null
+++ b/tools/cts-tradefed/Android.mk
@@ -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.
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_JAVA_RESOURCE_DIRS := res
+
+LOCAL_JAR_MANIFEST := MANIFEST.mf
+
+LOCAL_MODULE := cts-tradefed_v2
+LOCAL_MODULE_TAGS := optional
+LOCAL_JAVA_LIBRARIES := tradefed-prebuilt hosttestlib compatibility-tradefed
+
+LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
+
+include $(BUILD_HOST_JAVA_LIBRARY)
+
+# Build all sub-directories
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tools/cts-tradefed/MANIFEST.mf b/tools/cts-tradefed/MANIFEST.mf
new file mode 100644
index 0000000..5bac62a
--- /dev/null
+++ b/tools/cts-tradefed/MANIFEST.mf
@@ -0,0 +1,6 @@
+Manifest-Version: 1.0
+Main-Class: com.android.compatibility.tradefed.command.CtsConsole
+Specification-Title: Compatibility Test Suite
+Specification-Vendor: CTS_V2
+Specification-Version: 5.0_r1.91
+Implementation-Version: %BUILD_NUMBER%
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%
copy from common/host-side/scripts/compatibility-tradefed_v2
copy 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..3ae69b6
--- /dev/null
+++ b/tools/cts-tradefed/etc/cts-tradefed_v2
@@ -0,0 +1,104 @@
+#!/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-common-util-hostsidelib
+  compatibility-tradefed
+  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
+
+java $RDBG_FLAG -cp ${JAR_PATH} -DCTS_V2_ROOT=${CTS_V2_ROOT} com.android.compatibility.tradefed.command.CtsConsole "$@"
+
diff --git a/tools/cts-tradefed/res/config/cts.xml b/tools/cts-tradefed/res/config/cts.xml
new file mode 100644
index 0000000..d3bbfd9
--- /dev/null
+++ b/tools/cts-tradefed/res/config/cts.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="Runs CTS from a pre-existing CTS installation">
+
+    <include name="common-compatibility-config" />
+
+    <option name="enable-root" value="false" />
+
+</configuration>
diff --git a/tools/cts-tradefed/src/com/android/compatibility/tradefed/command/CtsConsole.java b/tools/cts-tradefed/src/com/android/compatibility/tradefed/command/CtsConsole.java
new file mode 100644
index 0000000..fcd9f94
--- /dev/null
+++ b/tools/cts-tradefed/src/com/android/compatibility/tradefed/command/CtsConsole.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.tradefed.command;
+
+import com.android.compatibility.common.tradefed.command.CompatibilityConsole;
+import com.android.tradefed.command.Console;
+import com.android.tradefed.config.ConfigurationException;
+
+/**
+ * An extension of {@link CompatibilityConsole} for running CTS tests.
+ *
+ * This file mainly exists to provide package name space from which the suite-specific values in the
+ * MANIFEST.mf can be read.
+ */
+public class CtsConsole extends CompatibilityConsole {
+
+    public static void main(String[] args) throws InterruptedException, ConfigurationException {
+        Console console = new CtsConsole();
+        Console.startConsole(console, args);
+    }
+}
diff --git a/tools/cts-tradefed/tests/Android.mk b/tools/cts-tradefed/tests/Android.mk
new file mode 100644
index 0000000..454c9d6
--- /dev/null
+++ b/tools/cts-tradefed/tests/Android.mk
@@ -0,0 +1,26 @@
+# Copyright (C) 2015 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+# Only compile source java files in this lib.
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_MODULE := cts-tradefed-tests_v2
+LOCAL_MODULE_TAGS := optional
+LOCAL_JAVA_LIBRARIES := tradefed-prebuilt cts-tradefed_v2 compatibility-tradefed
+
+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..2d29314
--- /dev/null
+++ b/tools/cts-tradefed/tests/src/com/android/compatibility/tradefed/CtsTradefedTest.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.tradefed;
+
+import com.android.compatibility.common.tradefed.build.BuildHelper;
+import com.android.compatibility.tradefed.command.CtsConsole;
+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_ROOT";
+    private static final String SUITE_FULL_NAME = "Compatibility Test Suite";
+    private static final String SUITE_NAME = "CTS";
+
+    public void testManifest() 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");
+        base.mkdirs();
+        File repo = new File(base, "repository");
+        repo.mkdirs();
+        File tests = new File(repo, "testcases");
+        tests.mkdirs();
+        File plans = new File(repo, "plans");
+        plans.mkdirs();
+        CtsConsole c = new CtsConsole();
+        BuildHelper build = c.getCompatibilityBuild();
+        assertEquals("Incorrect suite full name", SUITE_FULL_NAME, build.getSuiteFullName());
+        assertEquals("Incorrect suite name", SUITE_NAME, build.getSuiteName());
+        FileUtil.recursiveDelete(root);
+        System.clearProperty(PROPERTY_NAME);
+    }
+}