Snap for 4794708 from 8717f7c6f075b7be7bb9669880a07d6592286400 to sdk-release

Change-Id: I7dbfcbee810eef926943c833a721d03ed58f8a5d
diff --git a/Android.bp b/Android.bp
index 1beb3d6..9165626 100644
--- a/Android.bp
+++ b/Android.bp
@@ -17,5 +17,6 @@
      "proto",
      "compilation_tools",
      "runners/target/vts_hal_hidl_target",
+     "testcases",
      "utils",
 ]
diff --git a/OWNERS b/OWNERS
index b9c362e..4fd0eae 100644
--- a/OWNERS
+++ b/OWNERS
@@ -1,5 +1,3 @@
-ryanjcampbell@google.com
-trong@google.com
 yim@google.com
 yuexima@google.com
 zhuoyao@google.com
diff --git a/agents/apps/vts_agent_app/Android.mk b/agents/apps/vts_agent_app/Android.mk
index ee9d4c9..87b7f6d 100644
--- a/agents/apps/vts_agent_app/Android.mk
+++ b/agents/apps/vts_agent_app/Android.mk
@@ -23,6 +23,7 @@
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
 LOCAL_PACKAGE_NAME := VtsAgentApp
+LOCAL_SDK_VERSION := current
 
 LOCAL_JNI_SHARED_LIBRARIES := \
   libvts_agent_app_jni \
diff --git a/agents/hal/BinderClientToDriver.cpp b/agents/hal/BinderClientToDriver.cpp
index 7deb9cb..55b71b8 100644
--- a/agents/hal/BinderClientToDriver.cpp
+++ b/agents/hal/BinderClientToDriver.cpp
@@ -16,9 +16,9 @@
 
 #include <iostream>
 
-#include <utils/RefBase.h>
 #define LOG_TAG "VtsFuzzerBinderClient"
-#include <utils/Log.h>
+#include <log/log.h>
+#include <utils/RefBase.h>
 
 #include <binder/IBinder.h>
 #include <binder/IInterface.h>
diff --git a/compilation_tools/vtsc/VtsCompilerUtils.cpp b/compilation_tools/vtsc/VtsCompilerUtils.cpp
index c852f99..286bd1f 100644
--- a/compilation_tools/vtsc/VtsCompilerUtils.cpp
+++ b/compilation_tools/vtsc/VtsCompilerUtils.cpp
@@ -577,6 +577,16 @@
   return GetVersionString(message.component_type_version(), for_macro);
 }
 
+int GetMajorVersion(const ComponentSpecificationMessage& message) {
+  string version = GetVersion(message);
+  return stoi(version.substr(0, version.find('.')));
+}
+
+int GetMinorVersion(const ComponentSpecificationMessage& message) {
+  string version = GetVersion(message);
+  return stoi(version.substr(version.find('.') + 1));
+}
+
 string GetComponentBaseName(const ComponentSpecificationMessage& message) {
   if (!message.component_name().empty()) {
     return (message.component_name() == "types"
diff --git a/compilation_tools/vtsc/VtsCompilerUtils.h b/compilation_tools/vtsc/VtsCompilerUtils.h
index a851569..344ce9d 100644
--- a/compilation_tools/vtsc/VtsCompilerUtils.h
+++ b/compilation_tools/vtsc/VtsCompilerUtils.h
@@ -79,6 +79,12 @@
 std::string GetVersion(const ComponentSpecificationMessage& message,
                        bool for_macro = false);
 
+// Get component major version from the message. e.g. 1.0 -> 1
+int GetMajorVersion(const ComponentSpecificationMessage& message);
+
+// Get component minor version from the message. e.g. 1.0 -> 0
+int GetMinorVersion(const ComponentSpecificationMessage& message);
+
 // Get the base name of component from the message. e.g. typs, Foo.
 std::string GetComponentBaseName(const ComponentSpecificationMessage& message);
 
diff --git a/compilation_tools/vtsc/code_gen/driver/DriverCodeGenBase.cpp b/compilation_tools/vtsc/code_gen/driver/DriverCodeGenBase.cpp
index 8aeb78f..fc9e517 100644
--- a/compilation_tools/vtsc/code_gen/driver/DriverCodeGenBase.cpp
+++ b/compilation_tools/vtsc/code_gen/driver/DriverCodeGenBase.cpp
@@ -166,11 +166,16 @@
     out << "#include " << header << "\n";
   }
   out << "\n";
-  out << "#include <stdio.h>" << "\n";
-  out << "#include <stdarg.h>" << "\n";
-  out << "#include <stdlib.h>" << "\n";
-  out << "#include <string.h>" << "\n";
-  out << "#include <utils/Log.h>" << "\n";
+  out << "#include <log/log.h>"
+      << "\n";
+  out << "#include <stdarg.h>"
+      << "\n";
+  out << "#include <stdio.h>"
+      << "\n";
+  out << "#include <stdlib.h>"
+      << "\n";
+  out << "#include <string.h>"
+      << "\n";
   out << "\n";
   out << "#include <driver_base/DriverBase.h>"
       << "\n";
diff --git a/compilation_tools/vtsc/code_gen/driver/HalHidlCodeGen.cpp b/compilation_tools/vtsc/code_gen/driver/HalHidlCodeGen.cpp
index 49dc0b1..3b85148 100644
--- a/compilation_tools/vtsc/code_gen/driver/HalHidlCodeGen.cpp
+++ b/compilation_tools/vtsc/code_gen/driver/HalHidlCodeGen.cpp
@@ -527,7 +527,11 @@
   out << "#include <hidl/HidlSupport.h>" << "\n";
 
   for (const auto& import : message.import()) {
-    FQName import_name = FQName(import);
+    FQName import_name;
+    if (!FQName::parse(import, &import_name)) {
+      abort();
+    }
+
     string import_package_name = import_name.package();
     string import_package_version = import_name.version();
     string import_component_name = import_name.name();
@@ -672,7 +676,7 @@
         + ClearStringWithNameSpaceAccess(attribute.name());
     out << "void " << func_name
         << "(const VariableSpecificationMessage& var_msg, " << attribute.name()
-        << "* arg);\n";
+        << "* arg, const string& callback_socket_name);\n";
   } else if (attribute.type() == TYPE_ENUM) {
     string func_name = "EnumValue"
             + ClearStringWithNameSpaceAccess(attribute.name());
@@ -711,9 +715,11 @@
       }
       string func_name = "MessageTo"
           + ClearStringWithNameSpaceAccess(attribute.name());
-      out << "void " << func_name << "(const VariableSpecificationMessage& "
-                                     "var_msg __attribute__((__unused__)), "
-          << attribute.name() << "* arg __attribute__((__unused__))) {"
+      out << "void " << func_name
+          << "(const VariableSpecificationMessage& "
+             "var_msg __attribute__((__unused__)), "
+          << attribute.name() << "* arg __attribute__((__unused__)), "
+          << "const string& callback_socket_name __attribute__((__unused__))) {"
           << "\n";
       out.indent();
       int struct_index = 0;
@@ -737,7 +743,9 @@
           + ClearStringWithNameSpaceAccess(attribute.name());
       out << "void " << func_name
           << "(const VariableSpecificationMessage& var_msg, "
-          << attribute.name() << "* arg) {" << "\n";
+          << attribute.name() << "* arg, "
+          << "const string& callback_socket_name __attribute__((__unused__))) {"
+          << "\n";
       out.indent();
       int union_index = 0;
       for (const auto& union_value : attribute.union_value()) {
@@ -870,8 +878,8 @@
       if (val.has_predefined_type()) {
         string func_name = "MessageTo"
             + ClearStringWithNameSpaceAccess(val.predefined_type());
-        out << func_name << "(" << arg_value_name << ", &("
-            << arg_name << "));\n";
+        out << func_name << "(" << arg_value_name << ", &(" << arg_name
+            << "), callback_socket_name);\n";
       } else {
         int struct_index = 0;
         for (const auto struct_field : val.struct_value()) {
@@ -892,7 +900,7 @@
         string func_name = "MessageTo"
             + ClearStringWithNameSpaceAccess(val.predefined_type());
         out << func_name << "(" << arg_value_name << ", &(" << arg_name
-            << "));\n";
+            << "), callback_socket_name);\n";
       } else {
         int union_index = 0;
         for (const auto union_field : val.union_value()) {
diff --git a/compilation_tools/vtsc/code_gen/profiler/HalHidlProfilerCodeGen.cpp b/compilation_tools/vtsc/code_gen/profiler/HalHidlProfilerCodeGen.cpp
index 7d11ad0..d9e603a 100644
--- a/compilation_tools/vtsc/code_gen/profiler/HalHidlProfilerCodeGen.cpp
+++ b/compilation_tools/vtsc/code_gen/profiler/HalHidlProfilerCodeGen.cpp
@@ -445,7 +445,11 @@
 
   // Include imported classes.
   for (const auto& import : message.import()) {
-    FQName import_name = FQName(import);
+    FQName import_name;
+    if (!FQName::parse(import, &import_name)) {
+      abort();
+    }
+
     string imported_package_name = import_name.package();
     string imported_package_version = import_name.version();
     string imported_component_name = import_name.name();
@@ -503,11 +507,16 @@
   out << "return;\n";
   out.unindent();
   out << "}\n";
-
-  out << "if (strcmp(version, \"" << GetVersion(message) << "\") != 0) {\n";
+  out << "std::string version_str = std::string(version);\n";
+  out << "int major_version = stoi(version_str.substr(0, "
+         "version_str.find('.')));\n";
+  out << "int minor_version = stoi(version_str.substr(version_str.find('.') + "
+         "1));\n";
+  out << "if (major_version != " << GetMajorVersion(message)
+      << " || minor_version > " << GetMinorVersion(message) << ") {\n";
   out.indent();
   out << "LOG(WARNING) << \"incorrect version. Expect: " << GetVersion(message)
-      << " actual: \" << interface;\n";
+      << " or lower (if version != x.0), actual: \" << version;\n";
   out << "return;\n";
   out.unindent();
   out << "}\n";
diff --git a/compilation_tools/vtsc/test/Android.mk b/compilation_tools/vtsc/test/Android.mk
index 136db8c..9d5b82b 100644
--- a/compilation_tools/vtsc/test/Android.mk
+++ b/compilation_tools/vtsc/test/Android.mk
@@ -32,7 +32,7 @@
 $(LOCAL_BUILT_MODULE): $(PRIVATE_PY_SCRIPT) $(HOST_OUT_EXECUTABLES)/vtsc
 $(LOCAL_BUILT_MODULE): $(PRIVATE_PY_SCRIPT) $(HOST_OUT_EXECUTABLES)/hidl-gen
 	@echo "Regression test (build time): $(PRIVATE_MODULE)"
-	$(hide) PYTHONPATH=$$PYTHONPATH:test \
+	$(hide) PYTHONPATH=$$PYTHONPATH:external/python/futures:test \
 	python $(PRIVATE_PY_SCRIPT) -h $(PRIVATE_HIDL_EXEC) -p $(PRIVATE_VTSC_EXEC) \
 	    -c $(PRIVATE_CANONICAL_DIR) -o $(PRIVATE_OUT_DIR) -t $(PRIVATE_TEMP_DIR)
 	$(hide) touch $@
diff --git a/compilation_tools/vtsc/test/golden/DRIVER/Bar.driver.cpp b/compilation_tools/vtsc/test/golden/DRIVER/Bar.driver.cpp
index 576890a..dbd9302 100644
--- a/compilation_tools/vtsc/test/golden/DRIVER/Bar.driver.cpp
+++ b/compilation_tools/vtsc/test/golden/DRIVER/Bar.driver.cpp
@@ -10,8 +10,8 @@
 using namespace android::hardware::tests::bar::V1_0;
 namespace android {
 namespace vts {
-void MessageTo__android__hardware__tests__bar__V1_0__IBar__SomethingRelated(const VariableSpecificationMessage& var_msg __attribute__((__unused__)), ::android::hardware::tests::bar::V1_0::IBar::SomethingRelated* arg __attribute__((__unused__))) {
-    MessageTo__android__hardware__tests__foo__V1_0__Unrelated(var_msg.struct_value(0), &(arg->myRelated));
+void MessageTo__android__hardware__tests__bar__V1_0__IBar__SomethingRelated(const VariableSpecificationMessage& var_msg __attribute__((__unused__)), ::android::hardware::tests::bar::V1_0::IBar::SomethingRelated* arg __attribute__((__unused__)), const string& callback_socket_name __attribute__((__unused__))) {
+    MessageTo__android__hardware__tests__foo__V1_0__Unrelated(var_msg.struct_value(0), &(arg->myRelated), callback_socket_name);
 }
 bool Verify__android__hardware__tests__bar__V1_0__IBar__SomethingRelated(const VariableSpecificationMessage& expected_result __attribute__((__unused__)), const VariableSpecificationMessage& actual_result __attribute__((__unused__))){
     if (!Verify__android__hardware__tests__foo__V1_0__Unrelated(expected_result.struct_value(0), actual_result.struct_value(0))) { return false; }
@@ -45,6 +45,29 @@
 }
 
 
+::android::hardware::Return<void> Vts_android_hardware_tests_bar_V1_0_IBar::convertToBoolIfSmall(
+    ::android::hardware::tests::foo::V1_0::IFoo::Discriminator arg0 __attribute__((__unused__)),
+    const ::android::hardware::hidl_vec<::android::hardware::tests::foo::V1_0::IFoo::Union>& arg1 __attribute__((__unused__)), std::function<void(const ::android::hardware::hidl_vec<::android::hardware::tests::foo::V1_0::IFoo::ContainsUnion>& arg0)> cb) {
+    LOG(INFO) << "convertToBoolIfSmall called";
+    AndroidSystemCallbackRequestMessage callback_message;
+    callback_message.set_id(GetCallbackID("convertToBoolIfSmall"));
+    callback_message.set_name("Vts_android_hardware_tests_bar_V1_0_IBar::convertToBoolIfSmall");
+    VariableSpecificationMessage* var_msg0 = callback_message.add_arg();
+    var_msg0->set_type(TYPE_ENUM);
+    SetResult__android__hardware__tests__foo__V1_0__IFoo__Discriminator(var_msg0, arg0);
+    VariableSpecificationMessage* var_msg1 = callback_message.add_arg();
+    var_msg1->set_type(TYPE_VECTOR);
+    var_msg1->set_vector_size(arg1.size());
+    for (int i = 0; i < (int)arg1.size(); i++) {
+        auto *var_msg1_vector_i = var_msg1->add_vector_value();
+        var_msg1_vector_i->set_type(TYPE_UNION);
+        SetResult__android__hardware__tests__foo__V1_0__IFoo__Union(var_msg1_vector_i, arg1[i]);
+    }
+    RpcCallToAgent(callback_message, callback_socket_name_);
+    cb(::android::hardware::hidl_vec<::android::hardware::tests::foo::V1_0::IFoo::ContainsUnion>());
+    return ::android::hardware::Void();
+}
+
 ::android::hardware::Return<void> Vts_android_hardware_tests_bar_V1_0_IBar::doThis(
     float arg0 __attribute__((__unused__))) {
     LOG(INFO) << "doThis called";
@@ -623,6 +646,31 @@
         LOG(ERROR) << "hw_binder_proxy_ is null. ";
         return false;
     }
+    if (!strcmp(func_name, "convertToBoolIfSmall")) {
+        ::android::hardware::tests::foo::V1_0::IFoo::Discriminator arg0;
+        arg0 = EnumValue__android__hardware__tests__foo__V1_0__IFoo__Discriminator(func_msg.arg(0).scalar_value());
+        ::android::hardware::hidl_vec<::android::hardware::tests::foo::V1_0::IFoo::Union> arg1;
+        arg1.resize(func_msg.arg(1).vector_value_size());
+        for (int arg1_index = 0; arg1_index < func_msg.arg(1).vector_value_size(); arg1_index++) {
+            MessageTo__android__hardware__tests__foo__V1_0__IFoo__Union(func_msg.arg(1).vector_value(arg1_index), &(arg1[arg1_index]), callback_socket_name);
+        }
+        LOG(DEBUG) << "local_device = " << hw_binder_proxy_.get();
+        ::android::hardware::hidl_vec<::android::hardware::tests::foo::V1_0::IFoo::ContainsUnion> result0;
+        hw_binder_proxy_->convertToBoolIfSmall(arg0, arg1, [&](const ::android::hardware::hidl_vec<::android::hardware::tests::foo::V1_0::IFoo::ContainsUnion>& arg0){
+            LOG(INFO) << "callback convertToBoolIfSmall called";
+            result0 = arg0;
+        });
+        result_msg->set_name("convertToBoolIfSmall");
+        VariableSpecificationMessage* result_val_0 = result_msg->add_return_type_hidl();
+        result_val_0->set_type(TYPE_VECTOR);
+        result_val_0->set_vector_size(result0.size());
+        for (int i = 0; i < (int)result0.size(); i++) {
+            auto *result_val_0_vector_i = result_val_0->add_vector_value();
+            result_val_0_vector_i->set_type(TYPE_STRUCT);
+            SetResult__android__hardware__tests__foo__V1_0__IFoo__ContainsUnion(result_val_0_vector_i, result0[i]);
+        }
+        return true;
+    }
     if (!strcmp(func_name, "doThis")) {
         float arg0 = 0;
         arg0 = func_msg.arg(0).scalar_value().float_t();
@@ -749,7 +797,7 @@
         ::android::hardware::hidl_vec<::android::hardware::tests::foo::V1_0::IFoo::Goober> arg0;
         arg0.resize(func_msg.arg(0).vector_value_size());
         for (int arg0_index = 0; arg0_index < func_msg.arg(0).vector_value_size(); arg0_index++) {
-            MessageTo__android__hardware__tests__foo__V1_0__IFoo__Goober(func_msg.arg(0).vector_value(arg0_index), &(arg0[arg0_index]));
+            MessageTo__android__hardware__tests__foo__V1_0__IFoo__Goober(func_msg.arg(0).vector_value(arg0_index), &(arg0[arg0_index]), callback_socket_name);
         }
         LOG(DEBUG) << "local_device = " << hw_binder_proxy_.get();
         hw_binder_proxy_->haveAGooberVec(arg0);
@@ -758,7 +806,7 @@
     }
     if (!strcmp(func_name, "haveAGoober")) {
         ::android::hardware::tests::foo::V1_0::IFoo::Goober arg0;
-        MessageTo__android__hardware__tests__foo__V1_0__IFoo__Goober(func_msg.arg(0), &(arg0));
+        MessageTo__android__hardware__tests__foo__V1_0__IFoo__Goober(func_msg.arg(0), &(arg0), callback_socket_name);
         LOG(DEBUG) << "local_device = " << hw_binder_proxy_.get();
         hw_binder_proxy_->haveAGoober(arg0);
         result_msg->set_name("haveAGoober");
@@ -773,8 +821,8 @@
             for (int arg0_arg0_index__numbers_index = 0; arg0_arg0_index__numbers_index < func_msg.arg(0).vector_value(arg0_index).struct_value(3).vector_value_size(); arg0_arg0_index__numbers_index++) {
                 arg0[arg0_index].numbers[arg0_arg0_index__numbers_index] = func_msg.arg(0).vector_value(arg0_index).struct_value(3).vector_value(arg0_arg0_index__numbers_index).scalar_value().double_t();
             }
-            MessageTo__android__hardware__tests__foo__V1_0__IFoo__Fumble(func_msg.arg(0).vector_value(arg0_index).struct_value(4), &(arg0[arg0_index].fumble));
-            MessageTo__android__hardware__tests__foo__V1_0__IFoo__Fumble(func_msg.arg(0).vector_value(arg0_index).struct_value(5), &(arg0[arg0_index].gumble));
+            MessageTo__android__hardware__tests__foo__V1_0__IFoo__Fumble(func_msg.arg(0).vector_value(arg0_index).struct_value(4), &(arg0[arg0_index].fumble), callback_socket_name);
+            MessageTo__android__hardware__tests__foo__V1_0__IFoo__Fumble(func_msg.arg(0).vector_value(arg0_index).struct_value(5), &(arg0[arg0_index].gumble), callback_socket_name);
         }
         LOG(DEBUG) << "local_device = " << hw_binder_proxy_.get();
         hw_binder_proxy_->haveAGooberArray(arg0);
@@ -783,7 +831,7 @@
     }
     if (!strcmp(func_name, "haveATypeFromAnotherFile")) {
         ::android::hardware::tests::foo::V1_0::Abc arg0;
-        MessageTo__android__hardware__tests__foo__V1_0__Abc(func_msg.arg(0), &(arg0));
+        MessageTo__android__hardware__tests__foo__V1_0__Abc(func_msg.arg(0), &(arg0), callback_socket_name);
         LOG(DEBUG) << "local_device = " << hw_binder_proxy_.get();
         hw_binder_proxy_->haveATypeFromAnotherFile(arg0);
         result_msg->set_name("haveATypeFromAnotherFile");
@@ -868,7 +916,7 @@
     }
     if (!strcmp(func_name, "callingDrWho")) {
         ::android::hardware::tests::foo::V1_0::IFoo::MultiDimensional arg0;
-        MessageTo__android__hardware__tests__foo__V1_0__IFoo__MultiDimensional(func_msg.arg(0), &(arg0));
+        MessageTo__android__hardware__tests__foo__V1_0__IFoo__MultiDimensional(func_msg.arg(0), &(arg0), callback_socket_name);
         LOG(DEBUG) << "local_device = " << hw_binder_proxy_.get();
         ::android::hardware::tests::foo::V1_0::IFoo::MultiDimensional result0;
         hw_binder_proxy_->callingDrWho(arg0, [&](const ::android::hardware::tests::foo::V1_0::IFoo::MultiDimensional& arg0){
@@ -883,7 +931,7 @@
     }
     if (!strcmp(func_name, "transpose")) {
         ::android::hardware::tests::foo::V1_0::IFoo::StringMatrix5x3 arg0;
-        MessageTo__android__hardware__tests__foo__V1_0__IFoo__StringMatrix5x3(func_msg.arg(0), &(arg0));
+        MessageTo__android__hardware__tests__foo__V1_0__IFoo__StringMatrix5x3(func_msg.arg(0), &(arg0), callback_socket_name);
         LOG(DEBUG) << "local_device = " << hw_binder_proxy_.get();
         ::android::hardware::tests::foo::V1_0::IFoo::StringMatrix3x5 result0;
         hw_binder_proxy_->transpose(arg0, [&](const ::android::hardware::tests::foo::V1_0::IFoo::StringMatrix3x5& arg0){
@@ -1175,7 +1223,7 @@
             arg0 = nullptr;
         }
         ::android::hardware::tests::foo::V1_0::Abc arg1;
-        MessageTo__android__hardware__tests__foo__V1_0__Abc(func_msg.arg(1), &(arg1));
+        MessageTo__android__hardware__tests__foo__V1_0__Abc(func_msg.arg(1), &(arg1), callback_socket_name);
         LOG(DEBUG) << "local_device = " << hw_binder_proxy_.get();
         bool result0;
         bool result1;
@@ -1201,7 +1249,7 @@
         uint8_t arg1;
         arg1 = func_msg.arg(1).scalar_value().uint8_t();
         ::android::hardware::tests::foo::V1_0::IFoo::MyMask arg2;
-        MessageTo__android__hardware__tests__foo__V1_0__IFoo__MyMask(func_msg.arg(2), &(arg2));
+        MessageTo__android__hardware__tests__foo__V1_0__IFoo__MyMask(func_msg.arg(2), &(arg2), callback_socket_name);
         uint8_t arg3;
         arg3 = func_msg.arg(3).scalar_value().uint8_t();
         LOG(DEBUG) << "local_device = " << hw_binder_proxy_.get();
@@ -1267,6 +1315,17 @@
 
 bool FuzzerExtended_android_hardware_tests_bar_V1_0_IBar::VerifyResults(const FunctionSpecificationMessage& expected_result __attribute__((__unused__)),
     const FunctionSpecificationMessage& actual_result __attribute__((__unused__))) {
+    if (!strcmp(actual_result.name().c_str(), "convertToBoolIfSmall")) {
+        if (actual_result.return_type_hidl_size() != expected_result.return_type_hidl_size() ) { return false; }
+        if (actual_result.return_type_hidl(0).vector_value_size() != expected_result.return_type_hidl(0).vector_value_size()) {
+            LOG(ERROR) << "Verification failed for vector size. expected: " << expected_result.return_type_hidl(0).vector_value_size() << " actual: " << actual_result.return_type_hidl(0).vector_value_size();
+            return false;
+        }
+        for (int i = 0; i <expected_result.return_type_hidl(0).vector_value_size(); i++) {
+            if (!Verify__android__hardware__tests__foo__V1_0__IFoo__ContainsUnion(expected_result.return_type_hidl(0).vector_value(i), actual_result.return_type_hidl(0).vector_value(i))) { return false; }
+        }
+        return true;
+    }
     if (!strcmp(actual_result.name().c_str(), "doThis")) {
         if (actual_result.return_type_hidl_size() != expected_result.return_type_hidl_size() ) { return false; }
         return true;
diff --git a/compilation_tools/vtsc/test/golden/DRIVER/Bar.vts.h b/compilation_tools/vtsc/test/golden/DRIVER/Bar.vts.h
index 9492188..83fdfaf 100644
--- a/compilation_tools/vtsc/test/golden/DRIVER/Bar.vts.h
+++ b/compilation_tools/vtsc/test/golden/DRIVER/Bar.vts.h
@@ -4,11 +4,11 @@
 #undef LOG_TAG
 #define LOG_TAG "FuzzerExtended_android_hardware_tests_bar_V1_0_IBar"
 
-#include <stdio.h>
+#include <log/log.h>
 #include <stdarg.h>
+#include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
-#include <utils/Log.h>
 
 #include <driver_base/DriverBase.h>
 #include <driver_base/DriverCallbackBase.h>
@@ -35,7 +35,7 @@
 using namespace android::hardware::tests::bar::V1_0;
 namespace android {
 namespace vts {
-void MessageTo__android__hardware__tests__bar__V1_0__IBar__SomethingRelated(const VariableSpecificationMessage& var_msg, ::android::hardware::tests::bar::V1_0::IBar::SomethingRelated* arg);
+void MessageTo__android__hardware__tests__bar__V1_0__IBar__SomethingRelated(const VariableSpecificationMessage& var_msg, ::android::hardware::tests::bar::V1_0::IBar::SomethingRelated* arg, const string& callback_socket_name);
 bool Verify__android__hardware__tests__bar__V1_0__IBar__SomethingRelated(const VariableSpecificationMessage& expected_result, const VariableSpecificationMessage& actual_result);
 void SetResult__android__hardware__tests__bar__V1_0__IBar__SomethingRelated(VariableSpecificationMessage* result_msg, ::android::hardware::tests::bar::V1_0::IBar::SomethingRelated result_value);
 
@@ -46,6 +46,10 @@
 
     virtual ~Vts_android_hardware_tests_bar_V1_0_IBar() = default;
 
+    ::android::hardware::Return<void> convertToBoolIfSmall(
+        ::android::hardware::tests::foo::V1_0::IFoo::Discriminator arg0,
+        const ::android::hardware::hidl_vec<::android::hardware::tests::foo::V1_0::IFoo::Union>& arg1, std::function<void(const ::android::hardware::hidl_vec<::android::hardware::tests::foo::V1_0::IFoo::ContainsUnion>& arg0)> cb) override;
+
     ::android::hardware::Return<void> doThis(
         float arg0) override;
 
diff --git a/compilation_tools/vtsc/test/golden/DRIVER/MemoryTest.driver.cpp b/compilation_tools/vtsc/test/golden/DRIVER/MemoryTest.driver.cpp
index f2b544d..2209b45 100644
--- a/compilation_tools/vtsc/test/golden/DRIVER/MemoryTest.driver.cpp
+++ b/compilation_tools/vtsc/test/golden/DRIVER/MemoryTest.driver.cpp
@@ -175,7 +175,7 @@
     }
     if (!strcmp(func_name, "haveSomeMemoryBlock")) {
         ::android::hidl::memory::block::V1_0::MemoryBlock arg0;
-        MessageTo__android__hidl__memory__block__V1_0__MemoryBlock(func_msg.arg(0), &(arg0));
+        MessageTo__android__hidl__memory__block__V1_0__MemoryBlock(func_msg.arg(0), &(arg0), callback_socket_name);
         LOG(DEBUG) << "local_device = " << hw_binder_proxy_.get();
         ::android::hidl::memory::block::V1_0::MemoryBlock result0;
         hw_binder_proxy_->haveSomeMemoryBlock(arg0, [&](const ::android::hidl::memory::block::V1_0::MemoryBlock& arg0){
diff --git a/compilation_tools/vtsc/test/golden/DRIVER/MemoryTest.vts.h b/compilation_tools/vtsc/test/golden/DRIVER/MemoryTest.vts.h
index 747c2e1..6c1bfe6 100644
--- a/compilation_tools/vtsc/test/golden/DRIVER/MemoryTest.vts.h
+++ b/compilation_tools/vtsc/test/golden/DRIVER/MemoryTest.vts.h
@@ -4,11 +4,11 @@
 #undef LOG_TAG
 #define LOG_TAG "FuzzerExtended_android_hardware_tests_memory_V1_0_IMemoryTest"
 
-#include <stdio.h>
+#include <log/log.h>
 #include <stdarg.h>
+#include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
-#include <utils/Log.h>
 
 #include <driver_base/DriverBase.h>
 #include <driver_base/DriverCallbackBase.h>
diff --git a/compilation_tools/vtsc/test/golden/DRIVER/Nfc.vts.h b/compilation_tools/vtsc/test/golden/DRIVER/Nfc.vts.h
index 499528b..107ae2d 100644
--- a/compilation_tools/vtsc/test/golden/DRIVER/Nfc.vts.h
+++ b/compilation_tools/vtsc/test/golden/DRIVER/Nfc.vts.h
@@ -4,11 +4,11 @@
 #undef LOG_TAG
 #define LOG_TAG "FuzzerExtended_android_hardware_nfc_V1_0_INfc"
 
-#include <stdio.h>
+#include <log/log.h>
 #include <stdarg.h>
+#include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
-#include <utils/Log.h>
 
 #include <driver_base/DriverBase.h>
 #include <driver_base/DriverCallbackBase.h>
diff --git a/compilation_tools/vtsc/test/golden/DRIVER/NfcClientCallback.vts.h b/compilation_tools/vtsc/test/golden/DRIVER/NfcClientCallback.vts.h
index b8457b1..9626955 100644
--- a/compilation_tools/vtsc/test/golden/DRIVER/NfcClientCallback.vts.h
+++ b/compilation_tools/vtsc/test/golden/DRIVER/NfcClientCallback.vts.h
@@ -4,11 +4,11 @@
 #undef LOG_TAG
 #define LOG_TAG "FuzzerExtended_android_hardware_nfc_V1_0_INfcClientCallback"
 
-#include <stdio.h>
+#include <log/log.h>
 #include <stdarg.h>
+#include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
-#include <utils/Log.h>
 
 #include <driver_base/DriverBase.h>
 #include <driver_base/DriverCallbackBase.h>
diff --git a/compilation_tools/vtsc/test/golden/DRIVER/TestMsgQ.vts.h b/compilation_tools/vtsc/test/golden/DRIVER/TestMsgQ.vts.h
index 3514a75..15a69da 100644
--- a/compilation_tools/vtsc/test/golden/DRIVER/TestMsgQ.vts.h
+++ b/compilation_tools/vtsc/test/golden/DRIVER/TestMsgQ.vts.h
@@ -4,11 +4,11 @@
 #undef LOG_TAG
 #define LOG_TAG "FuzzerExtended_android_hardware_tests_msgq_V1_0_ITestMsgQ"
 
-#include <stdio.h>
+#include <log/log.h>
 #include <stdarg.h>
+#include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
-#include <utils/Log.h>
 
 #include <driver_base/DriverBase.h>
 #include <driver_base/DriverCallbackBase.h>
diff --git a/compilation_tools/vtsc/test/golden/DRIVER/libcV1.driver.cpp b/compilation_tools/vtsc/test/golden/DRIVER/libcV1.driver.cpp
index c5f72d6..49354f2 100644
--- a/compilation_tools/vtsc/test/golden/DRIVER/libcV1.driver.cpp
+++ b/compilation_tools/vtsc/test/golden/DRIVER/libcV1.driver.cpp
@@ -5,7 +5,7 @@
 #include <linux/socket.h>
 #include "vts_datatype.h"
 #include "vts_measurement.h"
-#include <iostream>
+#include <android-base/logging.h>
 
 
 namespace android {
@@ -14,14 +14,14 @@
     FunctionSpecificationMessage* func_msg,
     void** result, const string& callback_socket_name) {
     const char* func_name = func_msg->name().c_str();
-    cout << "Function: " << func_name << endl;
+    LOG(INFO) << "Function: " << func_name;
     if (!strcmp(func_name, "socket")) {
         int32_t arg0 = (func_msg->arg(0).type() == TYPE_SCALAR && func_msg->arg(0).scalar_value().has_int32_t()) ? func_msg->arg(0).scalar_value().int32_t() : RandomInt32();
-        cout << "arg0 = " << arg0 << endl;
+    LOG(INFO) << "arg0 = " << arg0;
         int32_t arg1 = (func_msg->arg(1).type() == TYPE_SCALAR && func_msg->arg(1).scalar_value().has_int32_t()) ? func_msg->arg(1).scalar_value().int32_t() : RandomInt32();
-        cout << "arg1 = " << arg1 << endl;
+    LOG(INFO) << "arg1 = " << arg1;
         int32_t arg2 = (func_msg->arg(2).type() == TYPE_SCALAR && func_msg->arg(2).scalar_value().has_int32_t()) ? func_msg->arg(2).scalar_value().int32_t() : RandomInt32();
-        cout << "arg2 = " << arg2 << endl;
+    LOG(INFO) << "arg2 = " << arg2;
         typedef void* (*func_type_socket)(...);
     *result = const_cast<void*>(reinterpret_cast<const void*>(    ((func_type_socket) target_loader_.GetLoaderFunction("socket"))(
           arg0,
@@ -31,11 +31,11 @@
       }
     if (!strcmp(func_name, "accept")) {
         int32_t arg0 = (func_msg->arg(0).type() == TYPE_SCALAR && func_msg->arg(0).scalar_value().has_int32_t()) ? func_msg->arg(0).scalar_value().int32_t() : RandomInt32();
-        cout << "arg0 = " << arg0 << endl;
+    LOG(INFO) << "arg0 = " << arg0;
         struct sockaddr* arg1 = (struct sockaddr*) malloc(sizeof(struct sockaddr));
-        cout << "arg1 = " << arg1 << endl;
+    LOG(INFO) << "arg1 = " << arg1;
         socklen_t* arg2 = (socklen_t*) malloc(sizeof(socklen_t));
-        cout << "arg2 = " << arg2 << endl;
+    LOG(INFO) << "arg2 = " << arg2;
         typedef void* (*func_type_accept)(...);
     *result = const_cast<void*>(reinterpret_cast<const void*>(    ((func_type_accept) target_loader_.GetLoaderFunction("accept"))(
           arg0,
@@ -45,11 +45,11 @@
       }
     if (!strcmp(func_name, "bind")) {
         int32_t arg0 = (func_msg->arg(0).type() == TYPE_SCALAR && func_msg->arg(0).scalar_value().has_int32_t()) ? func_msg->arg(0).scalar_value().int32_t() : RandomInt32();
-        cout << "arg0 = " << arg0 << endl;
+    LOG(INFO) << "arg0 = " << arg0;
         struct sockaddr* arg1 = (struct sockaddr*) malloc(sizeof(struct sockaddr));
-        cout << "arg1 = " << arg1 << endl;
+    LOG(INFO) << "arg1 = " << arg1;
         socklen_t* arg2 = (socklen_t*) malloc(sizeof(socklen_t));
-        cout << "arg2 = " << arg2 << endl;
+    LOG(INFO) << "arg2 = " << arg2;
         typedef void* (*func_type_bind)(...);
     *result = const_cast<void*>(reinterpret_cast<const void*>(    ((func_type_bind) target_loader_.GetLoaderFunction("bind"))(
           arg0,
@@ -59,11 +59,11 @@
       }
     if (!strcmp(func_name, "connect")) {
         int32_t arg0 = (func_msg->arg(0).type() == TYPE_SCALAR && func_msg->arg(0).scalar_value().has_int32_t()) ? func_msg->arg(0).scalar_value().int32_t() : RandomInt32();
-        cout << "arg0 = " << arg0 << endl;
+    LOG(INFO) << "arg0 = " << arg0;
         struct sockaddr* arg1 = (struct sockaddr*) malloc(sizeof(struct sockaddr));
-        cout << "arg1 = " << arg1 << endl;
+    LOG(INFO) << "arg1 = " << arg1;
         socklen_t* arg2 = (socklen_t*) malloc(sizeof(socklen_t));
-        cout << "arg2 = " << arg2 << endl;
+    LOG(INFO) << "arg2 = " << arg2;
         typedef void* (*func_type_connect)(...);
     *result = const_cast<void*>(reinterpret_cast<const void*>(    ((func_type_connect) target_loader_.GetLoaderFunction("connect"))(
           arg0,
@@ -73,9 +73,9 @@
       }
     if (!strcmp(func_name, "listen")) {
         int32_t arg0 = (func_msg->arg(0).type() == TYPE_SCALAR && func_msg->arg(0).scalar_value().has_int32_t()) ? func_msg->arg(0).scalar_value().int32_t() : RandomInt32();
-        cout << "arg0 = " << arg0 << endl;
+    LOG(INFO) << "arg0 = " << arg0;
         int32_t arg1 = (func_msg->arg(1).type() == TYPE_SCALAR && func_msg->arg(1).scalar_value().has_int32_t()) ? func_msg->arg(1).scalar_value().int32_t() : RandomInt32();
-        cout << "arg1 = " << arg1 << endl;
+    LOG(INFO) << "arg1 = " << arg1;
         typedef void* (*func_type_listen)(...);
     *result = const_cast<void*>(reinterpret_cast<const void*>(    ((func_type_listen) target_loader_.GetLoaderFunction("listen"))(
           arg0,
@@ -84,13 +84,13 @@
       }
     if (!strcmp(func_name, "recv")) {
         int32_t arg0 = (func_msg->arg(0).type() == TYPE_SCALAR && func_msg->arg(0).scalar_value().has_int32_t()) ? func_msg->arg(0).scalar_value().int32_t() : RandomInt32();
-        cout << "arg0 = " << arg0 << endl;
+    LOG(INFO) << "arg0 = " << arg0;
         void* arg1 = (func_msg->arg(1).type() == TYPE_SCALAR && func_msg->arg(1).scalar_value().has_void_pointer()) ? reinterpret_cast<void*>(func_msg->arg(1).scalar_value().void_pointer()) : RandomVoidPointer();
-        cout << "arg1 = " << arg1 << endl;
+    LOG(INFO) << "arg1 = " << arg1;
         uint32_t arg2 = (func_msg->arg(2).type() == TYPE_SCALAR && func_msg->arg(2).scalar_value().has_uint32_t()) ? func_msg->arg(2).scalar_value().uint32_t() : RandomUint32();
-        cout << "arg2 = " << arg2 << endl;
+    LOG(INFO) << "arg2 = " << arg2;
         int32_t arg3 = (func_msg->arg(3).type() == TYPE_SCALAR && func_msg->arg(3).scalar_value().has_int32_t()) ? func_msg->arg(3).scalar_value().int32_t() : RandomInt32();
-        cout << "arg3 = " << arg3 << endl;
+    LOG(INFO) << "arg3 = " << arg3;
         typedef void* (*func_type_recv)(...);
     *result = const_cast<void*>(reinterpret_cast<const void*>(    ((func_type_recv) target_loader_.GetLoaderFunction("recv"))(
           arg0,
@@ -101,13 +101,13 @@
       }
     if (!strcmp(func_name, "send")) {
         int32_t arg0 = (func_msg->arg(0).type() == TYPE_SCALAR && func_msg->arg(0).scalar_value().has_int32_t()) ? func_msg->arg(0).scalar_value().int32_t() : RandomInt32();
-        cout << "arg0 = " << arg0 << endl;
+    LOG(INFO) << "arg0 = " << arg0;
         void* arg1 = (func_msg->arg(1).type() == TYPE_SCALAR && func_msg->arg(1).scalar_value().has_void_pointer()) ? reinterpret_cast<void*>(func_msg->arg(1).scalar_value().void_pointer()) : RandomVoidPointer();
-        cout << "arg1 = " << arg1 << endl;
+    LOG(INFO) << "arg1 = " << arg1;
         uint32_t arg2 = (func_msg->arg(2).type() == TYPE_SCALAR && func_msg->arg(2).scalar_value().has_uint32_t()) ? func_msg->arg(2).scalar_value().uint32_t() : RandomUint32();
-        cout << "arg2 = " << arg2 << endl;
+    LOG(INFO) << "arg2 = " << arg2;
         int32_t arg3 = (func_msg->arg(3).type() == TYPE_SCALAR && func_msg->arg(3).scalar_value().has_int32_t()) ? func_msg->arg(3).scalar_value().int32_t() : RandomInt32();
-        cout << "arg3 = " << arg3 << endl;
+    LOG(INFO) << "arg3 = " << arg3;
         typedef void* (*func_type_send)(...);
     *result = const_cast<void*>(reinterpret_cast<const void*>(    ((func_type_send) target_loader_.GetLoaderFunction("send"))(
           arg0,
@@ -124,7 +124,7 @@
        strcpy(arg0, RandomCharPointer());
         }
     ;
-        cout << "arg0 = " << arg0 << endl;
+    LOG(INFO) << "arg0 = " << arg0;
         char arg1[func_msg->arg(1).string_value().length() + 1];
         if (func_msg->arg(1).type() == TYPE_SCALAR && func_msg->arg(1).string_value().has_message()) {
           strcpy(arg1, func_msg->arg(1).string_value().message().c_str());
@@ -132,7 +132,7 @@
        strcpy(arg1, RandomCharPointer());
         }
     ;
-        cout << "arg1 = " << arg1 << endl;
+    LOG(INFO) << "arg1 = " << arg1;
         typedef void* (*func_type_fopen)(...);
     *result = const_cast<void*>(reinterpret_cast<const void*>(    ((func_type_fopen) target_loader_.GetLoaderFunction("fopen"))(
           arg0,
@@ -141,11 +141,11 @@
       }
     if (!strcmp(func_name, "read")) {
         int32_t arg0 = (func_msg->arg(0).type() == TYPE_SCALAR && func_msg->arg(0).scalar_value().has_int32_t()) ? func_msg->arg(0).scalar_value().int32_t() : RandomInt32();
-        cout << "arg0 = " << arg0 << endl;
+    LOG(INFO) << "arg0 = " << arg0;
         void* arg1 = (func_msg->arg(1).type() == TYPE_SCALAR && func_msg->arg(1).scalar_value().has_void_pointer()) ? reinterpret_cast<void*>(func_msg->arg(1).scalar_value().void_pointer()) : RandomVoidPointer();
-        cout << "arg1 = " << arg1 << endl;
+    LOG(INFO) << "arg1 = " << arg1;
         uint32_t arg2 = (func_msg->arg(2).type() == TYPE_SCALAR && func_msg->arg(2).scalar_value().has_uint32_t()) ? func_msg->arg(2).scalar_value().uint32_t() : RandomUint32();
-        cout << "arg2 = " << arg2 << endl;
+    LOG(INFO) << "arg2 = " << arg2;
         typedef void* (*func_type_read)(...);
     *result = const_cast<void*>(reinterpret_cast<const void*>(    ((func_type_read) target_loader_.GetLoaderFunction("read"))(
           arg0,
@@ -155,11 +155,11 @@
       }
     if (!strcmp(func_name, "write")) {
         int32_t arg0 = (func_msg->arg(0).type() == TYPE_SCALAR && func_msg->arg(0).scalar_value().has_int32_t()) ? func_msg->arg(0).scalar_value().int32_t() : RandomInt32();
-        cout << "arg0 = " << arg0 << endl;
+    LOG(INFO) << "arg0 = " << arg0;
         void* arg1 = (func_msg->arg(1).type() == TYPE_SCALAR && func_msg->arg(1).scalar_value().has_void_pointer()) ? reinterpret_cast<void*>(func_msg->arg(1).scalar_value().void_pointer()) : RandomVoidPointer();
-        cout << "arg1 = " << arg1 << endl;
+    LOG(INFO) << "arg1 = " << arg1;
         int32_t arg2 = (func_msg->arg(2).type() == TYPE_SCALAR && func_msg->arg(2).scalar_value().has_int32_t()) ? func_msg->arg(2).scalar_value().int32_t() : RandomInt32();
-        cout << "arg2 = " << arg2 << endl;
+    LOG(INFO) << "arg2 = " << arg2;
         typedef void* (*func_type_write)(...);
     *result = const_cast<void*>(reinterpret_cast<const void*>(    ((func_type_write) target_loader_.GetLoaderFunction("write"))(
           arg0,
@@ -169,11 +169,11 @@
       }
     if (!strcmp(func_name, "lseek")) {
         int32_t arg0 = (func_msg->arg(0).type() == TYPE_SCALAR && func_msg->arg(0).scalar_value().has_int32_t()) ? func_msg->arg(0).scalar_value().int32_t() : RandomInt32();
-        cout << "arg0 = " << arg0 << endl;
+    LOG(INFO) << "arg0 = " << arg0;
         int32_t arg1 = (func_msg->arg(1).type() == TYPE_SCALAR && func_msg->arg(1).scalar_value().has_int32_t()) ? func_msg->arg(1).scalar_value().int32_t() : RandomInt32();
-        cout << "arg1 = " << arg1 << endl;
+    LOG(INFO) << "arg1 = " << arg1;
         int32_t arg2 = (func_msg->arg(2).type() == TYPE_SCALAR && func_msg->arg(2).scalar_value().has_int32_t()) ? func_msg->arg(2).scalar_value().int32_t() : RandomInt32();
-        cout << "arg2 = " << arg2 << endl;
+    LOG(INFO) << "arg2 = " << arg2;
         typedef void* (*func_type_lseek)(...);
     *result = const_cast<void*>(reinterpret_cast<const void*>(    ((func_type_lseek) target_loader_.GetLoaderFunction("lseek"))(
           arg0,
@@ -183,7 +183,7 @@
       }
     if (!strcmp(func_name, "close")) {
         int32_t arg0 = (func_msg->arg(0).type() == TYPE_SCALAR && func_msg->arg(0).scalar_value().has_int32_t()) ? func_msg->arg(0).scalar_value().int32_t() : RandomInt32();
-        cout << "arg0 = " << arg0 << endl;
+    LOG(INFO) << "arg0 = " << arg0;
         typedef void* (*func_type_close)(...);
     *result = const_cast<void*>(reinterpret_cast<const void*>(    ((func_type_close) target_loader_.GetLoaderFunction("close"))(
           arg0)));
@@ -195,8 +195,8 @@
     FunctionSpecificationMessage* func_msg,
     void** result) {
     const char* func_name = func_msg->name().c_str();
-    cout << "Function: " << __func__ << " '" << func_name << "'" << endl;
-    cerr << "attribute not supported for shared lib yet" << endl;
+    LOG(INFO) << " '" << func_name << "'";
+    LOG(ERROR) << "attribute not supported for shared lib yet.";
     return false;
 }
 bool FuzzerExtended_libc::CallFunction(const FunctionSpecificationMessage&, const string&, FunctionSpecificationMessage* ) {
diff --git a/compilation_tools/vtsc/test/golden/DRIVER/test/vts/specification/lib/ndk/bionic/1.0/libcV1.vts.h b/compilation_tools/vtsc/test/golden/DRIVER/test/vts/specification/lib/ndk/bionic/1.0/libcV1.vts.h
index b4bd18f..92fce14 100644
--- a/compilation_tools/vtsc/test/golden/DRIVER/test/vts/specification/lib/ndk/bionic/1.0/libcV1.vts.h
+++ b/compilation_tools/vtsc/test/golden/DRIVER/test/vts/specification/lib/ndk/bionic/1.0/libcV1.vts.h
@@ -8,11 +8,11 @@
 #include <sys/types.h>
 #include <linux/socket.h>
 
-#include <stdio.h>
+#include <log/log.h>
 #include <stdarg.h>
+#include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
-#include <utils/Log.h>
 
 #include <driver_base/DriverBase.h>
 #include <driver_base/DriverCallbackBase.h>
diff --git a/compilation_tools/vtsc/test/golden/DRIVER/types.vts.h b/compilation_tools/vtsc/test/golden/DRIVER/types.vts.h
index 18593c5..aa2fe29 100644
--- a/compilation_tools/vtsc/test/golden/DRIVER/types.vts.h
+++ b/compilation_tools/vtsc/test/golden/DRIVER/types.vts.h
@@ -4,11 +4,11 @@
 #undef LOG_TAG
 #define LOG_TAG "FuzzerExtended_android_hardware_nfc_V1_0_types"
 
-#include <stdio.h>
+#include <log/log.h>
 #include <stdarg.h>
+#include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
-#include <utils/Log.h>
 
 #include <driver_base/DriverBase.h>
 #include <driver_base/DriverCallbackBase.h>
diff --git a/compilation_tools/vtsc/test/golden/PROFILER/Bar.profiler.cpp b/compilation_tools/vtsc/test/golden/PROFILER/Bar.profiler.cpp
index bf1e490..981e5b2 100644
--- a/compilation_tools/vtsc/test/golden/PROFILER/Bar.profiler.cpp
+++ b/compilation_tools/vtsc/test/golden/PROFILER/Bar.profiler.cpp
@@ -31,8 +31,11 @@
         LOG(WARNING) << "incorrect package. Expect: android.hardware.tests.bar actual: " << package;
         return;
     }
-    if (strcmp(version, "1.0") != 0) {
-        LOG(WARNING) << "incorrect version. Expect: 1.0 actual: " << interface;
+    std::string version_str = std::string(version);
+    int major_version = stoi(version_str.substr(0, version_str.find('.')));
+    int minor_version = stoi(version_str.substr(version_str.find('.') + 1));
+    if (major_version != 1 || minor_version > 0) {
+        LOG(WARNING) << "incorrect version. Expect: 1.0 or lower (if version != x.0), actual: " << version;
         return;
     }
     if (strcmp(interface, "IBar") != 0) {
@@ -42,6 +45,76 @@
 
     VtsProfilingInterface& profiler = VtsProfilingInterface::getInstance(TRACEFILEPREFIX);
 
+    if (strcmp(method, "convertToBoolIfSmall") == 0) {
+        FunctionSpecificationMessage msg;
+        msg.set_name("convertToBoolIfSmall");
+        if (!args) {
+            LOG(WARNING) << "no argument passed";
+        } else {
+            switch (event) {
+                case details::HidlInstrumentor::CLIENT_API_ENTRY:
+                case details::HidlInstrumentor::SERVER_API_ENTRY:
+                case details::HidlInstrumentor::PASSTHROUGH_ENTRY:
+                {
+                    if ((*args).size() != 2) {
+                        LOG(ERROR) << "Number of arguments does not match. expect: 2, actual: " << (*args).size() << ", method name: convertToBoolIfSmall, event type: " << event;
+                        break;
+                    }
+                    auto *arg_0 __attribute__((__unused__)) = msg.add_arg();
+                    ::android::hardware::tests::foo::V1_0::IFoo::Discriminator *arg_val_0 __attribute__((__unused__)) = reinterpret_cast<::android::hardware::tests::foo::V1_0::IFoo::Discriminator*> ((*args)[0]);
+                    if (arg_val_0 != nullptr) {
+                        arg_0->set_type(TYPE_ENUM);
+                        profile____android__hardware__tests__foo__V1_0__IFoo__Discriminator(arg_0, (*arg_val_0));
+                    } else {
+                        LOG(WARNING) << "argument 0 is null.";
+                    }
+                    auto *arg_1 __attribute__((__unused__)) = msg.add_arg();
+                    ::android::hardware::hidl_vec<::android::hardware::tests::foo::V1_0::IFoo::Union> *arg_val_1 __attribute__((__unused__)) = reinterpret_cast<::android::hardware::hidl_vec<::android::hardware::tests::foo::V1_0::IFoo::Union>*> ((*args)[1]);
+                    if (arg_val_1 != nullptr) {
+                        arg_1->set_type(TYPE_VECTOR);
+                        arg_1->set_vector_size((*arg_val_1).size());
+                        for (int arg_1_index = 0; arg_1_index < (int)(*arg_val_1).size(); arg_1_index++) {
+                            auto *arg_1_vector_arg_1_index __attribute__((__unused__)) = arg_1->add_vector_value();
+                            arg_1_vector_arg_1_index->set_type(TYPE_UNION);
+                            profile____android__hardware__tests__foo__V1_0__IFoo__Union(arg_1_vector_arg_1_index, (*arg_val_1)[arg_1_index]);
+                        }
+                    } else {
+                        LOG(WARNING) << "argument 1 is null.";
+                    }
+                    break;
+                }
+                case details::HidlInstrumentor::CLIENT_API_EXIT:
+                case details::HidlInstrumentor::SERVER_API_EXIT:
+                case details::HidlInstrumentor::PASSTHROUGH_EXIT:
+                {
+                    if ((*args).size() != 1) {
+                        LOG(ERROR) << "Number of return values does not match. expect: 1, actual: " << (*args).size() << ", method name: convertToBoolIfSmall, event type: " << event;
+                        break;
+                    }
+                    auto *result_0 __attribute__((__unused__)) = msg.add_return_type_hidl();
+                    ::android::hardware::hidl_vec<::android::hardware::tests::foo::V1_0::IFoo::ContainsUnion> *result_val_0 __attribute__((__unused__)) = reinterpret_cast<::android::hardware::hidl_vec<::android::hardware::tests::foo::V1_0::IFoo::ContainsUnion>*> ((*args)[0]);
+                    if (result_val_0 != nullptr) {
+                        result_0->set_type(TYPE_VECTOR);
+                        result_0->set_vector_size((*result_val_0).size());
+                        for (int result_0_index = 0; result_0_index < (int)(*result_val_0).size(); result_0_index++) {
+                            auto *result_0_vector_result_0_index __attribute__((__unused__)) = result_0->add_vector_value();
+                            result_0_vector_result_0_index->set_type(TYPE_STRUCT);
+                            profile____android__hardware__tests__foo__V1_0__IFoo__ContainsUnion(result_0_vector_result_0_index, (*result_val_0)[result_0_index]);
+                        }
+                    } else {
+                        LOG(WARNING) << "return value 0 is null.";
+                    }
+                    break;
+                }
+                default:
+                {
+                    LOG(WARNING) << "not supported. ";
+                    break;
+                }
+            }
+        }
+        profiler.AddTraceEvent(event, package, version, interface, msg);
+    }
     if (strcmp(method, "doThis") == 0) {
         FunctionSpecificationMessage msg;
         msg.set_name("doThis");
diff --git a/compilation_tools/vtsc/test/golden/PROFILER/MemoryTest.profiler.cpp b/compilation_tools/vtsc/test/golden/PROFILER/MemoryTest.profiler.cpp
index be28232..62c0fef 100644
--- a/compilation_tools/vtsc/test/golden/PROFILER/MemoryTest.profiler.cpp
+++ b/compilation_tools/vtsc/test/golden/PROFILER/MemoryTest.profiler.cpp
@@ -23,8 +23,11 @@
         LOG(WARNING) << "incorrect package. Expect: android.hardware.tests.memory actual: " << package;
         return;
     }
-    if (strcmp(version, "1.0") != 0) {
-        LOG(WARNING) << "incorrect version. Expect: 1.0 actual: " << interface;
+    std::string version_str = std::string(version);
+    int major_version = stoi(version_str.substr(0, version_str.find('.')));
+    int minor_version = stoi(version_str.substr(version_str.find('.') + 1));
+    if (major_version != 1 || minor_version > 0) {
+        LOG(WARNING) << "incorrect version. Expect: 1.0 or lower (if version != x.0), actual: " << version;
         return;
     }
     if (strcmp(interface, "IMemoryTest") != 0) {
diff --git a/compilation_tools/vtsc/test/golden/PROFILER/Nfc.profiler.cpp b/compilation_tools/vtsc/test/golden/PROFILER/Nfc.profiler.cpp
index cc5e400..7234a53 100644
--- a/compilation_tools/vtsc/test/golden/PROFILER/Nfc.profiler.cpp
+++ b/compilation_tools/vtsc/test/golden/PROFILER/Nfc.profiler.cpp
@@ -23,8 +23,11 @@
         LOG(WARNING) << "incorrect package. Expect: android.hardware.nfc actual: " << package;
         return;
     }
-    if (strcmp(version, "1.0") != 0) {
-        LOG(WARNING) << "incorrect version. Expect: 1.0 actual: " << interface;
+    std::string version_str = std::string(version);
+    int major_version = stoi(version_str.substr(0, version_str.find('.')));
+    int minor_version = stoi(version_str.substr(version_str.find('.') + 1));
+    if (major_version != 1 || minor_version > 0) {
+        LOG(WARNING) << "incorrect version. Expect: 1.0 or lower (if version != x.0), actual: " << version;
         return;
     }
     if (strcmp(interface, "INfc") != 0) {
diff --git a/compilation_tools/vtsc/test/golden/PROFILER/NfcClientCallback.profiler.cpp b/compilation_tools/vtsc/test/golden/PROFILER/NfcClientCallback.profiler.cpp
index 61ceb04..00c71f5 100644
--- a/compilation_tools/vtsc/test/golden/PROFILER/NfcClientCallback.profiler.cpp
+++ b/compilation_tools/vtsc/test/golden/PROFILER/NfcClientCallback.profiler.cpp
@@ -23,8 +23,11 @@
         LOG(WARNING) << "incorrect package. Expect: android.hardware.nfc actual: " << package;
         return;
     }
-    if (strcmp(version, "1.0") != 0) {
-        LOG(WARNING) << "incorrect version. Expect: 1.0 actual: " << interface;
+    std::string version_str = std::string(version);
+    int major_version = stoi(version_str.substr(0, version_str.find('.')));
+    int minor_version = stoi(version_str.substr(version_str.find('.') + 1));
+    if (major_version != 1 || minor_version > 0) {
+        LOG(WARNING) << "incorrect version. Expect: 1.0 or lower (if version != x.0), actual: " << version;
         return;
     }
     if (strcmp(interface, "INfcClientCallback") != 0) {
diff --git a/compilation_tools/vtsc/test/golden/PROFILER/TestMsgQ.profiler.cpp b/compilation_tools/vtsc/test/golden/PROFILER/TestMsgQ.profiler.cpp
index ff97cd0..016b2c1 100644
--- a/compilation_tools/vtsc/test/golden/PROFILER/TestMsgQ.profiler.cpp
+++ b/compilation_tools/vtsc/test/golden/PROFILER/TestMsgQ.profiler.cpp
@@ -30,8 +30,11 @@
         LOG(WARNING) << "incorrect package. Expect: android.hardware.tests.msgq actual: " << package;
         return;
     }
-    if (strcmp(version, "1.0") != 0) {
-        LOG(WARNING) << "incorrect version. Expect: 1.0 actual: " << interface;
+    std::string version_str = std::string(version);
+    int major_version = stoi(version_str.substr(0, version_str.find('.')));
+    int minor_version = stoi(version_str.substr(version_str.find('.') + 1));
+    if (major_version != 1 || minor_version > 0) {
+        LOG(WARNING) << "incorrect version. Expect: 1.0 or lower (if version != x.0), actual: " << version;
         return;
     }
     if (strcmp(interface, "ITestMsgQ") != 0) {
diff --git a/drivers/hal/common/binder/VtsFuzzerBinderService.cpp b/drivers/hal/common/binder/VtsFuzzerBinderService.cpp
index 283ea8d..3566f5e 100644
--- a/drivers/hal/common/binder/VtsFuzzerBinderService.cpp
+++ b/drivers/hal/common/binder/VtsFuzzerBinderService.cpp
@@ -19,9 +19,9 @@
 #include <iostream>
 #include <string>
 
-#include <utils/RefBase.h>
 #define LOG_TAG "VtsFuzzerBinderService"
-#include <utils/Log.h>
+#include <log/log.h>
+#include <utils/RefBase.h>
 
 #include <binder/IBinder.h>
 #include <binder/IInterface.h>
diff --git a/drivers/hal/server/BinderServer.cpp b/drivers/hal/server/BinderServer.cpp
index 2a1bebc..a616db4 100644
--- a/drivers/hal/server/BinderServer.cpp
+++ b/drivers/hal/server/BinderServer.cpp
@@ -24,9 +24,9 @@
 #include <iostream>
 #include <string>
 
-#include <utils/RefBase.h>
 #define LOG_TAG "VtsFuzzerBinderServer"
-#include <utils/Log.h>
+#include <log/log.h>
+#include <utils/RefBase.h>
 #include <utils/String8.h>
 
 #include <binder/IBinder.h>
diff --git a/hals/light/bullhead/lights.c b/hals/light/bullhead/lights.c
index 8a10ea4..dde468b 100644
--- a/hals/light/bullhead/lights.c
+++ b/hals/light/bullhead/lights.c
@@ -17,7 +17,7 @@
 #define LOG_TAG "lights"
 #define DEBUG 1
 
-#include <cutils/log.h>
+#include <log/log.h>
 
 #include <malloc.h>
 #include <stdint.h>
diff --git a/harnesses/host_controller/build/README.md b/harnesses/host_controller/build/README.md
deleted file mode 100644
index 845bd85..0000000
--- a/harnesses/host_controller/build/README.md
+++ /dev/null
@@ -1,12 +0,0 @@
-# To set up client_secrets.json (once only)
-
-* Go to https://console.cloud.google.com/projectselector/apis/credentials/consent
- and create a new project if needed.
-* Once on the consent screen, set the product name to anything and save.
-* Click "Create credentials" and select "OAuth client ID"
-* Under "Application type", select "Other". Click "Create".
-* Click the download button for the client you just created,
- and save the resulting file at client_secrets.json
-
-# Running unit tests
-    python pab_client_test.py
\ No newline at end of file
diff --git a/harnesses/host_controller/build/__init__.py b/harnesses/host_controller/build/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/harnesses/host_controller/build/__init__.py
+++ /dev/null
diff --git a/harnesses/host_controller/build/build_flasher.py b/harnesses/host_controller/build/build_flasher.py
deleted file mode 100644
index 6ad77d5..0000000
--- a/harnesses/host_controller/build/build_flasher.py
+++ /dev/null
@@ -1,166 +0,0 @@
-#
-# Copyright (C) 2017 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-"""Class to flash build artifacts onto devices"""
-
-import logging
-import os
-
-from vts.harnesses.host_controller.build import build_provider
-from vts.utils.python.controllers import android_device
-
-
-class BuildFlasher(object):
-    """Client that manages build flashing.
-
-    Attributes:
-        device: AndroidDevice, the device associated with the client.
-    """
-
-    def __init__(self, serial=""):
-        """Initialize the client.
-
-        If serial not provided, find single device connected. Error if
-        zero or > 1 devices connected.
-
-        Args:
-            serial: optional string, serial number for the device.
-        """
-        if serial != "":
-            self.device = android_device.AndroidDevice(serial)
-        else:
-            serials = android_device.list_adb_devices()
-            if len(serials) == 0:
-                serials = android_device.list_fastboot_devices()
-                if len(serials) == 0:
-                    raise android_device.AndroidDeviceError(
-                        "ADB and fastboot could not find any target devices.")
-            if len(serials) > 1:
-                print("ADB or fastboot found more than one device: %s" % serials)
-            self.device = android_device.AndroidDevice(
-                serials[0], device_callback_port=-1)
-
-    def SetSerial(self, serial):
-        """Sets device serial.
-
-        Args:
-            serial: string, a device serial.
-
-        Returns:
-            True if successful; False otherwise.
-        """
-        if not serial:
-            print("no serial is given to BuildFlasher.SetSerial.")
-            return False
-
-        self.device = android_device.AndroidDevice(serial)
-        return True
-
-    def FlashGSI(self, system_img, vbmeta_img=None, skip_check=False):
-        """Flash the Generic System Image to the device.
-
-        Args:
-            system_img: string, path to GSI
-            vbmeta_img: string, optional, path to vbmeta image for new devices
-            skip_check: boolean, set True to skip adb-based checks when
-                        the DUT is already running its bootloader.
-        """
-        if not os.path.exists(system_img):
-            raise ValueError("Couldn't find system image at %s" % system_img)
-        if not skip_check:
-            self.device.adb.wait_for_device()
-            if not self.device.isBootloaderMode:
-                self.device.log.info(self.device.adb.reboot_bootloader())
-        if vbmeta_img is not None:
-            self.device.log.info(self.device.fastboot.flash(
-                'vbmeta', vbmeta_img))
-        self.device.log.info(self.device.fastboot.erase('system'))
-        self.device.log.info(self.device.fastboot.flash('system', system_img))
-        self.device.log.info(self.device.fastboot.erase('metadata'))
-        self.device.log.info(self.device.fastboot._w())
-        self.device.log.info(self.device.fastboot.reboot())
-
-    def Flashall(self, directory):
-        """Flash all images in a directory to the device using flashall.
-
-        Generally the directory is the result of unzipping the .zip from AB.
-        Args:
-            directory: string, path to directory containing images
-        """
-        # fastboot flashall looks for imgs in $ANDROID_PRODUCT_OUT
-        os.environ['ANDROID_PRODUCT_OUT'] = directory
-        self.device.adb.wait_for_device()
-        if not self.device.isBootloaderMode:
-            self.device.log.info(self.device.adb.reboot_bootloader())
-        self.device.log.info(self.device.fastboot.flashall())
-
-    def Flash(self, device_images):
-        """Flash the Generic System Image to the device.
-
-        Args:
-            device_images: dict, where the key is partition name and value is
-                           image file path.
-
-        Returns:
-            True if succesful; False otherwise
-        """
-        if not device_images:
-            logging.warn("Flash skipped because no device image is given.")
-            return False
-
-        if not self.device.isBootloaderMode:
-            self.device.adb.wait_for_device()
-            print("rebooting to bootloader")
-            self.device.log.info(self.device.adb.reboot_bootloader())
-
-        print("starting to flash vendor and other images...")
-        if build_provider.FULL_ZIPFILE in device_images:
-            print("fastboot update %s --skip-reboot" % (
-                  device_images[build_provider.FULL_ZIPFILE]))
-            self.device.log.info(self.device.fastboot.update(
-                device_images[build_provider.FULL_ZIPFILE],
-                "--skip-reboot"))
-
-        for partition, image_path in device_images.iteritems():
-            if partition in (build_provider.FULL_ZIPFILE, "system", "vbmeta"):
-                continue
-            if not image_path:
-                self.device.log.warning("%s image is empty", partition)
-                continue
-            self.device.log.info("fastboot flash %s %s", partition, image_path)
-            self.device.log.info(
-                self.device.fastboot.flash(partition, image_path))
-
-        print("starting to flash system and other images...")
-        if "system" in device_images and device_images["system"]:
-            system_img = device_images["system"]
-            vbmeta_img = device_images["vbmeta"] if (
-                "vbmeta" in device_images and device_images["vbmeta"]) else None
-            self.FlashGSI(system_img, vbmeta_img, skip_check=True)
-        else:
-            self.device.log.info(self.device.fastboot.reboot())
-        return True
-
-    def WaitForDevice(self, timeout_secs=600):
-        """Waits for the device to boot completely.
-
-        Args:
-            timeout_secs: integer, the maximum timeout value for this
-                          operation (unit: seconds).
-
-        Returns:
-            True if device is booted successfully; False otherwise.
-        """
-        return self.device.waitForBootCompletion(timeout=timeout_secs)
diff --git a/harnesses/host_controller/build/build_flasher_test.py b/harnesses/host_controller/build/build_flasher_test.py
deleted file mode 100644
index 66b4d86..0000000
--- a/harnesses/host_controller/build/build_flasher_test.py
+++ /dev/null
@@ -1,73 +0,0 @@
-#!/usr/bin/env python
-#
-# Copyright (C) 2017 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-import unittest
-from vts.harnesses.host_controller.build import build_flasher
-
-try:
-    from unittest import mock
-except ImportError:
-    import mock
-
-
-class BuildFlasherTest(unittest.TestCase):
-    """Tests for Build Flasher"""
-
-    @mock.patch(
-        "vts.harnesses.host_controller.build.build_flasher.android_device")
-    @mock.patch("vts.harnesses.host_controller.build.build_flasher.os")
-    def testFlashGSIBadPath(self, mock_os, mock_class):
-        flasher = build_flasher.BuildFlasher("thisismyserial")
-        mock_os.path.exists.return_value = False
-        with self.assertRaises(ValueError) as cm:
-            flasher.FlashGSI("notexists.img")
-        self.assertEqual("Couldn't find system image at notexists.img",
-                         str(cm.exception))
-
-    @mock.patch(
-        "vts.harnesses.host_controller.build.build_flasher.android_device")
-    @mock.patch("vts.harnesses.host_controller.build.build_flasher.os")
-    def testFlashGSISystemOnly(self, mock_os, mock_class):
-        mock_device = mock.Mock()
-        mock_class.AndroidDevice.return_value = mock_device
-        flasher = build_flasher.BuildFlasher("thisismyserial")
-        mock_os.path.exists.return_value = True
-        flasher.FlashGSI("exists.img")
-        mock_device.fastboot.erase.assert_any_call('system')
-        mock_device.fastboot.flash.assert_any_call('system', 'exists.img')
-        mock_device.fastboot.erase.assert_any_call('metadata')
-
-    @mock.patch(
-        "vts.harnesses.host_controller.build.build_flasher.android_device")
-    def testFlashall(self, mock_class):
-        mock_device = mock.Mock()
-        mock_class.AndroidDevice.return_value = mock_device
-        flasher = build_flasher.BuildFlasher("thisismyserial")
-        flasher.Flashall("path/to/dir")
-        mock_device.fastboot.flashall.assert_called_with()
-
-    @mock.patch(
-        "vts.harnesses.host_controller.build.build_flasher.android_device")
-    def testEmptySerial(self, mock_class):
-        mock_class.list_adb_devices.return_value = ['oneserial']
-        flasher = build_flasher.BuildFlasher(serial="")
-        mock_class.AndroidDevice.assert_called_with("oneserial",
-                                                    device_callback_port=-1)
-
-
-if __name__ == "__main__":
-    unittest.main()
diff --git a/harnesses/host_controller/build/build_provider.py b/harnesses/host_controller/build/build_provider.py
deleted file mode 100644
index 012d2e5..0000000
--- a/harnesses/host_controller/build/build_provider.py
+++ /dev/null
@@ -1,154 +0,0 @@
-#
-# Copyright (C) 2017 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-import os
-import shutil
-import tempfile
-import zipfile
-
-from vts.runners.host import utils
-
-FULL_ZIPFILE = "full-zipfile"
-
-
-class BuildProvider(object):
-    """The base class for build provider.
-
-    Attributes:
-        _IMAGE_FILE_EXTENSIONS: a list of strings which are common image file
-                                extensions.
-        _BASIC_IMAGE_FILE_NAMES: a list of strings which are the image names in
-                                 an artifact zip.
-        _device_images: dict where the key is image file name and value is the
-                        path.
-        _test_suites: dict where the key is test suite type and value is the
-                      test suite package file path.
-        _tmp_dirpath: string, the temp dir path created to keep artifacts.
-    """
-    _IMAGE_FILE_EXTENSIONS = [".img", ".bin"]
-    _BASIC_IMAGE_FILE_NAMES = ["boot.img", "system.img", "vendor.img"]
-
-    def __init__(self):
-        self._device_images = {}
-        self._test_suites = {}
-        tempdir_base = os.path.join(os.getcwd(), "tmp")
-        if not os.path.exists(tempdir_base):
-            os.mkdir(tempdir_base)
-        self._tmp_dirpath = tempfile.mkdtemp(dir=tempdir_base)
-
-    def __del__(self):
-        """Deletes the temp dir if still set."""
-        if self._tmp_dirpath:
-            shutil.rmtree(self._tmp_dirpath)
-            self._tmp_dirpath = None
-
-    @property
-    def tmp_dirpath(self):
-        return self._tmp_dirpath
-
-    def CreateNewTmpDir(self):
-        return tempfile.mkdtemp(dir=self._tmp_dirpath)
-
-    def SetDeviceImage(self, name, path):
-        """Sets device image `path` for the specified `name`."""
-        self._device_images[name] = path
-
-    def _IsFullDeviceImage(self, namelist):
-        """Returns true if given namelist list has all common device images."""
-        for image_file in self._BASIC_IMAGE_FILE_NAMES:
-            if image_file not in namelist:
-                return False
-        return True
-
-    def _IsImageFile(self, file_path):
-        """Returns whether a file is an image.
-
-        Args:
-            file_path: string, the file path.
-
-        Returns:
-            boolean, whether the file is an image.
-        """
-        return any(file_path.endswith(ext)
-                   for ext in self._IMAGE_FILE_EXTENSIONS)
-
-    def SetDeviceImagesInDirecotry(self, root_dir):
-        """Sets device images to *.img and *.bin in a directory.
-
-        Args:
-            root_dir: string, the directory to find images in.
-        """
-        for dir_name, file_name in utils.iterate_files(root_dir):
-            if self._IsImageFile(file_name):
-                self.SetDeviceImage(file_name,
-                                    os.path.join(dir_name, file_name))
-
-    def SetDeviceImageZip(self, path):
-        """Sets device image(s) using files in a given zip file.
-
-        It extracts image files inside the given zip file and selects
-        known Android image files.
-
-        Args:
-            path: string, the path to a zip file.
-        """
-        dest_path = path + ".dir"
-        with zipfile.ZipFile(path, 'r') as zip_ref:
-            if self._IsFullDeviceImage(zip_ref.namelist()):
-                self.SetDeviceImage(FULL_ZIPFILE, path)
-            else:
-                zip_ref.extractall(dest_path)
-                self.SetDeviceImagesInDirecotry(dest_path)
-
-    def GetDeviceImage(self, name=None):
-        """Returns device image info."""
-        if name is None:
-            return self._device_images
-        return self._device_images[name]
-
-    def SetTestSuitePackage(self, type, path):
-        """Sets test suite package `path` for the specified `type`.
-
-        Args:
-            type: string, test suite type such as 'vts' or 'cts'.
-            path: string, the path of a file. if a file is a zip file,
-                  it's unziped and its main binary is set.
-        """
-        if path.endswith("android-vts.zip"):
-            dest_path = os.path.join(self.tmp_dirpath, "android-vts")
-            with zipfile.ZipFile(path, 'r') as zip_ref:
-                zip_ref.extractall(dest_path)
-                bin_path = os.path.join(dest_path, "android-vts",
-                                        "tools", "vts-tradefed")
-                os.chmod(bin_path, 0766)
-                path = bin_path
-        else:
-            print("unsupported zip file %s" % path)
-        self._test_suites[type] = path
-
-    def GetTestSuitePackage(self, type=None):
-        """Returns test suite package info."""
-        if type is None:
-            return self._test_suites
-        return self._test_suites[type]
-
-    def PrintDeviceImageInfo(self):
-        """Prints device image info."""
-        print("%s" % self.GetDeviceImage())
-
-    def PrintGetTestSuitePackageInfo(self):
-        """Prints test suite package info."""
-        print("%s" % self.GetTestSuitePackage())
diff --git a/harnesses/host_controller/build/build_provider_ab.py b/harnesses/host_controller/build/build_provider_ab.py
deleted file mode 100644
index b14531b..0000000
--- a/harnesses/host_controller/build/build_provider_ab.py
+++ /dev/null
@@ -1,70 +0,0 @@
-#
-# Copyright (C) 2017 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-import os
-import zipfile
-
-from vts.harnesses.host_controller.build import build_provider
-from vts.utils.python.build.api import artifact_fetcher
-
-
-class BuildProviderAB(build_provider.BuildProvider):
-    """A build provider for Android Build (AB)."""
-
-    def __init__(self):
-        super(BuildProviderAB, self).__init__()
-        if 'run_ab_key' in os.environ:
-            print("For AB, use the key at %s" % os.environ['run_ab_key'])
-            self._artifact_fetcher = artifact_fetcher.AndroidBuildClient(
-                os.environ['run_ab_key'])
-        else:
-            self._artifact_fetcher = None
-
-    def Fetch(self, branch, target, artifact_name, build_id="latest"):
-        """Fetches Android device artifact file(s) from Android Build.
-
-        Args:
-            branch: string, androidbranch to pull resource from .
-            target: string, build target name.
-            artifact_name: string, file name.
-            build_id: string, ID of the build or latest.
-
-        Returns:
-            a dict containing the device image info.
-            a dict containing the test suite package info.
-        """
-        if not self._artifact_fetcher:
-            return self.GetDeviceImage(), self.GetTestSuitePackage()
-
-        if build_id == "latest":
-            recent_build_ids = self._artifact_fetcher.ListBuildIds(
-                branch, target)
-            build_id = recent_build_ids[0]
-
-        if "{build_id}" in artifact_name:
-            artifact_name = artifact_name.replace("{build_id}", build_id)
-
-        dest_filepath = os.path.join(self.tmp_dirpath, artifact_name)
-        self._artifact_fetcher.DownloadArtifactToFile(
-            branch, target, build_id, artifact_name,
-            dest_filepath=dest_filepath)
-
-        if dest_filepath.endswith("android-vts.zip"):
-            self.SetTestSuitePackage("vts", dest_filepath)
-        elif dest_filepath.endswith(".zip"):
-            self.SetDeviceImageZip(dest_filepath)
-
-        return self.GetDeviceImage(), self.GetTestSuitePackage()
diff --git a/harnesses/host_controller/build/build_provider_gcs.py b/harnesses/host_controller/build/build_provider_gcs.py
deleted file mode 100644
index b921edf..0000000
--- a/harnesses/host_controller/build/build_provider_gcs.py
+++ /dev/null
@@ -1,99 +0,0 @@
-#
-# Copyright (C) 2017 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-import os
-import re
-import zipfile
-
-from vts.harnesses.host_controller.build import build_provider
-from vts.utils.python.common import cmd_utils
-
-
-class BuildProviderGCS(build_provider.BuildProvider):
-    """A build provider for GCS (Google Cloud Storage)."""
-
-    def __init__(self):
-        super(BuildProviderGCS, self).__init__()
-
-    @staticmethod
-    def GetGsutilPath():
-        """Returns the gsutil file path if found; None otherwise."""
-        sh_stdout, sh_stderr, ret_code = cmd_utils.ExecuteOneShellCommand(
-            "which gsutil")
-        if ret_code == 0:
-            return sh_stdout.strip()
-        else:
-            logging.fatal("`gsutil` doesn't exist on the host; "
-                          "please install Google Cloud SDK before retrying.")
-            return None
-
-    @staticmethod
-    def IsGcsFile(gsutil_path, gs_path):
-        """Checks whether a given path is for a GCS file.
-
-        Args:
-            gsutil_path: string, the path of a gsutil binary.
-            gs_path: string, the GCS file path (e.g., gs://<bucket>/<file>.
-
-        Returns:
-            True if gs_path is a file, False otherwise.
-        """
-        check_command = "%s stat %s" % (gsutil_path, gs_path)
-        _, _, ret_code = cmd_utils.ExecuteOneShellCommand(
-            check_command)
-        return ret_code == 0
-
-    def Fetch(self, path):
-        """Fetches Android device artifact file(s) from GCS.
-
-        Args:
-            path: string, the path of a directory which keeps artifacts.
-
-        Returns:
-            a dict containing the device image info.
-            a dict containing the test suite package info.
-        """
-        if not path.startswith("gs://"):
-            path = "gs://" + re.sub("^/*", "", path)
-        path = re.sub("/*$", "", path)
-
-        # make sure gsutil is available. Instead of a Python library,
-        # gsutil binary is used that is to avoid packaging GCS PIP package
-        # as part of VTS HC (Host Controller).
-        gsutil_path = BuildProviderGCS.GetGsutilPath()
-        if gsutil_path:
-            temp_dir_path = self.CreateNewTmpDir()
-            if not BuildProviderGCS.IsGcsFile(gsutil_path, path):  # directory (not exist)
-                copy_command = "%s cp -r %s/* %s" % (
-                    gsutil_path, path, temp_dir_path)
-                _, _, ret_code = cmd_utils.ExecuteOneShellCommand(
-                    copy_command)
-                if ret_code == 0:
-                    self.SetDeviceImagesInDirecotry(temp_dir_path)
-                else:
-                    print("Error in copy files from GCS (code %s)." % ret_code)
-            else:
-                copy_command = "%s cp %s %s" % (
-                    gsutil_path, path, temp_dir_path)
-                _, _, ret_code = cmd_utils.ExecuteOneShellCommand(
-                    copy_command)
-                dest_file_path = os.path.join(temp_dir_path,
-                                              os.path.basename(path))
-                if ret_code == 0:
-                    self.SetTestSuitePackage("vts", dest_file_path)
-                else:
-                    print("Error in copy file from GCS (code %s)." % ret_code)
-        return self.GetDeviceImage(), self.GetTestSuitePackage()
diff --git a/harnesses/host_controller/build/build_provider_local_fs.py b/harnesses/host_controller/build/build_provider_local_fs.py
deleted file mode 100644
index 878b165..0000000
--- a/harnesses/host_controller/build/build_provider_local_fs.py
+++ /dev/null
@@ -1,55 +0,0 @@
-#
-# Copyright (C) 2017 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-import os
-import zipfile
-
-from vts.harnesses.host_controller.build import build_provider
-
-
-class BuildProviderLocalFS(build_provider.BuildProvider):
-    """A build provider for local file system (fs)."""
-
-    def __init__(self):
-        super(BuildProviderLocalFS, self).__init__()
-
-    def Fetch(self, path):
-        """Fetches Android device artifact file(s) from a local directory.
-
-        Args:
-            path: string, the path of a directory which keeps artifacts.
-
-        Returns:
-            a dict containing the device image info.
-            a dict containing the test suite package info.
-        """
-        if os.path.isdir(path):
-            self.SetDeviceImagesInDirecotry(path)
-        else:
-            if path.endswith("android-vts.zip"):
-                if os.path.isfile(path):
-                    dest_path = os.path.join(self.tmp_dirpath, "android-vts")
-                    with zipfile.ZipFile(path, 'r') as zip_ref:
-                        zip_ref.extractall(dest_path)
-                        bin_path = os.path.join(dest_path, "android-vts",
-                                                "tools", "vts-tradefed")
-                        os.chmod(bin_path, 0766)
-                        self.SetTestSuitePackage("vts", bin_path)
-                else:
-                    print("The specified file doesn't exist, %s" % path)
-            else:
-                print("unsupported zip file %s" % path)
-        return self.GetDeviceImage(), self.GetTestSuitePackage()
diff --git a/harnesses/host_controller/build/pab_client.py b/harnesses/host_controller/build/pab_client.py
deleted file mode 100644
index 8eac69f..0000000
--- a/harnesses/host_controller/build/pab_client.py
+++ /dev/null
@@ -1,519 +0,0 @@
-#
-# Copyright (C) 2017 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-"""Class to fetch artifacts from Partner Android Build server
-"""
-
-import argparse
-import getpass
-import httplib2
-import json
-import logging
-import os
-import requests
-import urlparse
-import zipfile
-from posixpath import join as path_urljoin
-
-from oauth2client.client import flow_from_clientsecrets
-from oauth2client.file import Storage
-from oauth2client.tools import argparser
-from oauth2client.tools import run_flow
-
-from selenium import webdriver
-from selenium.webdriver.common.by import By
-from selenium.common.exceptions import TimeoutException
-from selenium.webdriver.support import expected_conditions as EC
-from selenium.webdriver.common.keys import Keys
-from selenium.webdriver.chrome.options import Options
-from selenium.webdriver.support.ui import WebDriverWait
-
-from vts.harnesses.host_controller.build import build_provider
-
-# constants for GET and POST endpoints
-GET = 'GET'
-POST = 'POST'
-
-
-class PartnerAndroidBuildClient(build_provider.BuildProvider):
-    """Client that manages Partner Android Build downloading.
-
-    Attributes:
-        BAD_XSRF_CODE: int, error code for bad XSRF token error
-        BASE_URL: string, path to PAB entry point
-        BUILDARTIFACT_NAME_KEY: string, index in artifact containing name
-        BUILD_BUILDID_KEY: string, index in build containing build_id
-        BUILD_COMPLETED_STATUS: int, value of 'complete' build
-        BUILD_STATUS_KEY: string, index in build object containing status.
-        CHROME_DRIVER_LOCATION: string, path to chromedriver
-        CHROME_LOCATION: string, path to Chrome browser
-        CLIENT_STORAGE: string, path to store credentials.
-        DEFAULT_CHUNK_SIZE: int, number of bytes to download at a time.
-        DOWNLOAD_URL_KEY: string, index in downloadBuildArtifact containing url
-        EMAIL: string, email constant for userinfo JSON
-        EXPIRED_XSRF_CODE: int, error code for expired XSRF token error
-        GETBUILD_ARTIFACTS_KEY, string, index in build obj containing artifacts
-        GMS_DOWNLOAD_URL: string, base url for downloading artifacts.
-        LISTBUILD_BUILD_KEY: string, index in listBuild containing builds
-        PAB_URL: string, redirect url from Google sign-in to PAB
-        PASSWORD: string, password constant for userinfo JSON
-        SCOPE: string, URL for which to request access via oauth2.
-        SVC_URL: string, path to buildsvc RPC
-        XSRF_STORE: string, path to store xsrf token
-        _credentials : oauth2client credentials object
-        _userinfo_file: location of file containing email and password
-        _xsrf : string, XSRF token from PAB website. expires after 7 days.
-    """
-    _credentials = None
-    _userinfo_file = None
-    _xsrf = None
-    BAD_XSRF_CODE = -32000
-    BASE_URL = 'https://partner.android.com'
-    BUILDARTIFACT_NAME_KEY = '1'
-    BUILD_BUILDID_KEY = '1'
-    BUILD_COMPLETED_STATUS = 7
-    BUILD_STATUS_KEY = '7'
-    CHROME_DRIVER_LOCATION = '/usr/local/bin/chromedriver'
-    CHROME_LOCATION = '/usr/bin/google-chrome'
-    CLIENT_SECRETS = os.path.join(
-        os.path.dirname(__file__), 'client_secrets.json')
-    CLIENT_STORAGE = os.path.join(os.path.dirname(__file__), 'credentials')
-    DEFAULT_CHUNK_SIZE = 1024
-    DOWNLOAD_URL_KEY = '1'
-    EMAIL = 'email'
-    EXPIRED_XSRF_CODE = -32001
-    GETBUILD_ARTIFACTS_KEY = '2'
-    GMS_DOWNLOAD_URL = 'https://partnerdash.google.com/build/gmsdownload'
-    LISTBUILD_BUILD_KEY = '1'
-    PAB_URL = ('https://www.google.com/accounts/Login?&continue='
-               'https://partner.android.com/build/')
-    PASSWORD = 'password'
-    # need both of these scopes to access PAB downloader
-    scopes = ('https://www.googleapis.com/auth/partnerdash',
-              'https://www.googleapis.com/auth/alkali-base')
-    SCOPE = ' '.join(scopes)
-    SVC_URL = urlparse.urljoin(BASE_URL, 'build/u/0/_gwt/_rpc/buildsvc')
-    XSRF_STORE = os.path.join(os.path.dirname(__file__), 'xsrf')
-
-    def __init__(self):
-        """Creates a temp dir."""
-        super(PartnerAndroidBuildClient, self).__init__()
-
-    def Authenticate(self, userinfo_file=None):
-        """Authenticate using OAuth2."""
-        # this should be a JSON file with "email" and "password" string fields
-        self._userinfo_file = userinfo_file
-        logging.info('Parsing flags, use --noauth_local_webserver'
-                     ' if running on remote machine')
-
-        parser = argparse.ArgumentParser(parents=[argparser])
-        flags, unknown = parser.parse_known_args()
-        logging.info('Preparing OAuth token')
-        flow = flow_from_clientsecrets(self.CLIENT_SECRETS, scope=self.SCOPE)
-        storage = Storage(self.CLIENT_STORAGE)
-        if self._credentials is None:
-            self._credentials = storage.get()
-        if self._credentials is None or self._credentials.invalid:
-            logging.info('Credentials not found, authenticating.')
-            self._credentials = run_flow(flow, storage, flags)
-
-        if self._credentials.access_token_expired:
-            logging.info('Access token expired, refreshing.')
-            self._credentials.refresh(http=httplib2.Http())
-
-        if self.XSRF_STORE is not None and os.path.isfile(self.XSRF_STORE):
-            with open(self.XSRF_STORE, 'r') as handle:
-                self._xsrf = handle.read()
-
-    def GetXSRFToken(self, email=None, password=None):
-        """Get XSRF token. Prompt if email/password not provided.
-
-        Args:
-            email: string, optional. Gmail account of user logging in
-            password: string, optional. Password of user logging in
-
-        Returns:
-            boolean, whether the token was accessed and stored
-        """
-        if self._userinfo_file is not None:
-            with open(self._userinfo_file, 'r') as handle:
-                userinfo = json.load(handle)
-
-            if self.EMAIL not in userinfo or self.PASSWORD not in userinfo:
-                raise ValueError(
-                    'Malformed userinfo file: needs email and password')
-
-            email = userinfo[self.EMAIL]
-            password = userinfo[self.PASSWORD]
-
-        chrome_options = Options()
-        chrome_options.add_argument("--headless")
-        chrome_options.binary_location = self.CHROME_LOCATION
-
-        driver = webdriver.Chrome(
-            executable_path=os.path.abspath(self.CHROME_DRIVER_LOCATION),
-            chrome_options=chrome_options)
-
-        driver.set_window_size(1080, 800)
-        wait = WebDriverWait(driver, 10)
-
-        driver.get(self.PAB_URL)
-
-        query = driver.find_element_by_id("identifierId")
-        if email is None:
-            email = raw_input("Email: ")
-        query.send_keys(email)
-        driver.find_element_by_id("identifierNext").click()
-
-        pw = wait.until(EC.element_to_be_clickable((By.NAME, "password")))
-        pw.clear()
-
-        if password is None:
-            pw.send_keys(getpass.getpass("Password: "))
-        else:
-            pw.send_keys(password)
-
-        driver.find_element_by_id("passwordNext").click()
-
-        try:
-            wait.until(EC.title_contains("Partner Android Build"))
-        except TimeoutException as e:
-            logging.exception(e)
-            raise ValueError('Wrong password or non-standard login flow')
-
-        self._xsrf = driver.execute_script("return clientConfig.XSRF_TOKEN;")
-        with open(self.XSRF_STORE, 'w') as handle:
-            handle.write(self._xsrf)
-
-        return True
-
-    def CallBuildsvc(self, method, params, account_id):
-        """Call the buildsvc RPC with given parameters.
-
-        Args:
-            method: string, name of method to be called in buildsvc
-            params: dict, parameters to RPC call
-            account_id: int, ID associated with the PAB account.
-
-        Returns:
-            dict, result from RPC call
-        """
-        if self._xsrf is None:
-            self.GetXSRFToken()
-        params = json.dumps(params)
-
-        data = {"method": method, "params": params, "xsrf": self._xsrf}
-        data = json.dumps(data)
-        headers = {}
-        self._credentials.apply(headers)
-        headers['Content-Type'] = 'application/json'
-        headers['x-alkali-account'] = account_id
-
-        response = requests.post(self.SVC_URL, data=data, headers=headers)
-
-        responseJSON = {}
-
-        try:
-            responseJSON = response.json()
-        except ValueError:
-            raise ValueError("Backend error -- check your account ID")
-
-        if 'result' in responseJSON:
-            return responseJSON['result']
-
-        if 'error' in responseJSON and 'code' in responseJSON['error']:
-            if responseJSON['error']['code'] == self.BAD_XSRF_CODE:
-                raise ValueError(
-                    "Bad XSRF token -- must be for the same account as your credentials")
-            if responseJSON['error']['code'] == self.EXPIRED_XSRF_CODE:
-                raise ValueError("Expired XSRF token -- please refresh")
-
-        raise ValueError("Unknown response from server -- %s" %
-                         json.dumps(responseJSON))
-
-    def GetBuildList(self,
-                     account_id,
-                     branch,
-                     target,
-                     page_token="",
-                     max_results=10,
-                     internal=True,
-                     method=GET):
-        """Get the list of builds for a given account, branch and target
-        Args:
-            account_id: int, ID associated with the PAB account.
-            branch: string, branch to pull resource from.
-            target: string, "latest" or a specific version.
-            page_token: string, token used for pagination
-            max_results: maximum build results the build list contains, e.g. 25
-            internal: bool, whether to query internal build
-            method: 'GET' or 'POST', which endpoint to query
-
-        Returns:
-            list of dicts representing the builds, descending in time
-        """
-        if method == POST:
-            params = {
-                "1": branch,
-                "2": target,
-                "3": page_token,
-                "4": max_results,
-                "7": int(internal)
-            }
-
-            result = self.CallBuildsvc("listBuild", params, account_id)
-            # in listBuild response, index '1' contains builds
-            if self.LISTBUILD_BUILD_KEY in result:
-                return result[self.LISTBUILD_BUILD_KEY]
-            raise ValueError("Build list not found -- %s" % params)
-        elif method == GET:
-            headers = {}
-            self._credentials.apply(headers)
-
-            action = 'list-internal' if internal else 'list'
-            # PAB URL format expects something (anything) to be given as buildid
-            # and resource, even for action list
-            dummy = 'DUMMY'
-            url = path_urljoin(self.BASE_URL, 'build', 'builds', action,
-                               branch, target, dummy,
-                               dummy) + '?a=' + str(account_id)
-
-            response = requests.get(url, headers=headers)
-            try:
-                responseJSON = response.json()
-                return responseJSON['build']
-            except ValueError as e:
-                logging.exception(e)
-                raise ValueError("Backend error -- check your account ID")
-
-    def GetLatestBuildId(self, account_id, branch, target, method=GET):
-        """Get the most recent build_id for a given account, branch and target
-        Args:
-            account_id: int, ID associated with the PAB account.
-            branch: string, branch to pull resource from.
-            target: string, "latest" or a specific version.
-            method: 'GET' or 'POST', which endpoint to query
-
-        Returns:
-            string, most recent build id
-        """
-        # TODO: support pagination, maybe?
-        build_list = self.GetBuildList(account_id=account_id,
-                                       branch=branch,
-                                       target=target,
-                                       method=method)
-        if len(build_list) == 0:
-            raise ValueError(
-                'No builds found for account_id=%s, branch=%s, target=%s' %
-                (account_id, branch, target))
-        for build in build_list:
-            if method == POST:
-                # get build status: 7 = completed build
-                if build.get(self.BUILD_STATUS_KEY,
-                             None) == self.BUILD_COMPLETED_STATUS:
-                    # return build id (index '1')
-                    return build[self.BUILD_BUILDID_KEY]
-            elif method == GET:
-                if build['build_attempt_status'] == "COMPLETE" and build[
-                        "successful"]:
-                    return build['build_id']
-        raise ValueError(
-            'No complete builds found: %s failed or incomplete builds found' %
-            len(build_list))
-
-    def GetBuildArtifacts(
-            self, account_id, build_id, branch, target, method=POST):
-        """Get the list of build artifacts.
-
-        For an account, build, target, branch.
-
-        Args:
-            account_id: int, ID associated with the PAB account.
-            build_id: string, ID of the build
-            branch: string, branch to pull resource from.
-            target: string, "latest" or a specific version.
-            method: 'GET' or 'POST', which endpoint to query
-
-        Returns:
-            list of build artifact objects
-        """
-        if method == GET:
-            raise NotImplementedError(
-                "GetBuildArtifacts not supported with GET")
-        params = {"1": build_id, "2": target, "3": branch}
-
-        result = self.CallBuildsvc("getBuild", params, account_id)
-        # in getBuild response, index '2' contains the artifacts
-        if self.GETBUILD_ARTIFACTS_KEY in result:
-            return result[self.GETBUILD_ARTIFACTS_KEY]
-        if len(result) == 0:
-            raise ValueError("Build artifacts not found -- %s" % params)
-
-    def GetArtifactURL(self,
-                       account_id,
-                       build_id,
-                       target,
-                       artifact_name,
-                       branch,
-                       internal,
-                       method=GET):
-        """Get the URL for an artifact on the PAB server, using buildsvc.
-
-        Args:
-            account_id: int, ID associated with the PAB account.
-            build_id: string/int, id of the build.
-            target: string, "latest" or a specific version.
-            artifact_name: string, simple file name (no parent dir or path).
-            branch: string, branch to pull resource from.
-            internal: int, whether the request is for an internal build artifact
-            method: 'GET' or 'POST', which endpoint to query
-
-        Returns:
-            string, The URL for the resource specified by the parameters
-        """
-        if method == POST:
-            params = {
-                "1": str(build_id),
-                "2": target,
-                "3": artifact_name,
-                "4": branch,
-                "5": "",  # release_candidate_name
-                "6": internal
-            }
-
-            result = self.CallBuildsvc(method='downloadBuildArtifact',
-                                       params=params,
-                                       account_id=account_id)
-
-            # in downloadBuildArtifact response, index '1' contains the url
-            if self.DOWNLOAD_URL_KEY in result:
-                return result[self.DOWNLOAD_URL_KEY]
-            if len(result) == 0:
-                raise ValueError("Resource not found -- %s" % params)
-        elif method == GET:
-            headers = {}
-            self._credentials.apply(headers)
-
-            action = 'get-internal' if internal else 'get'
-            get_url = path_urljoin(self.BASE_URL, 'build', 'builds', action,
-                                   branch, target, build_id,
-                                   artifact_name) + '?a=' + str(account_id)
-
-            response = requests.get(get_url, headers=headers)
-            try:
-                responseJSON = response.json()
-                return responseJSON['url']
-            except ValueError:
-                raise ValueError("Backend error -- check your account ID")
-
-    def DownloadArtifact(self, download_url, filename):
-        """Get artifact from Partner Android Build server.
-
-        Args:
-            download_url: location of resource that we want to download
-            filename: where the artifact gets downloaded locally.
-
-        Returns:
-            boolean, whether the file was successfully downloaded
-        """
-
-        headers = {}
-        self._credentials.apply(headers)
-
-        response = requests.get(download_url, headers=headers, stream=True)
-        response.raise_for_status()
-
-        logging.info('%s now downloading...', download_url)
-        with open(filename, 'wb') as handle:
-            for block in response.iter_content(self.DEFAULT_CHUNK_SIZE):
-                handle.write(block)
-
-        return True
-
-    def GetArtifact(self,
-                    account_id,
-                    branch,
-                    target,
-                    artifact_name,
-                    build_id='latest',
-                    method=GET,
-                    unzip=True):
-        """Get an artifact for an account, branch, target and name and build id.
-
-        If build_id not given, get latest.
-
-        Args:
-            account_id: int, ID associated with the PAB account.
-            branch: string, branch to pull resource from.
-            target: string, "latest" or a specific version.
-            artifact_name: name of artifact, e.g. aosp_arm64_ab-img-4353141.zip
-                ({id} will automatically get replaced with build ID)
-            build_id: string, build ID of an artifact to fetch (or 'latest').
-            method: 'GET' or 'POST', which endpoint to query
-            unzip: boolean, True to unzip the artifact if that's a zip file.
-
-        Returns:
-            a dict containing the device image info.
-        """
-        if build_id == 'latest':
-            build_id = self.GetLatestBuildId(account_id=account_id,
-                                             branch=branch,
-                                             target=target,
-                                             method=method)
-            print("latest build ID = %s" % build_id)
-
-        if "build_id" in artifact_name:
-            artifact_name = artifact_name.format(build_id=build_id)
-
-        if method == POST:
-            artifacts = self.GetBuildArtifacts(account_id=account_id,
-                                               build_id=build_id,
-                                               branch=branch,
-                                               target=target,
-                                               method=method)
-
-            if len(artifacts) == 0:
-                raise ValueError(
-                    "No artifacts found for build_id=%s, branch=%s, target=%s"
-                    % (build_id, branch, target))
-
-            # in build artifact response, index '1' contains the name
-            artifact_names = [
-                artifact[self.BUILDARTIFACT_NAME_KEY] for artifact in artifacts
-            ]
-            if artifact_name not in artifact_names:
-                raise ValueError("%s not found in artifact list" %
-                                 artifact_name)
-
-        url = self.GetArtifactURL(account_id=account_id,
-                                  build_id=build_id,
-                                  target=target,
-                                  artifact_name=artifact_name,
-                                  branch=branch,
-                                  internal=False,
-                                  method=method)
-
-        if self.tmp_dirpath:
-            artifact_path = os.path.join(self.tmp_dirpath, artifact_name)
-        else:
-            artifact_path = artifact_name
-        self.DownloadArtifact(url, artifact_path)
-        dirname = os.path.dirname(artifact_path)
-        if unzip and artifact_path.endswith(".zip"):
-            if artifact_path.endswith("android-vts.zip"):
-                self.SetTestSuitePackage("vts", artifact_path)
-            else:
-                self.SetDeviceImageZip(artifact_path)
-        return self.GetDeviceImage(), self.GetTestSuitePackage()
diff --git a/harnesses/host_controller/build/pab_client_test.py b/harnesses/host_controller/build/pab_client_test.py
deleted file mode 100644
index ee9d8ab..0000000
--- a/harnesses/host_controller/build/pab_client_test.py
+++ /dev/null
@@ -1,295 +0,0 @@
-#!/usr/bin/env python
-#
-# Copyright (C) 2017 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-import unittest
-from vts.harnesses.host_controller.build import pab_client
-
-try:
-    from unittest import mock
-except ImportError:
-    import mock
-
-from requests.models import Response
-
-
-class PartnerAndroidBuildClientTest(unittest.TestCase):
-    """Tests for Partner Android Build client."""
-
-    def setUp(self):
-        self.client = pab_client.PartnerAndroidBuildClient()
-        self.client.XSRF_STORE = None
-
-    @mock.patch("pab_client.flow_from_clientsecrets")
-    @mock.patch("pab_client.run_flow")
-    @mock.patch("pab_client.Storage")
-    @mock.patch('pab_client.PartnerAndroidBuildClient._credentials')
-    def testAuthenticationNew(self, mock_creds, mock_storage, mock_rf,
-                              mock_ffc):
-        mock_creds.invalid = True
-        self.client.Authenticate()
-        mock_ffc.assert_called_once()
-        mock_storage.assert_called_once()
-        mock_rf.assert_called_once()
-
-    @mock.patch("pab_client.flow_from_clientsecrets")
-    @mock.patch("pab_client.run_flow")
-    @mock.patch("pab_client.Storage")
-    @mock.patch('pab_client.PartnerAndroidBuildClient._credentials')
-    def testAuthenticationStale(self, mock_creds, mock_storage, mock_rf,
-                                mock_ffc):
-        mock_creds.invalid = False
-        mock_creds.access_token_expired = True
-        self.client.Authenticate()
-        mock_ffc.assert_called_once()
-        mock_storage.assert_called_once()
-        mock_rf.assert_not_called()
-        mock_creds.refresh.assert_called_once()
-
-    @mock.patch("pab_client.flow_from_clientsecrets")
-    @mock.patch("pab_client.run_flow")
-    @mock.patch("pab_client.Storage")
-    @mock.patch('pab_client.PartnerAndroidBuildClient._credentials')
-    def testAuthenticationFresh(self, mock_creds, mock_storage, mock_rf,
-                                mock_ffc):
-        mock_creds.invalid = False
-        mock_creds.access_token_expired = False
-        self.client.Authenticate()
-        mock_ffc.assert_called_once()
-        mock_storage.assert_called_once()
-        mock_rf.assert_not_called()
-        mock_creds.refresh.assert_not_called()
-
-    @mock.patch('pab_client.PartnerAndroidBuildClient._credentials')
-    @mock.patch('pab_client.requests')
-    @mock.patch('pab_client.open')
-    def testDownloadArtifact(self, mock_open, mock_requests, mock_creds):
-        artifact_url = (
-            "https://partnerdash.google.com/build/gmsdownload/"
-            "f_companion/label/clockwork.companion_20170906_211311_RC00/"
-            "ClockworkCompanionGoogleWithGmsRelease_signed.apk?a=100621237")
-        self.client.DownloadArtifact(
-            artifact_url, 'ClockworkCompanionGoogleWithGmsRelease_signed.apk')
-        mock_creds.apply.assert_called_with({})
-        mock_requests.get.assert_called_with(
-            artifact_url, headers={}, stream=True)
-        mock_open.assert_called_with(
-            'ClockworkCompanionGoogleWithGmsRelease_signed.apk', 'wb')
-
-    @mock.patch('pab_client.PartnerAndroidBuildClient._credentials')
-    @mock.patch('pab_client.requests')
-    def testGetArtifactURL(self, mock_requests, mock_creds):
-        self.client._xsrf = 'disable'
-        response = Response()
-        response.status_code = 200
-        response._content = b'{ "result" : {"1": "this_url"}}'
-        mock_requests.post.return_value = response
-        url = self.client.GetArtifactURL(
-            100621237,
-            "4331445",
-            "darwin_mac",
-            "android-ndk-43345-darwin-x86_64.tar.bz2",
-            "aosp-master-ndk",
-            0,
-            method='POST')
-
-        mock_requests.post.assert_called_with(
-            'https://partner.android.com/build/u/0/_gwt/_rpc/buildsvc',
-            data=mock.ANY,
-            headers={
-                'Content-Type': 'application/json',
-                'x-alkali-account': 100621237
-            })
-        self.assertEqual(url, "this_url")
-
-    @mock.patch('pab_client.PartnerAndroidBuildClient._credentials')
-    @mock.patch('pab_client.requests')
-    def testGetArtifactURLBackendError(self, mock_requests, mock_creds):
-        self.client._xsrf = 'disable'
-        response = Response()
-        response.status_code = 200
-        response._content = b'not JSON'
-        mock_requests.post.return_value = response
-        with self.assertRaises(ValueError) as cm:
-            self.client.GetArtifactURL(
-                100621237,
-                "4331445",
-                "darwin_mac",
-                "android-ndk-43345-darwin-x86_64.tar.bz2",
-                "aosp-master-ndk",
-                0,
-                method='POST')
-        expected = "Backend error -- check your account ID"
-        self.assertEqual(str(cm.exception), expected)
-
-    @mock.patch('pab_client.PartnerAndroidBuildClient._credentials')
-    @mock.patch('pab_client.requests')
-    def testGetArtifactURLMissingResultError(self, mock_requests, mock_creds):
-        self.client._xsrf = 'disable'
-        response = Response()
-        response.status_code = 200
-        response._content = b'{"result": {}}'
-        mock_requests.post.return_value = response
-        with self.assertRaises(ValueError) as cm:
-            self.client.GetArtifactURL(
-                100621237,
-                "4331445",
-                "darwin_mac",
-                "android-ndk-43345-darwin-x86_64.tar.bz2",
-                "aosp-master-ndk",
-                0,
-                method='POST')
-        expected = "Resource not found"
-        self.assertIn(expected, str(cm.exception))
-
-    @mock.patch('pab_client.PartnerAndroidBuildClient._credentials')
-    @mock.patch('pab_client.requests')
-    def testGetArtifactURLInvalidXSRFError(self, mock_requests, mock_creds):
-        self.client._xsrf = 'disable'
-        response = Response()
-        response.status_code = 200
-        response._content = b'{"error": {"code": -32000, "message":"Invalid"}}'
-        mock_requests.post.return_value = response
-        with self.assertRaises(ValueError) as cm:
-            self.client.GetArtifactURL(
-                100621237,
-                "4331445",
-                "darwin_mac",
-                "android-ndk-43345-darwin-x86_64.tar.bz2",
-                "aosp-master-ndk",
-                0,
-                method='POST')
-        self.assertIn('Bad XSRF token', str(cm.exception))
-
-    @mock.patch('pab_client.PartnerAndroidBuildClient._credentials')
-    @mock.patch('pab_client.requests')
-    def testGetArtifactURLExpiredXSRFError(self, mock_requests, mock_creds):
-        self.client._xsrf = 'disable'
-        response = Response()
-        response.status_code = 200
-        response._content = b'{"error": {"code": -32001, "message":"Expired"}}'
-        mock_requests.post.return_value = response
-        with self.assertRaises(ValueError) as cm:
-            self.client.GetArtifactURL(
-                100621237,
-                "4331445",
-                "darwin_mac",
-                "android-ndk-43345-darwin-x86_64.tar.bz2",
-                "aosp-master-ndk",
-                0,
-                method='POST')
-        self.assertIn('Expired XSRF token', str(cm.exception))
-
-    @mock.patch('pab_client.PartnerAndroidBuildClient._credentials')
-    @mock.patch('pab_client.requests')
-    def testGetArtifactURLUnknownError(self, mock_requests, mock_creds):
-        self.client._xsrf = 'disable'
-        response = Response()
-        response.status_code = 200
-        response._content = b'{"some_other_json": "foo"}'
-        mock_requests.post.return_value = response
-        with self.assertRaises(ValueError) as cm:
-            self.client.GetArtifactURL(
-                100621237,
-                "4331445",
-                "darwin_mac",
-                "android-ndk-43345-darwin-x86_64.tar.bz2",
-                "aosp-master-ndk",
-                0,
-                method='POST')
-        self.assertIn('Unknown response from server', str(cm.exception))
-
-    @mock.patch('pab_client.PartnerAndroidBuildClient._credentials')
-    @mock.patch('pab_client.requests')
-    def testGetBuildListSuccess(self, mock_requests, mock_creds):
-        self.client._xsrf = 'disable'
-        response = Response()
-        response.status_code = 200
-        response._content = b'{"result": {"1": "foo"}}'
-        mock_requests.post.return_value = response
-        result = self.client.GetBuildList(
-            100621237,
-            "git_oc-treble-dev",
-            "aosp_arm64_ab-userdebug",
-            method='POST')
-        self.assertEqual(result, "foo")
-        mock_requests.post.assert_called_with(
-            'https://partner.android.com/build/u/0/_gwt/_rpc/buildsvc',
-            data=mock.ANY,
-            headers={
-                'Content-Type': 'application/json',
-                'x-alkali-account': 100621237
-            })
-
-    @mock.patch('pab_client.PartnerAndroidBuildClient._credentials')
-    @mock.patch('pab_client.requests')
-    def testGetBuildListError(self, mock_requests, mock_creds):
-        self.client._xsrf = 'disable'
-        response = Response()
-        response.status_code = 200
-        response._content = b'{"result": {"3": "foo"}}'
-        mock_requests.post.return_value = response
-        with self.assertRaises(ValueError) as cm:
-            self.client.GetBuildList(
-                100621237,
-                "git_oc-treble-dev",
-                "aosp_arm64_ab-userdebug",
-                method='POST')
-        self.assertIn('Build list not found', str(cm.exception))
-
-    @mock.patch('pab_client.PartnerAndroidBuildClient._credentials')
-    @mock.patch('pab_client.PartnerAndroidBuildClient.GetBuildList')
-    def testGetLatestBuildIdSuccess(self, mock_gbl, mock_creds):
-        self.client._xsrf = 'disable'
-        mock_gbl.return_value = [{'7': 5, '1': 'bad'}, {'7': 7, '1': 'good'}]
-        result = self.client.GetLatestBuildId(
-            100621237,
-            "git_oc-treble-dev",
-            "aosp_arm64_ab-userdebug",
-            method='POST')
-        self.assertEqual(result, 'good')
-
-    @mock.patch('pab_client.PartnerAndroidBuildClient._credentials')
-    @mock.patch('pab_client.PartnerAndroidBuildClient.GetBuildList')
-    def testGetLatestBuildIdEmpty(self, mock_gbl, mock_creds):
-        self.client._xsrf = 'disable'
-        mock_gbl.return_value = []
-        with self.assertRaises(ValueError) as cm:
-            result = self.client.GetLatestBuildId(
-                100621237,
-                "git_oc-treble-dev",
-                "aosp_arm64_ab-userdebug",
-                method='POST')
-        self.assertIn("No builds found for", str(cm.exception))
-
-    @mock.patch('pab_client.PartnerAndroidBuildClient._credentials')
-    @mock.patch('pab_client.PartnerAndroidBuildClient.GetBuildList')
-    def testGetLatestBuildIdAllBad(self, mock_gbl, mock_creds):
-        self.client._xsrf = 'disable'
-        mock_gbl.return_value = [{'7': 0}, {'7': 0}]
-        with self.assertRaises(ValueError) as cm:
-            result = self.client.GetLatestBuildId(
-                100621237,
-                "git_oc-treble-dev",
-                "aosp_arm64_ab-userdebug",
-                method='POST')
-        self.assertEqual(
-            "No complete builds found: 2 failed or incomplete builds found",
-            str(cm.exception))
-
-
-if __name__ == "__main__":
-    unittest.main()
diff --git a/harnesses/host_controller/console.py b/harnesses/host_controller/console.py
deleted file mode 100644
index 6d5c283..0000000
--- a/harnesses/host_controller/console.py
+++ /dev/null
@@ -1,747 +0,0 @@
-#
-# Copyright (C) 2017 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-import argparse
-import cmd
-import imp  # Python v2 compatibility
-import logging
-import os
-import shutil
-import socket
-import subprocess
-import sys
-import threading
-import time
-
-import httplib2
-from apiclient import errors
-import urlparse
-
-from vts.harnesses.host_controller.tfc import request
-from vts.harnesses.host_controller.build import build_flasher
-from vts.harnesses.host_controller.build import build_provider
-from vts.harnesses.host_controller.build import build_provider_ab
-from vts.harnesses.host_controller.build import build_provider_gcs
-from vts.harnesses.host_controller.build import build_provider_local_fs
-from vts.harnesses.host_controller.tradefed import remote_operation
-
-# The default Partner Android Build (PAB) public account.
-# To obtain access permission, please reach out to Android partner engineering
-# department of Google LLC.
-_DEFAULT_ACCOUNT_ID = '543365459'
-
-# The default value for "flash --current".
-_DEFAULT_FLASH_IMAGES = [
-    build_provider.FULL_ZIPFILE, "boot.img", "cache.img", "system.img",
-    "userdata.img", "vbmeta.img", "vendor.img",
-]
-
-# The environment variable for default serial numbers.
-_ANDROID_SERIAL = "ANDROID_SERIAL"
-
-
-class ConsoleArgumentError(Exception):
-    """Raised when the console fails to parse commands."""
-    pass
-
-
-class ConsoleArgumentParser(argparse.ArgumentParser):
-    """The argument parser for a console command."""
-
-    def __init__(self, command_name, description):
-        """Initializes the ArgumentParser without --help option.
-
-        Args:
-            command_name: A string, the first argument of the command.
-            description: The help message about the command.
-        """
-        super(ConsoleArgumentParser, self).__init__(
-            prog=command_name, description=description, add_help=False)
-
-    def ParseLine(self, line):
-        """Parses a command line.
-
-        Args:
-            line: A string, the command line.
-
-        Returns:
-            An argparse.Namespace object.
-        """
-        return self.parse_args(line.split())
-
-    # @Override
-    def error(self, message):
-        """Raises an exception when failing to parse the command.
-
-        Args:
-            message: The error message.
-
-        Raises:
-            ConsoleArgumentError.
-        """
-        raise ConsoleArgumentError(message)
-
-
-class Console(cmd.Cmd):
-    """The console for host controllers.
-
-    Attributes:
-        device_image_info: dict containing info about device image files.
-        prompt: The prompt string at the beginning of each command line.
-        test_suite_info: dict containing info about test suite package files.
-        update_thread: threading.Thread that updates device state regularly
-        _pab_client: The PartnerAndroidBuildClient used to download artifacts
-        _tfc_client: The TfcClient that the host controllers connect to.
-        _hosts: A list of HostController objects.
-        _in_file: The input file object.
-        _out_file: The output file object.
-        _serials: A list of string where each string is a device serial.
-        _copy_parser: The parser for copy command.
-        _device_parser: The parser for device command.
-        _fetch_parser: The parser for fetch command.
-        _flash_parser: The parser for flash command.
-        _lease_parser: The parser for lease command.
-        _list_parser: The parser for list command.
-        _request_parser: The parser for request command.
-    """
-
-    def __init__(self,
-                 tfc,
-                 pab,
-                 host_controllers,
-                 in_file=sys.stdin,
-                 out_file=sys.stdout):
-        """Initializes the attributes and the parsers."""
-        # cmd.Cmd is old-style class.
-        cmd.Cmd.__init__(self, stdin=in_file, stdout=out_file)
-        self._build_provider = {}
-        self._build_provider["pab"] = pab
-        self._build_provider[
-            "local_fs"] = build_provider_local_fs.BuildProviderLocalFS()
-        self._build_provider["gcs"] = build_provider_gcs.BuildProviderGCS()
-        self._build_provider["ab"] = build_provider_ab.BuildProviderAB()
-        self._tfc_client = tfc
-        self._hosts = host_controllers
-        self._in_file = in_file
-        self._out_file = out_file
-        self.prompt = "> "
-        self.device_image_info = {}
-        self.test_suite_info = {}
-        self.update_thread = None
-
-        self._InitCopyParser()
-        self._InitDeviceParser()
-        self._InitFetchParser()
-        self._InitFlashParser()
-        self._InitLeaseParser()
-        self._InitListParser()
-        self._InitRequestParser()
-        self._InitTestParser()
-
-    def _InitRequestParser(self):
-        """Initializes the parser for request command."""
-        self._request_parser = ConsoleArgumentParser(
-            "request", "Send TFC a request to execute a command.")
-        self._request_parser.add_argument(
-            "--cluster",
-            required=True,
-            help="The cluster to which the request is submitted.")
-        self._request_parser.add_argument(
-            "--run-target",
-            required=True,
-            help="The target device to run the command.")
-        self._request_parser.add_argument(
-            "--user",
-            required=True,
-            help="The name of the user submitting the request.")
-        self._request_parser.add_argument(
-            "command",
-            metavar="COMMAND",
-            nargs="+",
-            help='The command to be executed. If the command contains '
-            'arguments starting with "-", place the command after '
-            '"--" at end of line.')
-
-    def ProcessScript(self, script_file_path):
-        """Processes a .py script file.
-
-        A script file implements a function which emits a list of console
-        commands to execute. That function emits an empty list or None if
-        no more command needs to be processed.
-
-        Args:
-            script_file_path: string, the path of a script file (.py file).
-
-        Returns:
-            True if successful; False otherwise
-        """
-        if not script_file_path.endswith(".py"):
-            print("Script file is not .py file: %s" % script_file_path)
-            return False
-
-        script_module = imp.load_source('script_module', script_file_path)
-
-        if _ANDROID_SERIAL in os.environ:
-            serial = os.environ[_ANDROID_SERIAL]
-        else:
-            serial = None
-
-        if serial:
-            self.onecmd("device --set_serial=%s" % serial)
-
-        commands = script_module.EmitConsoleCommands()
-        if commands:
-            for command in commands:
-                self.onecmd(command)
-        return True
-
-    def do_request(self, line):
-        """Sends TFC a request to execute a command."""
-        args = self._request_parser.ParseLine(line)
-        req = request.Request(
-            cluster=args.cluster,
-            command_line=" ".join(args.command),
-            run_target=args.run_target,
-            user=args.user)
-        self._tfc_client.NewRequest(req)
-
-    def help_request(self):
-        """Prints help message for request command."""
-        self._request_parser.print_help(self._out_file)
-
-    def _InitListParser(self):
-        """Initializes the parser for list command."""
-        self._list_parser = ConsoleArgumentParser(
-            "list", "Show information about the hosts.")
-        self._list_parser.add_argument(
-            "--host", type=int, help="The index of the host.")
-        self._list_parser.add_argument(
-            "type",
-            choices=("hosts", "devices"),
-            help="The type of the shown objects.")
-
-    def _Print(self, string):
-        """Prints a string and a new line character.
-
-        Args:
-            string: The string to be printed.
-        """
-        self._out_file.write(string + "\n")
-
-    def do_list(self, line):
-        """Shows information about the hosts."""
-        args = self._list_parser.ParseLine(line)
-        if args.host is None:
-            hosts = enumerate(self._hosts)
-        else:
-            hosts = [(args.host, self._hosts[args.host])]
-        if args.type == "hosts":
-            self._PrintHosts(self._hosts)
-        elif args.type == "devices":
-            for ind, host in hosts:
-                devices = host.ListDevices()
-                self._Print("[%3d]  %s" % (ind, host.hostname))
-                self._PrintDevices(devices)
-
-    def help_list(self):
-        """Prints help message for list command."""
-        self._list_parser.print_help(self._out_file)
-
-    def _PrintHosts(self, hosts):
-        """Shows a list of host controllers.
-
-        Args:
-            hosts: A list of HostController objects.
-        """
-        self._Print("index  name")
-        for ind, host in enumerate(hosts):
-            self._Print("[%3d]  %s" % (ind, host.hostname))
-
-    def _PrintDevices(self, devices):
-        """Shows a list of devices.
-
-        Args:
-            devices: A list of DeviceInfo objects.
-        """
-        attr_names = ("device_serial", "state", "run_target", "build_id",
-                      "sdk_version", "stub")
-        self._PrintObjects(devices, attr_names)
-
-    def _PrintObjects(self, objects, attr_names):
-        """Shows objects as a table.
-
-        Args:
-            object: The objects to be shown, one object in a row.
-            attr_names: The attributes to be shown, one attribute in a column.
-        """
-        width = [len(name) for name in attr_names]
-        rows = [attr_names]
-        for dev_info in objects:
-            attrs = [
-                _ToPrintString(getattr(dev_info, name, ""))
-                for name in attr_names
-            ]
-            rows.append(attrs)
-            for index, attr in enumerate(attrs):
-                width[index] = max(width[index], len(attr))
-
-        for row in rows:
-            self._Print("  ".join(
-                attr.ljust(width[index]) for index, attr in enumerate(row)))
-
-    def _InitLeaseParser(self):
-        """Initializes the parser for lease command."""
-        self._lease_parser = ConsoleArgumentParser(
-            "lease", "Make a host lease command tasks from TFC.")
-        self._lease_parser.add_argument(
-            "--host", type=int, help="The index of the host.")
-
-    def do_lease(self, line):
-        """Makes a host lease command tasks from TFC."""
-        args = self._lease_parser.ParseLine(line)
-        if args.host is None:
-            if len(self._hosts) > 1:
-                raise ConsoleArgumentError("More than one hosts.")
-            args.host = 0
-        tasks = self._hosts[args.host].LeaseCommandTasks()
-        self._PrintTasks(tasks)
-
-    def help_lease(self):
-        """Prints help message for lease command."""
-        self._lease_parser.print_help(self._out_file)
-
-    def _InitFetchParser(self):
-        """Initializes the parser for fetch command."""
-        self._fetch_parser = ConsoleArgumentParser("fetch",
-                                                   "Fetch a build artifact.")
-        self._fetch_parser.add_argument(
-            '--type',
-            default='pab',
-            choices=('local_fs', 'gcs', 'pab', 'ab'),
-            help='Build provider type')
-        self._fetch_parser.add_argument(
-            '--method',
-            default='GET',
-            choices=('GET', 'POST'),
-            help='Method for fetching')
-        self._fetch_parser.add_argument(
-            "--path",  # required for local_fs
-            help="The path of a local directory which keeps the artifacts.")
-        self._fetch_parser.add_argument(
-            "--branch",  # required for pab
-            help="Branch to grab the artifact from.")
-        self._fetch_parser.add_argument(
-            "--target",  # required for pab
-            help="Target product to grab the artifact from.")
-        # TODO(lejonathan): find a way to not specify this?
-        self._fetch_parser.add_argument(
-            "--account_id",
-            default=_DEFAULT_ACCOUNT_ID,
-            help="Partner Android Build account_id to use.")
-        self._fetch_parser.add_argument(
-            '--build_id',
-            default='latest',
-            help='Build ID to use default latest.')
-        self._fetch_parser.add_argument(
-            "--artifact_name",  # required for pab
-            help=
-            "Name of the artifact to be fetched. {id} replaced with build id.")
-        self._fetch_parser.add_argument(
-            "--userinfo_file",
-            help=
-            "Location of file containing email and password, if using POST.")
-
-    def do_fetch(self, line):
-        """Makes the host download a build artifact from PAB."""
-        args = self._fetch_parser.ParseLine(line)
-        if args.type == "pab":
-            # do we want this somewhere else? No harm in doing multiple times
-            self._build_provider[args.type].Authenticate(args.userinfo_file)
-            device_images, test_suites = self._build_provider[
-                args.type].GetArtifact(
-                    account_id=args.account_id,
-                    branch=args.branch,
-                    target=args.target,
-                    artifact_name=args.artifact_name,
-                    build_id=args.build_id,
-                    method=args.method)
-        elif args.type == "local_fs" or args.type == "gcs":
-            device_images, test_suites = self._build_provider[args.type].Fetch(
-                args.path)
-        elif args.type == "ab":
-            device_images, test_suites = self._build_provider[args.type].Fetch(
-                branch=args.branch,
-                target=args.target,
-                artifact_name=args.artifact_name,
-                build_id=args.build_id)
-        else:
-            print("ERROR: unknown fetch type %s" % args.type)
-            sys.exit(-1)
-
-        self.device_image_info.update(device_images)
-        self.test_suite_info.update(test_suites)
-
-        if self.device_image_info:
-            logging.info("device images:\n%s",
-                         "\n".join(image + ": " + path for image, path in
-                                   self.device_image_info.iteritems()))
-        if self.test_suite_info:
-            logging.info("test suites:\n%s",
-                         "\n".join(suite + ": " + path for suite, path in
-                                   self.test_suite_info.iteritems()))
-
-    def help_fetch(self):
-        """Prints help message for fetch command."""
-        self._fetch_parser.print_help(self._out_file)
-
-    def DownloadTestResources(self, request_id):
-        """Download all of the test resources for a TFC request id.
-
-        Args:
-            request_id: int, TFC request id
-        """
-        resources = self._tfc_client.TestResourceList(request_id)
-        for resource in resources:
-            self.DownloadTestResource(resource['url'])
-
-    def DownloadTestResource(self, url):
-        """Download a test resource with build provider, given a url.
-
-        Args:
-            url: a resource locator (not necessarily HTTP[s])
-                with the scheme specifying the build provider.
-        """
-        parsed = urlparse.urlparse(url)
-        path = (parsed.netloc + parsed.path).split('/')
-        # pab://5346564/oc-release/marlin-userdebug/4329875/artifact.img
-        if parsed.scheme == "pab":
-            if len(path) != 5:
-                print("Invalid pab resource locator: %s" % url)
-                return
-            account_id, branch, target, build_id, artifact_name = path
-            cmd = ("fetch"
-                   " --type=pab"
-                   " --account_id=%s"
-                   " --branch=%s"
-                   " --target=%s"
-                   " --build_id=%s"
-                   " --artifact_name=%s") % (account_id, branch, target,
-                                             build_id, artifact_name)
-            self.onecmd(cmd)
-        # ab://oc-release/marlin-userdebug/4329875/artifact.img
-        elif parsed.scheme == "ab":
-            if len(path) != 4:
-                print("Invalid ab resource locator: %s" % url)
-                return
-            branch, target, build_id, artifact_name = path
-            cmd = ("fetch"
-                   "--type=ab"
-                   " --branch=%s"
-                   " --target=%s"
-                   " --build_id=%s"
-                   " --artifact_name=%s") % (branch, target, build_id,
-                                             artifact_name)
-            self.onecmd(cmd)
-        elif parsed.scheme == gcs:
-            cmd = "fetch --type=gcs --path=%s" % url
-            self.onecmd(cmd)
-        else:
-            print "Invalid URL: %s" % url
-
-    def _InitFlashParser(self):
-        """Initializes the parser for flash command."""
-        self._flash_parser = ConsoleArgumentParser("flash",
-                                                   "Flash images to a device.")
-        self._flash_parser.add_argument(
-            "--current",
-            metavar="PARTITION_IMAGE",
-            nargs="*",
-            type=lambda x: x.split("="),
-            help="The partitions and images to be flashed. The format is "
-                 "<partition>=<image>. If PARTITION_IMAGE list is empty, "
-                 "currently fetched " + ", ".join(_DEFAULT_FLASH_IMAGES) +
-                 " will be flashed.")
-        self._flash_parser.add_argument(
-            "--serial", default="", help="Serial number for device.")
-        self._flash_parser.add_argument(
-            "--build_dir",
-            help="Directory containing build images to be flashed.")
-        self._flash_parser.add_argument(
-            "--gsi", help="Path to generic system image")
-        self._flash_parser.add_argument(
-            "--vbmeta", help="Path to vbmeta image")
-
-    def do_flash(self, line):
-        """Flash GSI or build images to a device connected with ADB."""
-        args = self._flash_parser.ParseLine(line)
-
-        flashers = []
-        if args.serial:
-            flasher = build_flasher.BuildFlasher(args.serial)
-            flashers.append(flasher)
-        elif self._serials:
-            for serial in self._serials:
-                flasher = build_flasher.BuildFlasher(serial)
-                flashers.append(flasher)
-        else:
-            flasher = build_flasher.BuildFlasher()
-            flashers.append(flasher)
-
-        if args.current:
-            partition_image = dict(
-                (partition, self.device_image_info[image])
-                for partition, image in args.current)
-        else:
-            partition_image = dict(
-                (image.rsplit(".img", 1)[0], self.device_image_info[image])
-                for image in _DEFAULT_FLASH_IMAGES
-                if image in self.device_image_info)
-
-        if flashers:
-            # Can be parallelized as long as that's proven reliable.
-            for flasher in flashers:
-                if args.current is not None:
-                    flasher.Flash(partition_image)
-                else:
-                    if args.gsi is None and args.build_dir is None:
-                        self._flash_parser.error(
-                            "Nothing requested: specify --gsi or --build_dir")
-                    if args.build_dir is not None:
-                        flasher.Flashall(args.build_dir)
-                    if args.gsi is not None:
-                        flasher.FlashGSI(args.gsi, args.vbmeta)
-            for flasher in flashers:
-                flasher.WaitForDevice()
-
-    def help_flash(self):
-        """Prints help message for flash command."""
-        self._flash_parser.print_help(self._out_file)
-
-    def _InitCopyParser(self):
-        """Initializes the parser for copy command."""
-        self._copy_parser = ConsoleArgumentParser("copy", "Copy a file.")
-
-    def do_copy(self, line):
-        """Copy a file from source to destination path."""
-        src, dst = line.split()
-        if dst == "{vts_tf_home}":
-            dst = os.path.dirname(self.test_suite_info["vts"])
-        elif "{" in dst:
-            print("unknown dst %s" % dst)
-            return
-        shutil.copy(src, dst)
-
-    def help_copy(self):
-        """Prints help message for copy command."""
-        self._copy_parser.print_help(self._out_file)
-
-    def _InitDeviceParser(self):
-        """Initializes the parser for device command."""
-        self._device_parser = ConsoleArgumentParser(
-            "device", "Selects device(s) under test.")
-        self._device_parser.add_argument(
-            "--set_serial",
-            default="",
-            help="Serial number for device. Can be a comma-separated list.")
-        self._device_parser.add_argument(
-            "--update",
-            choices=("single", "start", "stop"),
-            default="",
-            help="Update device info on TradeFed cluster")
-        self._device_parser.add_argument(
-            "--interval",
-            type=int,
-            default=30,
-            help="Interval (seconds) to repeat device update.")
-        self._device_parser.add_argument(
-            "--host", type=int, help="The index of the host.")
-        self._serials = []
-
-    def UpdateDevice(self, host):
-        """Updates the device state of all devices on a given host.
-
-        Args:
-            host: HostController object
-        """
-        devices = host.ListDevices()
-        for device in devices:
-            device.Extend(['sim_state', 'sim_operator', 'mac_address'])
-        snapshots = self._tfc_client.CreateDeviceSnapshot(
-            host._cluster_ids[0], host.hostname, devices)
-        self._tfc_client.SubmitHostEvents([snapshots])
-
-    def UpdateDeviceRepeat(self, host, update_interval):
-        """Regularly updates the device state of devices on a given host.
-
-        Args:
-            host: HostController object
-            update_internval: int, number of seconds before repeating
-        """
-        thread = threading.currentThread()
-        while getattr(thread, 'keep_running', True):
-            try:
-                self.UpdateDevice(host)
-            except (socket.error, remote_operation.RemoteOperationException,
-                    httplib2.HttpLib2Error, errors.HttpError) as e:
-                logging.exception(e)
-            time.sleep(update_interval)
-
-    def do_device(self, line):
-        """Sets device info such as serial number."""
-        args = self._device_parser.ParseLine(line)
-        if args.set_serial:
-            self._serials = args.set_serial.split(",")
-            print("serials: %s" % self._serials)
-        if args.update:
-            if args.host is None:
-                if len(self._hosts) > 1:
-                    raise ConsoleArgumentError("More than one host.")
-                args.host = 0
-            host = self._hosts[args.host]
-            if args.update == "single":
-                self.UpdateDevice(host)
-            elif args.update == "start":
-                if args.interval <= 0:
-                    raise ConsoleArgumentError(
-                        "update interval must be positive")
-                # do not allow user to create new
-                # thread if one is currently running
-                if self.update_thread is not None and not hasattr(
-                        self.update_thread, 'keep_running'):
-                    print(
-                        'device update already running. '
-                        'run device --update stop first.'
-                    )
-                    return
-                self.update_thread = threading.Thread(
-                    target=self.UpdateDeviceRepeat,
-                    args=(
-                        host,
-                        args.interval, ))
-                self.update_thread.daemon = True
-                self.update_thread.start()
-            elif args.update == "stop":
-                self.update_thread.keep_running = False
-
-    def help_device(self):
-        """Prints help message for device command."""
-        self._device_parser.print_help(self._out_file)
-
-    def _InitTestParser(self):
-        """Initializes the parser for test command."""
-        self._test_parser = ConsoleArgumentParser("test",
-                                                  "Executes a command on TF.")
-        self._test_parser.add_argument(
-            "--serial",
-            default=None,
-            help="The target device serial to run the command.")
-        self._test_parser.add_argument(
-            "--test_exec_mode",
-            default="subprocess",
-            help="The target exec model.")
-        self._test_parser.add_argument(
-            "command",
-            metavar="COMMAND",
-            nargs="+",
-            help='The command to be executed. If the command contains '
-            'arguments starting with "-", place the command after '
-            '"--" at end of line. format: plan -m module -t testcase')
-
-    def do_test(self, line):
-        """Executes a command using a VTS-TF instance."""
-        args = self._test_parser.ParseLine(line)
-        if args.serial:
-            serial = args.serial
-        elif self._serials:
-            serial = self._serials[0]
-        else:
-            serial = ""
-
-        if args.test_exec_mode == "subprocess":
-            bin_path = self.test_suite_info["vts"]
-            cmd = [bin_path, "run"]
-            cmd.extend(args.command)
-            if serial:
-                cmd.extend(["-s", serial])
-            print("Command: %s" % cmd)
-            result = subprocess.check_output(cmd)
-            logging.debug("result: %s", result)
-        else:
-            print("unsupported exec mode: %s", args.test_exec_mode)
-
-    def help_test(self):
-        """Prints help message for test command."""
-        self._test_parser.print_help(self._out_file)
-
-    def _PrintTasks(self, tasks):
-        """Shows a list of command tasks.
-
-        Args:
-            devices: A list of DeviceInfo objects.
-        """
-        attr_names = ("request_id", "command_id", "task_id", "device_serials",
-                      "command_line")
-        self._PrintObjects(tasks, attr_names)
-
-    def do_exit(self, line):
-        """Terminates the console.
-
-        Returns:
-            True, which stops the cmdloop.
-        """
-        return True
-
-    def help_exit(self):
-        """Prints help message for exit command."""
-        self._Print("Terminate the console.")
-
-    # @Override
-    def onecmd(self, line):
-        """Executes a command and prints any exception."""
-        if line:
-            print("Command: %s" % line)
-        try:
-            return cmd.Cmd.onecmd(self, line)
-        except Exception as e:
-            self._Print("%s: %s" % (type(e).__name__, e))
-            return None
-
-    # @Override
-    def emptyline(self):
-        """Ignores empty lines."""
-        pass
-
-    # @Override
-    def default(self, line):
-        """Handles unrecognized commands.
-
-        Returns:
-            True if receives EOF; otherwise delegates to default handler.
-        """
-        if line == "EOF":
-            return self.do_exit(line)
-        return cmd.Cmd.default(self, line)
-
-
-def _ToPrintString(obj):
-    """Converts an object to printable string on console.
-
-    Args:
-        obj: The object to be printed.
-    """
-    if isinstance(obj, (list, tuple, set)):
-        return ",".join(str(x) for x in obj)
-    return str(obj)
diff --git a/harnesses/host_controller/console_test.py b/harnesses/host_controller/console_test.py
deleted file mode 100644
index ef3b3ec..0000000
--- a/harnesses/host_controller/console_test.py
+++ /dev/null
@@ -1,189 +0,0 @@
-#!/usr/bin/env python
-#
-# Copyright (C) 2017 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-import unittest
-
-try:
-    from unittest import mock
-except ImportError:
-    import mock
-
-try:
-    import StringIO as string_io_module
-except ImportError:
-    import io as string_io_module
-
-from vts.harnesses.host_controller.tfc import command_task
-from vts.harnesses.host_controller.tfc import device_info
-from vts.harnesses.host_controller import console
-
-
-class ConsoleTest(unittest.TestCase):
-    """A test for console.Console.
-
-    Attribute:
-        _out_file: The console output buffer.
-        _host_controller: A mock host_controller.HostController.
-        _pab_client: A mock pab_client.PartnerAndroidBuildClient.
-        _tfc_client: A mock tfc_client.TfcClient.
-        _console: The console being tested.
-    """
-    _DEVICES = [device_info.DeviceInfo(device_serial="ABC001",
-                                      run_target="sailfish",
-                                      state="Available",
-                                      build_id="111111",
-                                      sdk_version="27")]
-    _TASKS = [command_task.CommandTask(request_id="1",
-                                       task_id="1-0",
-                                       command_id="2",
-                                       command_line="vts -m SampleShellTest",
-                                       device_serials=["ABC001"])]
-
-    def setUp(self):
-        """Creates the console."""
-        self._out_file = string_io_module.StringIO()
-        self._host_controller = mock.Mock()
-        self._pab_client = mock.Mock()
-        self._tfc_client = mock.Mock()
-        self._console = console.Console(self._tfc_client,
-                                        self._pab_client,
-                                        [self._host_controller],
-                                        None,
-                                        self._out_file)
-
-    def tearDown(self):
-        """Closes the output file."""
-        self._out_file.close()
-
-    def _IssueCommand(self, command_line):
-        """Issues a command in the console.
-
-        Args:
-            command_line: A string, the input to the console.
-
-        Returns:
-            A string, the output of the console.
-        """
-        out_position = self._out_file.tell()
-        self._console.onecmd(command_line)
-        self._out_file.seek(out_position)
-        return self._out_file.read()
-
-    def testLease(self):
-        """Tests the lease command."""
-        self._host_controller.LeaseCommandTasks.return_value = self._TASKS
-        output = self._IssueCommand("lease")
-        expected = ("request_id  command_id  task_id  device_serials  command_line          \n"
-                    "1           2           1-0      ABC001          vts -m SampleShellTest\n")
-        self.assertEqual(expected, output)
-        output = self._IssueCommand("lease --host 0")
-        self.assertEqual(expected, output)
-
-    def testRequest(self):
-        """Tests the request command."""
-        user = "user0"
-        cluster = "cluster0"
-        run_target = "sailfish"
-        command_line = "vts -m SampleShellTest"
-        self._IssueCommand("request --user %s --cluster %s --run-target %s "
-                           "-- %s" % (user, cluster, run_target, command_line))
-        req = self._tfc_client.NewRequest.call_args[0][0]
-        self.assertEqual(user, req.user)
-        self.assertEqual(cluster, req.cluster)
-        self.assertEqual(run_target, req.run_target)
-        self.assertEqual(command_line, req.command_line)
-
-    def testListHosts(self):
-        """Tests the list command."""
-        self._host_controller.hostname = "host0"
-        output = self._IssueCommand("list hosts")
-        self.assertEqual("index  name\n"
-                         "[  0]  host0\n",
-                         output)
-
-    def testListDevices(self):
-        """Tests the list command."""
-        self._host_controller.ListDevices.return_value = self._DEVICES
-        self._host_controller.hostname = "host0"
-        output = self._IssueCommand("list devices")
-        expected = ("[  0]  host0\n"
-                    "device_serial  state      run_target  build_id  sdk_version  stub\n"
-                    "ABC001         Available  sailfish    111111    27               \n")
-        self.assertEqual(expected, output)
-        output = self._IssueCommand("list devices --host 0")
-        self.assertEqual(expected, output)
-
-    def testWrongHostIndex(self):
-        """Tests host index out of range."""
-        output = self._IssueCommand("list devices --host 1")
-        expected = "IndexError: "
-        self.assertTrue(output.startswith(expected))
-        output = self._IssueCommand("lease --host 1")
-        self.assertTrue(output.startswith(expected))
-
-    @mock.patch('vts.harnesses.host_controller.'
-        'console.build_flasher.BuildFlasher')
-    def testFetchPOSTAndFlash(self, mock_class):
-        """Tests fetching from pab and flashing."""
-        self._pab_client.GetArtifact.return_value = (
-            {"system.img": "/mock/system.img", "odm.img": "/mock/odm.img"},
-            None)
-        self._IssueCommand(
-            "fetch --branch=aosp-master-ndk --target=darwin_mac "
-            "--account_id=100621237 "
-            "--artifact_name=foo-{id}.tar.bz2 --method=POST"
-        )
-        self._pab_client.GetArtifact.assert_called_with(
-            account_id='100621237',
-            branch='aosp-master-ndk',
-            target='darwin_mac',
-            artifact_name='foo-{id}.tar.bz2',
-            build_id='latest',
-            method='POST')
-
-        flasher = mock.Mock()
-        mock_class.return_value = flasher
-        self._IssueCommand("flash --current system=system.img odm=odm.img")
-        flasher.Flash.assert_called_with(
-            {"system": "/mock/system.img", "odm": "/mock/odm.img"})
-
-    @mock.patch('vts.harnesses.host_controller.'
-        'console.build_flasher.BuildFlasher')
-    def testFlashGSI(self, mock_class):
-        flasher = mock.Mock()
-        mock_class.return_value = flasher
-        self._IssueCommand("flash --gsi=system.img")
-        flasher.FlashGSI.assert_called_with('system.img', None)
-
-    @mock.patch('vts.harnesses.host_controller.'
-        'console.build_flasher.BuildFlasher')
-    def testFlashGSIWithVbmeta(self, mock_class):
-        flasher = mock.Mock()
-        mock_class.return_value = flasher
-        self._IssueCommand("flash --gsi=system.img --vbmeta=vbmeta.img")
-        flasher.FlashGSI.assert_called_with('system.img', 'vbmeta.img')
-
-    @mock.patch('vts.harnesses.host_controller.'
-        'console.build_flasher.BuildFlasher')
-    def testFlashall(self, mock_class):
-        flasher = mock.Mock()
-        mock_class.return_value = flasher
-        self._IssueCommand("flash --build_dir=path/to/dir/")
-        flasher.Flashall.assert_called_with('path/to/dir/')
-
-if __name__ == "__main__":
-    unittest.main()
diff --git a/harnesses/host_controller/host_controller.py b/harnesses/host_controller/host_controller.py
deleted file mode 100644
index d04e65d..0000000
--- a/harnesses/host_controller/host_controller.py
+++ /dev/null
@@ -1,140 +0,0 @@
-#
-# Copyright (C) 2017 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-import logging
-import socket
-import time
-import uuid
-
-import httplib2
-from apiclient import errors
-
-from vts.harnesses.host_controller import invocation_thread
-from vts.harnesses.host_controller.tradefed import remote_operation
-from vts.harnesses.host_controller.tfc import command_attempt
-
-
-class HostController(object):
-    """The class that relays commands between a TradeFed host and clusters.
-
-    Attributes:
-        _remote_client: The RemoteClient which runs commands.
-        _tfc_client: The TfcClient from which the command tasks are leased.
-        _hostname: A string, the name of the TradeFed host.
-        _cluster_ids: A list of strings, the cluster IDs for leasing tasks.
-        _invocation_threads: The list of running InvocationThread.
-    """
-
-    def __init__(self, remote_client, tfc_client, hostname, cluster_ids):
-        """Initializes the attributes."""
-        self._remote_client = remote_client
-        self._tfc_client = tfc_client
-        self._hostname = hostname
-        self._cluster_ids = cluster_ids
-        self._invocation_threads = []
-
-    @property
-    def hostname(self):
-        """Returns the name of the host."""
-        return self._hostname
-
-    def _JoinInvocationThreads(self):
-        """Removes terminated threads from _invocation_threads."""
-        alive_threads = []
-        for inv_thread in self._invocation_threads:
-            inv_thread.join(0)
-            if inv_thread.is_alive():
-                alive_threads.append(inv_thread)
-        self._invocation_threads = alive_threads
-
-    def _CreateInvocationThread(self, task):
-        """Creates an invocation thread from a command task.
-
-        Args:
-            task: The CommandTask object.
-
-        Returns:
-            An InvocationThread.
-        """
-        attempt_id = uuid.uuid4()
-        attempt = command_attempt.CommandAttempt(
-                task.task_id, attempt_id,
-                self._hostname, task.device_serials[0])
-        inv_thread = invocation_thread.InvocationThread(
-                self._remote_client, self._tfc_client, attempt,
-                task.command_line.split(), task.device_serials)
-        return inv_thread
-
-    def ListDevices(self):
-        """Lists present devices on the host.
-
-        Returns:
-            A list of DeviceInfo.
-        """
-        devices = self._remote_client.ListDevices()
-        return [dev for dev in devices if not dev.IsStub()]
-
-    def ListAvailableDevices(self):
-        """Lists available devices for command tasks.
-
-        Returns:
-            A list of DeviceInfo.
-        """
-        self._JoinInvocationThreads()
-        allocated_serials = set()
-        for inv_thread in self._invocation_threads:
-            allocated_serials.update(inv_thread.device_serials)
-
-        present_devices = self.ListDevices()
-        return [dev for dev in present_devices if
-                dev.IsAvailable() and
-                dev.device_serial not in allocated_serials]
-
-    def LeaseCommandTasks(self):
-        """Leases command tasks and creates threads to execute them.
-
-        Returns:
-            A list of CommandTask. The leased command tasks.
-        """
-        available_devices = self.ListAvailableDevices()
-        if not available_devices:
-            return []
-
-        tasks = self._tfc_client.LeaseHostTasks(
-                self._cluster_ids[0], self._cluster_ids[1:],
-                self._hostname, available_devices)
-        for task in tasks:
-            inv_thread = self._CreateInvocationThread(task)
-            inv_thread.daemon = True
-            inv_thread.start()
-            self._invocation_threads.append(inv_thread)
-        return tasks
-
-    def Run(self, poll_interval):
-        """Starts polling TFC for tasks.
-
-        Args:
-            poll_interval: The poll interval in seconds.
-        """
-        while True:
-            try:
-                self.LeaseCommandTasks()
-            except (socket.error,
-                    remote_operation.RemoteOperationException,
-                    httplib2.HttpLib2Error,
-                    errors.HttpError) as e:
-                logging.exception(e)
-            time.sleep(poll_interval)
diff --git a/harnesses/host_controller/host_controller_test.py b/harnesses/host_controller/host_controller_test.py
deleted file mode 100644
index 5eef588..0000000
--- a/harnesses/host_controller/host_controller_test.py
+++ /dev/null
@@ -1,92 +0,0 @@
-#!/usr/bin/env python
-#
-# Copyright (C) 2017 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-import threading
-import unittest
-import time
-
-try:
-    from unittest import mock
-except ImportError:
-    import mock
-
-from vts.harnesses.host_controller import host_controller
-from vts.harnesses.host_controller.tfc import command_task
-from vts.harnesses.host_controller.tfc import device_info
-
-
-class HostControllerTest(unittest.TestCase):
-    """A test for host_controller.HostController.
-
-    Args:
-        _remote_client: A mock remote_client.RemoteClient.
-        _tfc_client: A mock tfc_client.TfcClient.
-        _host_controller: The HostController being tested.
-    """
-    _AVAILABLE_DEVICE = device_info.DeviceInfo(
-            device_serial="ABC001",
-            run_target="sailfish",
-            state="Available")
-    _ALLOCATED_DEVICE = device_info.DeviceInfo(
-            device_serial="ABC002",
-            run_target="sailfish",
-            state="Allocated")
-    _STUB_DEVICE = device_info.DeviceInfo(
-            device_serial="emulator-5554",
-            run_target="unknown",
-            state="Available",
-            stub=True)
-    _DEVICES = [_AVAILABLE_DEVICE, _ALLOCATED_DEVICE, _STUB_DEVICE]
-    _TASKS = [command_task.CommandTask(task_id="1-0",
-                                       command_line="vts -m SampleShellTest",
-                                       device_serials=["ABC001"])]
-
-    def setUp(self):
-        """Creates the HostController."""
-        self._remote_client = mock.Mock()
-        self._tfc_client = mock.Mock()
-        self._host_controller = host_controller.HostController(
-                self._remote_client, self._tfc_client, "host1", ["cluster1"])
-
-    @mock.patch("vts.harnesses.host_controller.invocation_thread."
-                "InvocationThread.run")
-    def testDeviceStateDuringInvocation(self, mock_run):
-        """Tests LeaseHostTasks and ListAvailableDevices."""
-        self._remote_client.ListDevices.return_value = self._DEVICES
-        self._tfc_client.LeaseHostTasks.return_value = self._TASKS
-        run_event = threading.Event()
-        mock_run.side_effect = lambda: run_event.wait()
-
-        self._host_controller.LeaseCommandTasks()
-        devices = self._host_controller.ListAvailableDevices()
-        self.assertEqual([], devices)
-        run_event.set()
-        # Wait for thread termination
-        time.sleep(0.2)
-        devices = self._host_controller.ListAvailableDevices()
-        self.assertEqual([self._AVAILABLE_DEVICE], devices)
-
-    def testListDevices(self):
-        """Tests ListDevices."""
-        self._remote_client.ListDevices.return_value = self._DEVICES
-        devices = self._host_controller.ListDevices()
-        self.assertEqual([self._AVAILABLE_DEVICE, self._ALLOCATED_DEVICE],
-                         devices)
-
-
-if __name__ == "__main__":
-    unittest.main()
diff --git a/harnesses/host_controller/invocation_thread.py b/harnesses/host_controller/invocation_thread.py
deleted file mode 100644
index 9673862..0000000
--- a/harnesses/host_controller/invocation_thread.py
+++ /dev/null
@@ -1,169 +0,0 @@
-#
-# Copyright (C) 2017 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-import logging
-import socket
-import threading
-
-import httplib2
-from apiclient import errors
-
-from vts.harnesses.host_controller.tfc import command_attempt
-from vts.harnesses.host_controller.tradefed import remote_operation
-
-
-class InvocationThread(threading.Thread):
-    """The thread that remotely executes a command task.
-
-    Attributes:
-        _remote_client: The RemoteClient which executes the command.
-        _tfc_client: The TfcClient to which the command events are sent.
-        _attempt: The CommandAttempt whose events are sent to TFC.
-        _command: A list of strings, the command and arguments.
-        device_serials: A list of strings, the serial numbers of the devices
-                        which need to be allocated to the task.
-        _allocated_serials: A list of strings, the serial numbers of the devices
-                            which are successfully allocated.
-        _tfc_heartbeat_interval: The interval of TestRunInProgress events in
-                                 seconds.
-    """
-
-    def __init__(self,
-                 remote_client,
-                 tfc_client,
-                 attempt,
-                 command,
-                 device_serials,
-                 tfc_heartbeat_interval=5 * 60):
-        """Initializes the attributes."""
-        super(InvocationThread, self).__init__()
-        self._remote_client = remote_client
-        self._tfc_client = tfc_client
-        self._attempt = attempt
-        self._command = command
-        self.device_serials = device_serials
-        self._allocated_serials = None
-        # The value in Java implementation is 5 minutes.
-        self._tfc_heartbeat_interval = tfc_heartbeat_interval
-
-    def _AllocateDevices(self):
-        """Allocates all of device_serial."""
-        for serial in self.device_serials:
-            self._remote_client.SendOperation(
-                    remote_operation.AllocateDevice(serial))
-            self._allocated_serials.append(serial)
-
-    def _StartInvocation(self):
-        """Starts executing command and sends the event to TFC."""
-        self._remote_client.SendOperation(
-                remote_operation.ExecuteCommand(self.device_serials[0],
-                                                *self._command))
-        event = self._attempt.CreateCommandEvent(
-                command_attempt.EventType.INVOCATION_STARTED)
-        self._tfc_client.SubmitCommandEvents([event])
-
-    def _WaitForCommandResult(self):
-        """Waits for command result and keeps sending heartbeat to TFC
-
-        Returns:
-            A JSON object returned from TradeFed remote manager.
-        """
-        while True:
-            result = self._remote_client.WaitForCommandResult(
-                    self.device_serials[0], self._tfc_heartbeat_interval)
-            if result:
-                return result
-            event = self._attempt.CreateCommandEvent(
-                    command_attempt.EventType.TEST_RUN_IN_PROGRESS)
-            self._tfc_client.SubmitCommandEvents([event])
-
-    def _CompleteInvocation(self, result):
-        """Sends InvocationCompleted event according to the result.
-
-        Args:
-            result: A JSON object returned from TradeFed remote manager.
-        """
-        if result["status"] == "INVOCATION_SUCCESS":
-            event = self._attempt.CreateInvocationCompletedEvent(
-                    str(result), 1, 0)
-        else:
-            event = self._attempt.CreateInvocationCompletedEvent(
-                    str(result), 1, 1, error=str(result))
-        self._tfc_client.SubmitCommandEvents([event])
-
-    def _FreeAllocatedDevices(self):
-        """Frees allocated devices and tolerates RemoteOperationException."""
-        for serial in self._allocated_serials:
-            try:
-                self._remote_client.SendOperation(
-                        remote_operation.FreeDevice(serial))
-            except remote_operation.RemoteOperationException as e:
-                logging.exception(e)
-            except socket.error as e:
-                logging.exception(e)
-                break
-        self._allocated_serials = []
-
-    def _SubmitErrorEvent(self, event_type, error_msg):
-        """Submits an error event and tolerates http exceptions.
-
-        Args:
-            event_type: A string, the type of the command event.
-            error_msg: A string, the error message.
-        """
-        try:
-            self._tfc_client.SubmitCommandEvents(
-                [self._attempt.CreateCommandEvent(event_type, error_msg)])
-        except (httplib2.HttpLib2Error, errors.HttpError) as e:
-            logging.exception(e)
-
-    # @Override
-    def run(self):
-        """Executes a command task with exception handling."""
-        self._allocated_serials = []
-        last_error = None
-        error_event = command_attempt.EventType.ALLOCATION_FAILED
-        try:
-            self._AllocateDevices()
-            error_event = command_attempt.EventType.EXECUTE_FAILED
-            self._StartInvocation()
-            result = self._WaitForCommandResult()
-            self._CompleteInvocation(result)
-            error_event = None
-        except errors.HttpError as e:
-            logging.exception(e)
-            last_error = e
-        except remote_operation.RemoteOperationException as e:
-            logging.exception(e)
-            last_error = e
-            # ConfigurationException on TradeFed remote manager.
-            if str(e).startswith("Config error: "):
-                error_event = command_attempt.EventType.CONFIGURATION_ERROR
-        except httplib2.HttpLib2Error as e:
-            logging.exception("Cannot communicate with TradeFed cluster: %s\n"
-                              "Skip submitting event %s.", e, error_event)
-            last_error = e
-            error_event = None
-        except socket.error as e:
-            logging.exception("Cannot communicate with TradeFed remote "
-                              "manager: %s\nSkip freeing devices %s.",
-                              e, self._allocated_serials)
-            last_error = e
-            self._allocated_serials = []
-        finally:
-            if error_event:
-                self._SubmitErrorEvent(error_event, str(last_error))
-            self._FreeAllocatedDevices()
diff --git a/harnesses/host_controller/invocation_thread_test.py b/harnesses/host_controller/invocation_thread_test.py
deleted file mode 100644
index c940329..0000000
--- a/harnesses/host_controller/invocation_thread_test.py
+++ /dev/null
@@ -1,148 +0,0 @@
-#!/usr/bin/env python
-#
-# Copyright (C) 2017 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-import unittest
-
-try:
-    from unittest import mock
-except ImportError:
-    import mock
-
-from vts.harnesses.host_controller import invocation_thread
-from vts.harnesses.host_controller.tfc import command_attempt
-from vts.harnesses.host_controller.tradefed import remote_operation
-
-
-class InvocationThreadTest(unittest.TestCase):
-    """A test for invocation_thread.InvocationThread.
-
-    Attributes:
-        _remote_client: A mock remote_client.RemoteClient.
-        _tfc_client: A mock tfc_client.TfcClient.
-        _inv_thread: The InvocationThread being tested.
-    """
-
-    def setUp(self):
-        """Creates the InvocationThread."""
-        self._remote_client = mock.Mock()
-        self._tfc_client = mock.Mock()
-        attempt = command_attempt.CommandAttempt(
-                task_id="321-0",
-                attempt_id="abcd-1234",
-                hostname="host0",
-                device_serial="ABCDEF")
-        command = ["vts", "-m", "SampleShellTest"]
-        serials = ["serial123", "serial456"]
-        self._inv_thread = invocation_thread.InvocationThread(
-                self._remote_client, self._tfc_client,
-                attempt, command, serials)
-
-    def _GetSubmittedEventTypes(self):
-        """Gets the types of the events submitted by the mock TfcClient.
-
-        Returns:
-            A list of strings, the event types.
-        """
-        event_types = []
-        for args, kwargs in self._tfc_client.SubmitCommandEvents.call_args_list:
-            event_types.extend(event["type"] for event in args[0])
-        return event_types
-
-    def _GetSentOperationTypes(self):
-        """Gets the types of the operations sent by the mock RemoteClient.
-
-        Returns:
-            A list of strings, the operation types.
-        """
-        operation_types = [args[0].type for args, kwargs in
-                           self._remote_client.SendOperation.call_args_list]
-        return operation_types
-
-    def testAllocationFailed(self):
-        """Tests AllocationFailed event."""
-        self._remote_client.SendOperation.side_effect = (
-                lambda op: _RaiseExceptionForOperation(op, "ALLOCATE_DEVICE"))
-        self._inv_thread.run()
-        self.assertEqual([command_attempt.EventType.ALLOCATION_FAILED],
-                         self._GetSubmittedEventTypes())
-        self.assertEqual(["ALLOCATE_DEVICE"],
-                         self._GetSentOperationTypes())
-
-    def testExecuteFailed(self):
-        """Tests ExecuteFailed event."""
-        self._remote_client.SendOperation.side_effect = (
-                lambda op: _RaiseExceptionForOperation(op, "EXEC_COMMAND"))
-        self._inv_thread.run()
-        self.assertEqual([command_attempt.EventType.EXECUTE_FAILED],
-                         self._GetSubmittedEventTypes())
-        self.assertEqual(["ALLOCATE_DEVICE",
-                          "ALLOCATE_DEVICE",
-                          "EXEC_COMMAND",
-                          "FREE_DEVICE",
-                          "FREE_DEVICE"],
-                         self._GetSentOperationTypes())
-
-    def testConfigurationError(self):
-        """Tests ConfigurationError event."""
-        self._remote_client.SendOperation.side_effect = (
-                lambda op: _RaiseExceptionForOperation(op, "EXEC_COMMAND",
-                                                       "Config error: test"))
-        self._inv_thread.run()
-        self.assertEqual([command_attempt.EventType.CONFIGURATION_ERROR],
-                         self._GetSubmittedEventTypes())
-        self.assertEqual(["ALLOCATE_DEVICE",
-                          "ALLOCATE_DEVICE",
-                          "EXEC_COMMAND",
-                          "FREE_DEVICE",
-                          "FREE_DEVICE"],
-                         self._GetSentOperationTypes())
-
-    def testInvocationCompleted(self):
-        """Tests InvocationCompleted event."""
-        self._remote_client.WaitForCommandResult.side_effect = (
-                None, {"status": "INVOCATION_SUCCESS"})
-        self._inv_thread.run()
-        self.assertEqual([command_attempt.EventType.INVOCATION_STARTED,
-                          command_attempt.EventType.TEST_RUN_IN_PROGRESS,
-                          command_attempt.EventType.INVOCATION_COMPLETED],
-                         self._GetSubmittedEventTypes())
-        # GET_LAST_COMMAND_RESULT isn't called in mock WaitForCommandResult.
-        self.assertEqual(["ALLOCATE_DEVICE",
-                          "ALLOCATE_DEVICE",
-                          "EXEC_COMMAND",
-                          "FREE_DEVICE",
-                          "FREE_DEVICE"],
-                         self._GetSentOperationTypes())
-
-
-def _RaiseExceptionForOperation(operation, op_type, error_msg="unit test"):
-    """Raises exception for specific operation type.
-
-    Args:
-        operation: A remote_operation.RemoteOperation object.
-        op_type: A string, the expected type.
-        error_msg: The message in the exception.
-
-    Raises:
-        RemoteOperationException if the operation's type matches op_type.
-    """
-    if operation.type == op_type:
-        raise remote_operation.RemoteOperationException(error_msg)
-
-
-if __name__ == "__main__":
-    unittest.main()
diff --git a/harnesses/host_controller/main.py b/harnesses/host_controller/main.py
deleted file mode 100644
index 07daba7..0000000
--- a/harnesses/host_controller/main.py
+++ /dev/null
@@ -1,178 +0,0 @@
-#!/usr/bin/env python
-#
-# Copyright (C) 2017 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-import argparse
-import json
-import logging
-import socket
-import time
-import threading
-import sys
-
-from vts.harnesses.host_controller import console
-from vts.harnesses.host_controller import host_controller
-from vts.harnesses.host_controller.build import pab_client
-from vts.harnesses.host_controller.tfc import tfc_client
-from vts.harnesses.host_controller.tradefed import remote_client
-from vts.utils.python.os import env_utils
-
-_ANDROID_BUILD_TOP = "ANDROID_BUILD_TOP"
-_SECONDS_PER_UNIT = {
-    "m": 60,
-    "h": 60 * 60,
-    "d": 60 * 60 * 24
-}
-
-
-def _ParseInterval(interval_str):
-    """Parses string to time interval.
-
-    Args:
-        interval_str: string, a floating-point number followed by time unit.
-
-    Returns:
-        float, the interval in seconds.
-
-    Raises:
-        ValueError if the argument format is wrong.
-    """
-    if not interval_str:
-        raise ValueError("Argument is empty.")
-
-    unit = interval_str[-1]
-    if unit not in _SECONDS_PER_UNIT:
-        raise ValueError("Unknown unit: %s" % unit)
-
-    interval = float(interval_str[:-1])
-    if interval < 0:
-        raise ValueError("Invalid time interval: %s" % interval)
-
-    return interval * _SECONDS_PER_UNIT[unit]
-
-
-def main():
-    """Parses arguments and starts console."""
-    parser = argparse.ArgumentParser()
-    parser.add_argument("--config-file",
-                        default=None,
-                        type=argparse.FileType('r'),
-                        help="The configuration file in JSON format")
-    parser.add_argument("--poll", action="store_true",
-                        help="Disable console and start host controller "
-                             "threads polling TFC.")
-    parser.add_argument("--use-tfc", action="store_true",
-                        help="Enable TFC (TradeFed Cluster).")
-    parser.add_argument("--script",
-                        default=None,
-                        help="The path to a script file in .py format")
-    parser.add_argument("--loop",
-                        default=None,
-                        metavar="INTERVAL",
-                        type=_ParseInterval,
-                        help="The interval of repeating the script. "
-                             "The format is a float followed by unit which is "
-                             "one of 'm' (minute), 'h' (hour), and 'd' (day). "
-                             "If this option is unspecified, the script will "
-                             "be processed once.")
-    parser.add_argument("--console", action="store_true",
-                        help="Whether to start a console after processing "
-                             "a script.")
-    args = parser.parse_args()
-    if args.config_file:
-        config_json = json.load(args.config_file)
-    else:
-        config_json = {}
-        config_json["log_level"] = "DEBUG"
-        config_json["hosts"] = []
-        host_config = {}
-        host_config["cluster_ids"] = ["local-cluster-1",
-                                      "local-cluster-2"]
-        host_config["lease_interval_sec"] = 30
-        config_json["hosts"].append(host_config)
-
-    env_vars = env_utils.SaveAndClearEnvVars([_ANDROID_BUILD_TOP])
-
-    root_logger = logging.getLogger()
-    root_logger.setLevel(getattr(logging, config_json["log_level"]))
-
-    tfc = None
-    if args.use_tfc:
-        if args.config_file:
-            tfc = tfc_client.CreateTfcClient(
-                    config_json["tfc_api_root"],
-                    config_json["service_key_json_path"],
-                    api_name=config_json["tfc_api_name"],
-                    api_version=config_json["tfc_api_version"],
-                    scopes=config_json["tfc_scopes"])
-        else:
-            print("WARN: If --use_tfc is set, --config_file argument value "
-                  "must be provided. Starting without TFC.")
-
-    pab = pab_client.PartnerAndroidBuildClient()
-
-    hosts = []
-    for host_config in config_json["hosts"]:
-        cluster_ids = host_config["cluster_ids"]
-        # If host name is not specified, use local host.
-        hostname = host_config.get("hostname", socket.gethostname())
-        port = host_config.get("port", remote_client.DEFAULT_PORT)
-        cluster_ids = host_config["cluster_ids"]
-        remote = remote_client.RemoteClient(hostname, port)
-        host = host_controller.HostController(remote, tfc, hostname,
-                                              cluster_ids)
-        hosts.append(host)
-        if args.poll:
-            lease_interval_sec = host_config["lease_interval_sec"]
-            host_thread = threading.Thread(target=host.Run,
-                                           args=(lease_interval_sec,))
-            host_thread.daemon = True
-            host_thread.start()
-
-    if args.poll:
-        while True:
-            sys.stdin.readline()
-    else:
-        main_console = console.Console(tfc, pab, hosts)
-        if args.script:
-            next_start_time = time.time()
-            while main_console.ProcessScript(args.script):
-                if args.loop is None:
-                    break
-                if args.loop == 0:
-                    continue
-                current_time = time.time()
-                skip_cnt = (current_time - next_start_time) // args.loop
-                if skip_cnt >= 1:
-                    logging.warning("Script execution time is longer than "
-                                    "loop interval. Skip %d iteration(s).",
-                                    skip_cnt)
-                next_start_time += (skip_cnt + 1) * args.loop
-                if next_start_time - current_time >= 0:
-                    time.sleep(next_start_time - current_time)
-                else:
-                    logging.error("Unexpected timestamps: current=%f, next=%f",
-                                  current_time, next_start_time)
-            if args.console:
-                main_console.cmdloop()
-        else:  # if not script, the default is console mode.
-            main_console.cmdloop()
-
-    env_utils.RestoreEnvVars(env_vars)
-
-
-if __name__ == "__main__":
-    main()
diff --git a/harnesses/host_controller/tfc/__init__.py b/harnesses/host_controller/tfc/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/harnesses/host_controller/tfc/__init__.py
+++ /dev/null
diff --git a/harnesses/host_controller/tfc/api_message.py b/harnesses/host_controller/tfc/api_message.py
deleted file mode 100644
index d1b618e..0000000
--- a/harnesses/host_controller/tfc/api_message.py
+++ /dev/null
@@ -1,46 +0,0 @@
-#
-# Copyright (C) 2017 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-
-class ApiMessage(object):
-    """The class for the messages defined by TFC API."""
-
-    def __init__(self, all_keys, **kwargs):
-        """Initializes the attributes.
-
-        Args:
-            all_keys: A collection of attribute names.
-            **kwargs: The names and values of the attributes. The names must be
-                      in all_keys.
-
-        Raises:
-            KeyError if any key in kwargs is not in all_keys.
-        """
-        for key, value in kwargs.iteritems():
-            if key not in all_keys:
-                raise KeyError(key)
-            setattr(self, key, value)
-
-    def ToJson(self, keys):
-        """Creates a JSON object containing the specified keys.
-
-        Args:
-            keys: The attributes to be included in the object.
-
-        Returns:
-            A JSON object.
-        """
-        return dict((x, getattr(self, x)) for x in keys if hasattr(self, x))
diff --git a/harnesses/host_controller/tfc/command_attempt.py b/harnesses/host_controller/tfc/command_attempt.py
deleted file mode 100644
index 91b7551..0000000
--- a/harnesses/host_controller/tfc/command_attempt.py
+++ /dev/null
@@ -1,137 +0,0 @@
-#
-# Copyright (C) 2017 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-import time
-
-from vts.harnesses.host_controller.tfc import api_message
-
-
-class EventType(object):
-    """The types of command events."""
-    ALLOCATION_FAILED = "AllocationFailed"
-    CONFIGURATION_ERROR = "ConfigurationError"
-    EXECUTE_FAILED = "ExecuteFailed"
-    FETCH_FAILED = "FetchFailed"
-    INVOCATION_COMPLETED = "InvocationCompleted"
-    INVOCATION_STARTED = "InvocationStarted"
-    TEST_RUN_IN_PROGRESS = "TestRunInProgress"
-
-
-class CommandAttempt(api_message.ApiMessage):
-    """The command attempt defined by TFC API.
-
-    Attributes:
-        _COMMAND_EVENT: The parameters of command_events.submit.
-        _COMMAND_EVENT_DATA: The fields in "data" parameter of command_events.
-        _LIST_ATTEMPT: The fields returned by commandAttempts.list.
-    """
-    _COMMAND_EVENT = {
-            "attempt_id",
-            "data",
-            "device_serial",
-            "hostname",
-            "task_id",
-            "time",
-            "type"}
-    _COMMAND_EVENT_DATA = {
-            "error",
-            "failed_test_count",
-            "summary",
-            "test_run_name",
-            "total_test_count"}
-    _LIST_ATTEMPT = {
-            "attempt_id",
-            "command_id",
-            "create_time",
-            "end_time",
-            "error",
-            "device_serial",
-            "failed_test_count",
-            "hostname",
-            "request_id",
-            "start_time",
-            "state",
-            "status",
-            "summary",
-            "task_id",
-            "total_test_count",
-            "update_time"}
-
-    def __init__(self, task_id, attempt_id, hostname, device_serial, **kwargs):
-        """Initializes the attributes.
-
-        Args:
-            task_id: A string, the task id assigned by the server.
-            attempt_id: A string or UUID, the attempt id generated by the host.
-            hostname: The name of the TradeFed host.
-            device_serial: The serial number of the device.
-            **kwargs: The optional attributes.
-        """
-        super(CommandAttempt, self).__init__(self._LIST_ATTEMPT,
-                                             task_id=task_id,
-                                             attempt_id=str(attempt_id),
-                                             hostname=hostname,
-                                             device_serial=device_serial,
-                                             **kwargs)
-
-    def CreateCommandEvent(self, event_type, error=None, event_time=None):
-        """Creates an event defined by command_events.submit.
-
-        Args:
-            event_type: A string in EventType.
-            error: A string, the error message for *Failed, *Error, and
-                   *Completed events.
-            event_time: A float, Unix timestamp of the event in seconds.
-
-        Returns:
-            A JSON object.
-        """
-        obj = self.ToJson(self._COMMAND_EVENT)
-        obj["type"] = event_type
-        obj["time"] = int(event_time if event_time is not None else time.time())
-        data_obj = self.ToJson(self._COMMAND_EVENT_DATA)
-        if error is not None:
-            data_obj["error"] = error
-        if data_obj:
-            obj["data"] = data_obj
-        return obj
-
-    def CreateInvocationCompletedEvent(self,
-                                       summary,
-                                       total_test_count,
-                                       failed_test_count,
-                                       error=None,
-                                       event_time=None):
-        """Creates an InvocationCompleted event.
-
-        Args:
-            summary: A string, the result of the command.
-            total_test_count: Number of test cases.
-            failed_test_count: Number of failed test cases.
-            error: A string, the error message.
-            event_time: A float, Unix timestamp of the event in seconds.
-
-        Returns:
-            A JSON object.
-        """
-        obj = self.CreateCommandEvent(EventType.INVOCATION_COMPLETED,
-                                      error, event_time)
-        if "data" not in obj:
-            obj["data"] = dict()
-        obj["data"].update({"summary": summary,
-                            "total_test_count": total_test_count,
-                            "failed_test_count": failed_test_count})
-        return obj
diff --git a/harnesses/host_controller/tfc/command_task.py b/harnesses/host_controller/tfc/command_task.py
deleted file mode 100644
index 4c2b4c9..0000000
--- a/harnesses/host_controller/tfc/command_task.py
+++ /dev/null
@@ -1,39 +0,0 @@
-#
-# Copyright (C) 2017 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-from vts.harnesses.host_controller.tfc import api_message
-
-
-class CommandTask(api_message.ApiMessage):
-    """The task of executing a command defined by TFC API.
-
-    Attributes:
-        _LEASE_HOST_TASK: The fields returned by commandAttempts.list.
-    """
-    _LEASE_HOST_TASK = {
-            "request_id",
-            "command_id",
-            "task_id",
-            "command_line",
-            "request_type",
-            "device_serials"}
-
-    def __init__(self, task_id, command_line, device_serials, **kwargs):
-        super(CommandTask, self).__init__(self._LEASE_HOST_TASK,
-                                          task_id=task_id,
-                                          command_line=command_line,
-                                          device_serials=device_serials,
-                                          **kwargs)
diff --git a/harnesses/host_controller/tfc/device_info.py b/harnesses/host_controller/tfc/device_info.py
deleted file mode 100644
index 12a3b06..0000000
--- a/harnesses/host_controller/tfc/device_info.py
+++ /dev/null
@@ -1,105 +0,0 @@
-#
-# Copyright (C) 2017 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-from vts.harnesses.host_controller.tfc import api_message
-from vts.utils.python.controllers import android_device
-
-
-class DeviceInfo(api_message.ApiMessage):
-    """The device information defined by TFC API.
-
-    Attributes:
-        _DEVICE_SNAPSHOT: The parameters of host_events.
-        _LEASE_HOST_TASKS: The parameters of leasehosttasks.
-        _OTHER: The data retrieved from TradeFed host but not used by the API.
-        _ALL_KEYS: Union of above.
-    """
-    _DEVICE_SNAPSHOT = {
-            "battery_level",
-            "build_id",
-            "device_serial",
-            "group_name",
-            "mac_address",
-            "product",
-            "product_variant",
-            "run_target",
-            "sim_operator",
-            "sim_state",
-            "sdk_version",
-            "state"}
-    _LEASE_HOST_TASKS = {
-            "device_serial",
-            "group_name",
-            "run_target",
-            "state"}
-    _OTHER = {"stub"}
-    _ALL_KEYS = (_DEVICE_SNAPSHOT | _LEASE_HOST_TASKS | _OTHER)
-
-    def __init__(self, device_serial, **kwargs):
-        """Initializes the attributes.
-
-        Args:
-            device_serial: The serial number of the device.
-            **kwargs: The optional attributes.
-        """
-        super(DeviceInfo, self).__init__(self._ALL_KEYS,
-                                         device_serial=device_serial, **kwargs)
-
-    def IsAvailable(self):
-        """Returns whether the device is available for running commands.
-
-        Returns:
-            A boolean.
-        """
-        return getattr(self, "state", None) == "Available"
-
-    def IsStub(self):
-        """Returns whether the device is a stub.
-
-        Returns:
-            A boolean.
-        """
-        return getattr(self, "stub", False)
-
-    def ToDeviceSnapshotJson(self):
-        """Converts to the parameter of host_events.
-
-        Returns:
-            A JSON object.
-        """
-        return self.ToJson(self._DEVICE_SNAPSHOT)
-
-    def ToLeaseHostTasksJson(self):
-        """Converts to the parameter of leasehosttasks.
-
-        Returns:
-            A JSON object.
-        """
-        return self.ToJson(self._LEASE_HOST_TASKS)
-
-    def Extend(self, properties):
-        """Adds properties to a DeviceInfo object, using AndroidDevice
-
-        Args:
-            properties: list of strings, list of properties to update
-        """
-        serial = getattr(self, "device_serial", None)
-        ad = android_device.AndroidDevice(serial)
-        for prop in properties:
-            val = getattr(ad, prop, None)
-            if val is None:
-                continue
-            setattr(self, prop, val)
diff --git a/harnesses/host_controller/tfc/request.py b/harnesses/host_controller/tfc/request.py
deleted file mode 100644
index 6a5b3a3..0000000
--- a/harnesses/host_controller/tfc/request.py
+++ /dev/null
@@ -1,74 +0,0 @@
-#
-# Copyright (C) 2017 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-from vts.harnesses.host_controller.tfc import api_message
-
-
-class Request(api_message.ApiMessage):
-    """The requests defined by TFC API.
-
-    Attributes:
-        _BODY: The requests.new parameters that are put into http message body.
-        _PARAMETERS: The requests.new parameters put into http parameters.
-        _ALL_KEYS: Union of above.
-    """
-    _BODY = {
-            "command_line",
-            "user"}
-    _PARAMETERS = {
-            "branch",
-            "build_flavor",
-            "build_id",
-            "build_os",
-            "cluster",
-            "no_build_args",
-            "run_target",
-            "shard_count"
-            "run_count"}
-    _ALL_KEYS = (_BODY | _PARAMETERS)
-
-    def __init__(self, cluster, command_line, run_target, user, **kwargs):
-        """Initializes the attributes.
-
-        Args:
-            cluster: The ID of the cluster to send this request to.
-            command_line: The command to execute on a host.
-            run_target: The target device to run the command.
-            user: The name of the user sending this request.
-            **kwargs: The optional attributes.
-        """
-        super(Request, self).__init__(self._ALL_KEYS,
-                                      cluster=cluster,
-                                      command_line=command_line,
-                                      run_target=run_target,
-                                      user=user,
-                                      **kwargs)
-
-    def GetBody(self):
-        """Returns the http message body.
-
-        Returns:
-            A JSON object.
-        """
-        return self.ToJson(self._BODY)
-
-    def GetParameters(self):
-        """Returns the http parameters.
-
-        Returns:
-            A dict of strings.
-        """
-        return self.ToJson(self._PARAMETERS)
diff --git a/harnesses/host_controller/tfc/tfc_client.py b/harnesses/host_controller/tfc/tfc_client.py
deleted file mode 100644
index 39a0656..0000000
--- a/harnesses/host_controller/tfc/tfc_client.py
+++ /dev/null
@@ -1,176 +0,0 @@
-#
-# Copyright (C) 2017 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-import httplib2
-import logging
-import threading
-import time
-
-from apiclient import discovery
-from apiclient import http
-from oauth2client.service_account import ServiceAccountCredentials
-
-from vts.harnesses.host_controller.tfc import command_task
-
-API_NAME = "tradefed_cluster"
-API_VERSION = "v1"
-SCOPES = ['https://www.googleapis.com/auth/userinfo.email']
-
-
-class TfcClient(object):
-    """The class for accessing TFC API.
-
-    Attributes:
-        _service: The TFC service.
-    """
-
-    def __init__(self, service):
-        self._service = service
-
-    def LeaseHostTasks(self, cluster_id, next_cluster_ids, hostname, device_infos):
-        """Calls leasehosttasks.
-
-        Args:
-            cluster_id: A string, the primary cluster to lease tasks from.
-            next_cluster_ids: A list of Strings, the secondary clusters to lease
-                              tasks from.
-            hostname: A string, the name of the TradeFed host.
-            device_infos: A list of DeviceInfo, the information about the
-                          devices connected to the host.
-
-        Returns:
-            A list of command_task.CommandTask, the leased tasks.
-        """
-        lease = {"hostname": hostname,
-                 "cluster": cluster_id,
-                 "next_cluster_ids": next_cluster_ids,
-                 "device_infos": [x.ToLeaseHostTasksJson()
-                                  for x in device_infos]}
-        logging.info("tasks.leasehosttasks body=%s", lease)
-        tasks = self._service.tasks().leasehosttasks(body=lease).execute()
-        logging.info("tasks.leasehosttasks response=%s", tasks)
-        if "tasks" not in tasks:
-            return []
-        return [command_task.CommandTask(**task) for task in tasks["tasks"]]
-
-    def TestResourceList(self, request_id):
-        """Calls testResource.list.
-
-        Args:
-            request_id: int, id of request to grab resources for
-
-        Returns:
-            A list of TestResources
-        """
-        logging.info("request.testResource.list request_id=%s", request_id)
-        test_resources = self._service.requests().testResource().list(request_id=request_id).execute()
-        logging.info("request.testResource.list response=%s", test_resources)
-        if 'test_resources' not in test_resources:
-            return {}
-        return test_resources['test_resources']
-
-    @staticmethod
-    def CreateDeviceSnapshot(cluster_id, hostname, dev_infos):
-        """Creates a DeviceSnapshot which can be uploaded as host event.
-
-        Args:
-            cluster_id: A string, the cluster to upload snapshot to.
-            hostname: A string, the name of the TradeFed host.
-            dev_infos: A list of DeviceInfo.
-
-        Returns:
-            A JSON object.
-        """
-        obj = {"time": int(time.time()),
-               "data": {},
-               "cluster": cluster_id,
-               "hostname": hostname,
-               "tf_version": "(unknown)",
-               "type": "DeviceSnapshot",
-               "device_infos": [x.ToDeviceSnapshotJson() for x in dev_infos]}
-        return obj
-
-    def SubmitHostEvents(self, host_events):
-        """Calls host_events.submit.
-
-        Args:
-            host_events: A list of JSON objects. Currently DeviceSnapshot is
-                         the only type of host events.
-        """
-        json_obj = {"host_events": host_events}
-        logging.info("host_events.submit body=%s", json_obj)
-        self._service.host_events().submit(body=json_obj).execute()
-
-    def SubmitCommandEvents(self, command_events):
-        """Calls command_events.submit.
-
-        Args:
-            command_events: A list of JSON objects converted from CommandAttempt.
-        """
-        json_obj = {"command_events": command_events}
-        logging.info("command_events.submit body=%s", json_obj)
-        self._service.command_events().submit(body=json_obj).execute()
-
-    def NewRequest(self, request):
-        """Calls requests.new.
-
-        Args:
-            request: An instance of Request.
-
-        Returns:
-            A JSON object, the new request queued in the cluster.
-
-            Sample
-            {'state': 'UNKNOWN',
-             'command_line': 'vts-codelab --run-target sailfish',
-             'id': '2',
-             'user': 'testuser'}
-        """
-        body = request.GetBody()
-        params = request.GetParameters()
-        logging.info("requests.new parameters=%s body=%s", params, body)
-        return self._service.requests().new(body=body, **params).execute()
-
-
-def CreateTfcClient(api_root, oauth2_service_json,
-                    api_name=API_NAME, api_version=API_VERSION, scopes=SCOPES):
-    """Builds an object of TFC service from a given URL.
-
-    Args:
-        api_root: The URL to the service.
-        oauth2_service_json: The path to service account key file.
-
-    Returns:
-        A TfcClient object.
-    """
-    discovery_url = "%s/discovery/v1/apis/%s/%s/rest" % (
-            api_root, api_name, api_version)
-    logging.info("Build service from: %s", discovery_url)
-    credentials = ServiceAccountCredentials.from_json_keyfile_name(
-            oauth2_service_json, scopes=scopes)
-    # httplib2.Http is not thread-safe. Use thread local object.
-    thread_local = threading.local()
-    thread_local.http = credentials.authorize(httplib2.Http())
-    def BuildHttpRequest(unused_http, *args, **kwargs):
-        if not hasattr(thread_local, "http"):
-            thread_local.http = credentials.authorize(httplib2.Http())
-        return http.HttpRequest(thread_local.http, *args, **kwargs)
-
-    service = discovery.build(
-            api_name, api_version, http=thread_local.http,
-            discoveryServiceUrl=discovery_url,
-            requestBuilder=BuildHttpRequest)
-    return TfcClient(service)
diff --git a/harnesses/host_controller/tfc/tfc_client_test.py b/harnesses/host_controller/tfc/tfc_client_test.py
deleted file mode 100644
index 411d039..0000000
--- a/harnesses/host_controller/tfc/tfc_client_test.py
+++ /dev/null
@@ -1,130 +0,0 @@
-#!/usr/bin/env python
-#
-# Copyright (C) 2017 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-import unittest
-
-try:
-    from unittest import mock
-except ImportError:
-    import mock
-
-from vts.harnesses.host_controller.tfc import tfc_client
-from vts.harnesses.host_controller.tfc import command_attempt
-from vts.harnesses.host_controller.tfc import device_info
-from vts.harnesses.host_controller.tfc import request
-
-
-class TfcClientTest(unittest.TestCase):
-    """A test for tfc_client.TfcClient.
-
-    Attributes:
-        _client: The tfc_client.TfcClient being tested.
-        _service: The mock service that _client connects to.
-    """
-    _DEVICE_INFOS = [device_info.DeviceInfo(
-            device_serial="ABCDEF", group_name="group0",
-            run_target="sailfish", state="Available")]
-
-    def setUp(self):
-        """Creates a TFC client connecting to a mock service."""
-        self._service = mock.Mock()
-        self._client = tfc_client.TfcClient(self._service)
-
-    def testNewRequest(self):
-        """Tests requests.new."""
-        req = request.Request(cluster="cluster0",
-                              command_line="vts-codelab",
-                              run_target="sailfish",
-                              user="user0")
-        self._client.NewRequest(req)
-        self._service.assert_has_calls([
-                mock.call.requests().new().execute()])
-
-    def testLeaseHostTasks(self):
-        """Tests tasks.leasehosttasks."""
-        tasks = {"tasks": [{"request_id": "2",
-                            "command_line": "vts-codelab --serial ABCDEF",
-                            "task_id": "1-0",
-                            "device_serials": ["ABCDEF"],
-                            "command_id": "1"}]}
-        self._service.tasks().leasehosttasks().execute.return_value = tasks
-        self._client.LeaseHostTasks("cluster0", ["cluster1", "cluster2"],
-                                    "host0", self._DEVICE_INFOS)
-        self._service.assert_has_calls([
-                mock.call.tasks().leasehosttasks().execute()])
-
-    def testHostEvents(self):
-        """Tests host_events.submit."""
-        device_snapshots = [
-                self._client.CreateDeviceSnapshot("vts-staging", "host0",
-                                                  self._DEVICE_INFOS),
-                self._client.CreateDeviceSnapshot("vts-presubmit", "host0",
-                                                  self._DEVICE_INFOS)]
-        self._client.SubmitHostEvents(device_snapshots)
-        self._service.assert_has_calls([
-                mock.call.host_events().submit().execute()])
-
-    def testCommandEvents(self):
-        """Tests command_events.submit."""
-        cmd = command_attempt.CommandAttempt(
-                task_id="321-0",
-                attempt_id="abcd-1234",
-                hostname="host0",
-                device_serial="ABCDEF")
-        expected_event = {
-                "task_id": "321-0",
-                "attempt_id": "abcd-1234",
-                "hostname": "host0",
-                "device_serial": "ABCDEF",
-                "time": 1}
-
-        normal_event = cmd.CreateCommandEvent(
-                command_attempt.EventType.INVOCATION_STARTED,
-                event_time=1)
-        expected_event["type"] = command_attempt.EventType.INVOCATION_STARTED
-        self.assertDictEqual(expected_event, normal_event)
-
-        error_event = cmd.CreateCommandEvent(
-                command_attempt.EventType.EXECUTE_FAILED,
-                error="unit test", event_time=1.1)
-        expected_event["type"] = command_attempt.EventType.EXECUTE_FAILED
-        expected_event["data"] = {"error":"unit test"}
-        self.assertDictEqual(expected_event, error_event)
-
-        complete_event = cmd.CreateInvocationCompletedEvent(
-                summary="complete", total_test_count=2, failed_test_count=1,
-                error="unit test")
-        expected_event["type"] = command_attempt.EventType.INVOCATION_COMPLETED
-        expected_event["data"] = {"summary": "complete", "error": "unit test",
-                                  "total_test_count": 2, "failed_test_count": 1}
-        del expected_event["time"]
-        self.assertDictContainsSubset(expected_event, complete_event)
-        self.assertIn("time", complete_event)
-
-        self._client.SubmitCommandEvents([
-                normal_event, error_event, complete_event])
-        self._service.assert_has_calls([
-                mock.call.command_events().submit().execute()])
-
-    def testWrongParameter(self):
-        """Tests raising exception for wrong parameter name."""
-        self.assertRaisesRegexp(KeyError, "sdk", device_info.DeviceInfo,
-                                device_serial="123", sdk="25")
-
-
-if __name__ == "__main__":
-    unittest.main()
diff --git a/harnesses/host_controller/tradefed/__init__.py b/harnesses/host_controller/tradefed/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/harnesses/host_controller/tradefed/__init__.py
+++ /dev/null
diff --git a/harnesses/host_controller/tradefed/remote_client.py b/harnesses/host_controller/tradefed/remote_client.py
deleted file mode 100644
index 6427676..0000000
--- a/harnesses/host_controller/tradefed/remote_client.py
+++ /dev/null
@@ -1,135 +0,0 @@
-#
-# Copyright (C) 2017 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-import logging
-import socket
-import time
-
-from vts.harnesses.host_controller.tradefed import remote_operation
-
-LOCALHOST = "localhost"
-DEFAULT_PORT = 30103
-
-
-class RemoteClient(object):
-    """The class for sending remote operations to TradeFed."""
-
-    def __init__(self, host=LOCALHOST, port=DEFAULT_PORT, timeout=None):
-        """Initializes the client of TradeFed remote manager.
-
-        Args:
-            _host: The host name of the remote manager.
-            _port: The port of the remote manager.
-            _timeout: The connect and receive timeout in seconds
-        """
-        self._host = host
-        self._port = port
-        self._timeout = timeout if timeout else socket.getdefaulttimeout()
-
-    def SendOperations(self, *operations):
-        """Sends a list of operations and waits for each response.
-
-        Args:
-            *operations: A list of remote_operation.RemoteOperation objects.
-
-        Returns:
-            A list of JSON objects.
-
-        Raises:
-            socket.error if fails to communicate with remote manager.
-            remote_operation.RemoteOperationException if any operation fails or
-            has no response.
-        """
-        op_socket = socket.create_connection((self._host, self._port),
-                                             self._timeout)
-        logging.info("Connect to %s:%d", self._host, self._port)
-        try:
-            if self._timeout is not None:
-                op_socket.settimeout(self._timeout)
-            ops_str = "\n".join(str(op) for op in operations)
-            logging.info("Send: %s", ops_str)
-            op_socket.send(ops_str)
-            op_socket.shutdown(socket.SHUT_WR)
-            resp_str = ""
-            while True:
-                buf = op_socket.recv(4096)
-                if not buf:
-                    break
-                resp_str += buf
-        finally:
-            op_socket.close()
-        logging.info("Receive: %s", resp_str)
-        resp_lines = [x for x in resp_str.split("\n") if x]
-        if len(resp_lines) != len(operations):
-            raise remote_operation.RemoteOperationException(
-                    "Unexpected number of responses: %d" % len(resp_lines))
-        return [operations[i].ParseResponse(resp_lines[i])
-                for i in range(len(resp_lines))]
-
-    def SendOperation(self, operation):
-        """Sends one operation and waits for its response.
-
-        Args:
-            operation: A remote_operation.RemoteOperation object.
-
-        Returns:
-            A JSON object.
-
-        Raises:
-            socket.error if fails to communicate with remote manager.
-            remote_operation.RemoteOperationException if the operation fails or
-            has no response.
-        """
-        return self.SendOperations(operation)[0]
-
-    def ListDevices(self):
-        """Sends ListDevices operation.
-
-        Returns:
-            A list of device_info.DeviceInfo which are the devices connected to
-            the host.
-        """
-        json_obj = self.SendOperation(remote_operation.ListDevices())
-        return remote_operation.ParseListDevicesResponse(json_obj)
-
-    def WaitForCommandResult(self, serial, timeout, poll_interval=5):
-        """Sends a series of operations to wait until a command finishes.
-
-        Args:
-            serial: The serial number of the device.
-            timeout: A float, the timeout in seconds.
-            poll_interval: A float, the interval of each GetLastCommandResult
-                           operation in seconds.
-
-        Returns:
-            A JSON object which is the result of the command.
-            None if timeout.
-
-            Sample
-            {'status': 'INVOCATION_SUCCESS',
-             'free_device_state': 'AVAILABLE'}
-        """
-        deadline = time.time() + timeout
-        get_result_op = remote_operation.GetLastCommandResult(serial)
-        while True:
-            result = self.SendOperation(get_result_op)
-            if result["status"] != "EXECUTING":
-                return result
-            if time.time() > deadline:
-                return None
-            time.sleep(poll_interval)
-            if time.time() > deadline:
-                return None
diff --git a/harnesses/host_controller/tradefed/remote_client_test.py b/harnesses/host_controller/tradefed/remote_client_test.py
deleted file mode 100644
index c4d2e5d..0000000
--- a/harnesses/host_controller/tradefed/remote_client_test.py
+++ /dev/null
@@ -1,179 +0,0 @@
-#!/usr/bin/env python
-#
-# Copyright (C) 2017 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-import queue
-import socket
-import threading
-import unittest
-
-from vts.harnesses.host_controller.tradefed import remote_client
-from vts.harnesses.host_controller.tradefed import remote_operation
-
-
-class MockRemoteManagerThread(threading.Thread):
-    """A thread which mocks remote manager.
-
-    Attributes:
-        HOST: Local host name.
-        PORT: The port that the remote manager listens to.
-        _remote_mgr_socket: The remote manager socket.
-        _response_queue: A queue.Queue object containing the response strings.
-        _timeout: Socket timeout in seconds.
-        last_error: The exception which caused this thread to terminate.
-    """
-    HOST = remote_client.LOCALHOST
-    PORT = 32123
-
-    def __init__(self, timeout):
-        """Creates and listens to remote manager socket."""
-        super(MockRemoteManagerThread, self).__init__()
-        self._response_queue = queue.Queue()
-        self._timeout = timeout
-        self.last_error = None
-        self._remote_mgr_socket = socket.socket()
-        try:
-            self._remote_mgr_socket.settimeout(self._timeout)
-            self._remote_mgr_socket.bind((self.HOST, self.PORT))
-            self._remote_mgr_socket.listen(1)
-        except socket.error:
-            self._remote_mgr_socket.close()
-
-    def _Respond(self, response_str):
-        """Accepts a client connection and responds.
-
-        Args:
-            response_str: The response string.
-        """
-        (server_socket, client_address) = self._remote_mgr_socket.accept()
-        try:
-            server_socket.settimeout(self._timeout)
-            # Receive until connection is closed
-            while not server_socket.recv(4096):
-                pass
-            server_socket.send(response_str)
-        finally:
-            server_socket.close()
-
-    def AddResponse(self, response_str):
-        """Add a response string to the queue.
-
-        Args:
-            response_str: The response string.
-        """
-        self._response_queue.put_nowait(response_str)
-
-    def CloseSocket(self):
-        """Closes the remote manager socket."""
-        if self._remote_mgr_socket:
-            self._remote_mgr_socket.close()
-            self._remote_mgr_socket = None
-
-    # @Override
-    def run(self):
-        """Sends the queued responses to the clients."""
-        try:
-            while True:
-                response_str = self._response_queue.get()
-                self._response_queue.task_done()
-                if response_str is None:
-                    break
-                self._Respond(response_str)
-        except socket.error as e:
-            self.last_error = e
-        finally:
-            self.CloseSocket()
-
-
-class RemoteClientTest(unittest.TestCase):
-    """A test for remote_client.RemoteClient.
-
-    Attributes:
-        _remote_mgr_thread: An instance of MockRemoteManagerThread.
-        _client: The remote_client.RemoteClient being tested.
-    """
-
-    def setUp(self):
-        """Creates remote manager thread."""
-        self._remote_mgr_thread = MockRemoteManagerThread(5)
-        self._remote_mgr_thread.daemon = True
-        self._remote_mgr_thread.start()
-        self._client = remote_client.RemoteClient(self._remote_mgr_thread.HOST,
-                                                  self._remote_mgr_thread.PORT,
-                                                  5)
-
-    def tearDown(self):
-        """Terminates remote manager thread."""
-        self._remote_mgr_thread.AddResponse(None)
-        self._remote_mgr_thread.join(15)
-        self._remote_mgr_thread.CloseSocket()
-        self.assertFalse(self._remote_mgr_thread.is_alive(),
-                         "Cannot stop remote manager thread.")
-        if self._remote_mgr_thread.last_error:
-            raise self._remote_mgr_thread.last_error
-
-    def testListDevice(self):
-        """Tests ListDevices operation."""
-        self._remote_mgr_thread.AddResponse('{"serials": []}')
-        self._client.ListDevices()
-
-    def testAddCommand(self):
-        """Tests AddCommand operation."""
-        self._remote_mgr_thread.AddResponse('{}')
-        self._client.SendOperation(remote_operation.AddCommand(0, "COMMAND"))
-
-    def testMultipleOperations(self):
-        """Tests sending multiple operations via one connection."""
-        self._remote_mgr_thread.AddResponse('{}\n{}')
-        self._client.SendOperations(remote_operation.ListDevices(),
-                                    remote_operation.ListDevices())
-
-    def testExecuteCommand(self):
-        """Tests executing a command and waiting for result."""
-        self._remote_mgr_thread.AddResponse('{}')
-        self._client.SendOperation(remote_operation.AllocateDevice("serial123"))
-        self._remote_mgr_thread.AddResponse('{}')
-        self._client.SendOperation(remote_operation.ExecuteCommand(
-                "serial123", "vts", "-m", "SampleShellTest"))
-
-        self._remote_mgr_thread.AddResponse('{"status": "EXECUTING"}')
-        result = self._client.WaitForCommandResult("serial123",
-                                                   timeout=0.5, poll_interval=1)
-        self.assertIsNone(result, "Client returns result before command finishes.")
-
-        self._remote_mgr_thread.AddResponse('{"status": "EXECUTING"}')
-        self._remote_mgr_thread.AddResponse('{"status": "INVOCATION_SUCCESS"}')
-        result = self._client.WaitForCommandResult("serial123",
-                                                   timeout=5, poll_interval=1)
-        self._remote_mgr_thread.AddResponse('{}')
-        self._client.SendOperation(remote_operation.FreeDevice("serial123"))
-        self.assertIsNotNone(result, "Client doesn't return command result.")
-
-    def testSocketError(self):
-        """Tests raising exception when socket error occurs."""
-        self.assertRaises(socket.timeout, self._client.ListDevices)
-        self._remote_mgr_thread.AddResponse(None)
-        self.assertRaises(socket.error, self._client.ListDevices)
-
-    def testRemoteOperationException(self):
-        """Tests raising exception when response is an error."""
-        self._remote_mgr_thread.AddResponse('{"error": "unit test"}')
-        self.assertRaises(remote_operation.RemoteOperationException,
-                          self._client.ListDevices)
-
-
-if __name__ == "__main__":
-    unittest.main()
diff --git a/harnesses/host_controller/tradefed/remote_operation.py b/harnesses/host_controller/tradefed/remote_operation.py
deleted file mode 100644
index a398954..0000000
--- a/harnesses/host_controller/tradefed/remote_operation.py
+++ /dev/null
@@ -1,172 +0,0 @@
-#
-# Copyright (C) 2017 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-import json
-
-from vts.harnesses.host_controller.tfc import device_info
-
-
-class RemoteOperationException(Exception):
-    """Raised when remote operation fails."""
-    pass
-
-
-class RemoteOperation(object):
-    """The operation sent to TradeFed remote manager.
-
-    Args:
-        _obj: A JSON object with at least 2 entries, "type" and "version".
-    """
-    CURRENT_PROTOCOL_VERSION = 8
-
-    def __init__(self, type, **kwargs):
-        """Initializes a remote operation.
-
-        Args:
-            type: A string, the type of the operation.
-            **kwargs: The arguments which are specific to the operation type.
-        """
-        self._obj = kwargs
-        self._obj["type"] = type
-        if "version" not in self._obj:
-            self._obj["version"] = self.CURRENT_PROTOCOL_VERSION
-
-    def ParseResponse(self, response_str):
-        """Parses the response to the operation.
-
-        Args:
-            response_str: A JSON string.
-
-        Returns:
-            A JSON object.
-
-        Raises:
-            RemoteOperationException if the response is an error.
-        """
-        response = json.loads(response_str)
-        if "error" in response:
-            raise RemoteOperationException(response["error"])
-        return response
-
-    @property
-    def type(self):
-        """Returns the type of this operation."""
-        return self._obj["type"]
-
-    def __str__(self):
-        """Converts the JSON object to string."""
-        return json.dumps(self._obj)
-
-
-def ListDevices():
-    """Creates an operation of listing devices."""
-    return RemoteOperation("LIST_DEVICES")
-
-
-def ParseListDevicesResponse(json_obj):
-    """Parses ListDevices response to a list of DeviceInfo.
-
-    Sample response:
-    {"serials": [
-        {"product": "unknown", "battery": "0", "variant": "unknown",
-         "stub": True, "state": "Available", "build": "unknown",
-         "serial": "emulator-5554", "sdk": "unknown"},
-    ]}
-
-    Args:
-        json_obj: A JSON object, the response to ListDevices.
-
-    Returns:
-        A list of DeviceInfo object.
-    """
-    dev_infos = []
-    for dev_obj in json_obj["serials"]:
-        if dev_obj["product"] == dev_obj["variant"]:
-            run_target = dev_obj["product"]
-        else:
-            run_target = dev_obj["product"] + ":" + dev_obj["variant"]
-        dev_info = device_info.DeviceInfo(
-                battery_level=dev_obj["battery"],
-                build_id=dev_obj["build"],
-                device_serial=dev_obj["serial"],
-                product=dev_obj["product"],
-                product_variant=dev_obj["variant"],
-                run_target=run_target,
-                sdk_version=dev_obj["sdk"],
-                state=dev_obj["state"],
-                stub=dev_obj["stub"])
-        dev_infos.append(dev_info)
-    return dev_infos
-
-
-def AllocateDevice(serial):
-    """Creates an operation of allocating a device.
-
-    Args:
-        serial: The serial number of the device.
-    """
-    return RemoteOperation("ALLOCATE_DEVICE", serial=serial)
-
-
-def FreeDevice(serial):
-    """Creates an operation of freeing a device.
-
-    Args:
-        serial: The serial number of the device.
-    """
-    return RemoteOperation("FREE_DEVICE", serial=serial)
-
-
-def Close():
-    """Creates an operation of stopping the remote manager."""
-    return RemoteOperation("CLOSE")
-
-
-def AddCommand(time, *command_args):
-    """Creates an operation of adding a command to the queue.
-
-    Args:
-        time: The time in ms that the command has been executing for. The value
-              is non-zero in handover situation.
-        command_args: The command to execute.
-    """
-    return RemoteOperation("ADD_COMMAND", time=time, commandArgs=command_args)
-
-
-def ExecuteCommand(serial, *command_args):
-    """Creates an operation of executing a command on a device.
-
-    Args:
-        serial: The serial number of the device.
-        command_args: The command to execute.
-    """
-    return RemoteOperation(
-            "EXEC_COMMAND", serial=serial, commandArgs=command_args)
-
-
-def GetLastCommandResult(serial):
-    """Creates an operation of getting last EXEC_COMMAND result on a device.
-
-    Sample response:
-    {"status": "INVOCATION_ERROR",
-     "invocation_error": "java.lang.NullPointerException",
-     "free_device_state": "AVAILABLE"
-    }
-
-    Args:
-        serial: The serial number of the device.
-    """
-    return RemoteOperation("GET_LAST_COMMAND_RESULT", serial=serial)
diff --git a/harnesses/tradefed/src/com/android/compatibility/common/tradefed/build/VtsCompatibilityInvocationHelper.java b/harnesses/tradefed/src/com/android/compatibility/common/tradefed/build/VtsCompatibilityInvocationHelper.java
index 6cbf889..0f06866 100644
--- a/harnesses/tradefed/src/com/android/compatibility/common/tradefed/build/VtsCompatibilityInvocationHelper.java
+++ b/harnesses/tradefed/src/com/android/compatibility/common/tradefed/build/VtsCompatibilityInvocationHelper.java
@@ -15,6 +15,8 @@
  */
 package com.android.compatibility.common.tradefed.build;
 
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.log.LogUtil.CLog;
 import com.android.tradefed.testtype.suite.TestSuiteInfo;
 
 import java.io.File;
@@ -31,23 +33,30 @@
      */
     public File getTestsDir() throws FileNotFoundException {
         if (mTestCasesDir == null) {
-            String rootDirPath = null;
-
-            rootDirPath = System.getProperty(String.format("%s_ROOT",
-                    TestSuiteInfo.getInstance().getName()), rootDirPath);
-            if (rootDirPath == null || rootDirPath.trim().equals("")) {
-                throw new IllegalArgumentException(
-                        String.format("Missing install path property %s_ROOT",
-                                TestSuiteInfo.getInstance().getName()));
+            String testCasesRootDirPath;
+            testCasesRootDirPath = System.getenv(
+                    String.format("%s_TESTCASES", TestSuiteInfo.getInstance().getName()));
+            File testCaseDir;
+            if (testCasesRootDirPath != null && !testCasesRootDirPath.trim().equals("")) {
+                testCaseDir = new File(testCasesRootDirPath);
+            } else {
+                String rootDirPath;
+                rootDirPath = System.getProperty(
+                        String.format("%s_ROOT", TestSuiteInfo.getInstance().getName()));
+                if (rootDirPath == null || rootDirPath.trim().equals("")) {
+                    throw new IllegalArgumentException(
+                            String.format("Missing install path property %s_ROOT",
+                                    TestSuiteInfo.getInstance().getName()));
+                }
+                testCaseDir = new File(rootDirPath, "android-vts/testcases");
             }
-
-            File testCaseDir = new File(rootDirPath, "android-vts/testcases");
             if (!testCaseDir.exists()) {
                 throw new FileNotFoundException(String.format(
                         "Root directory doesn't exist %s", testCaseDir.getAbsolutePath()));
             }
-
             mTestCasesDir = testCaseDir.getAbsoluteFile();
+            CLog.d(String.format(
+                    "%s TEST CASES DIR: %s", TestSuiteInfo.getInstance().getName(), mTestCasesDir));
         }
 
         return mTestCasesDir;
diff --git a/harnesses/tradefed/src/com/android/compatibility/common/tradefed/targetprep/VtsFilePusher.java b/harnesses/tradefed/src/com/android/compatibility/common/tradefed/targetprep/VtsFilePusher.java
index a9ceb89..e22c058 100644
--- a/harnesses/tradefed/src/com/android/compatibility/common/tradefed/targetprep/VtsFilePusher.java
+++ b/harnesses/tradefed/src/com/android/compatibility/common/tradefed/targetprep/VtsFilePusher.java
@@ -228,13 +228,15 @@
      */
     @Override
     public File resolveRelativeFilePath(IBuildInfo buildInfo, String fileName) {
+        File f = null;
         try {
-            File f = new File(mInvocationHelper.getTestsDir(),
+            f = new File(mInvocationHelper.getTestsDir(),
                     String.format("%s%s", fileName, mAppendBitness ? mAbi.getBitness() : ""));
-            CLog.logAndDisplay(LogLevel.INFO, "Copying from %s", f.getAbsolutePath());
+            CLog.d("Copying from %s", f.getAbsolutePath());
             return f;
         } catch (FileNotFoundException e) {
-            e.printStackTrace();
+            CLog.e(e);
+            CLog.e("File not found: %s", f);
         }
         return null;
     }
diff --git a/harnesses/tradefed/src/com/android/compatibility/common/tradefed/testtype/CompatibilityTestMultiDevice.java b/harnesses/tradefed/src/com/android/compatibility/common/tradefed/testtype/CompatibilityTestMultiDevice.java
new file mode 100644
index 0000000..99edfec
--- /dev/null
+++ b/harnesses/tradefed/src/com/android/compatibility/common/tradefed/testtype/CompatibilityTestMultiDevice.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compatibility.common.tradefed.testtype;
+
+import com.android.compatibility.common.tradefed.util.RetryFilterHelper;
+import com.android.compatibility.common.tradefed.util.VtsRetryFilterHelper;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.config.ConfigurationException;
+import com.android.tradefed.config.OptionClass;
+import com.android.tradefed.config.OptionCopier;
+import com.android.tradefed.config.OptionSetter;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.testtype.IMultiDeviceTest;
+import com.android.tradefed.testtype.IInvocationContextReceiver;
+import com.android.tradefed.testtype.IRemoteTest;
+
+import java.io.FileNotFoundException;
+import java.util.LinkedList;
+import java.util.Map;
+
+/**
+ * A Test for running Compatibility Suites
+ */
+@OptionClass(alias = "compatibility")
+public class CompatibilityTestMultiDevice extends CompatibilityTest implements IMultiDeviceTest {
+    private Map<ITestDevice, IBuildInfo> mDeviceInfos = null;
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void setDeviceInfos(Map<ITestDevice, IBuildInfo> deviceInfos) {
+        mDeviceInfos = deviceInfos;
+    }
+
+    /**
+     * Create a new {@link CompatibilityTest} that will run a sublist of
+     * modules.
+     */
+    public CompatibilityTestMultiDevice(
+            int totalShards, IModuleRepo moduleRepo, Integer shardIndex) {
+        super(totalShards, moduleRepo, shardIndex);
+    }
+
+    /**
+     * Create a new {@link CompatibilityTestMultiDevice} that will run the default list of
+     * modules.
+     */
+    public CompatibilityTestMultiDevice() {
+        super(1 /* totalShards */, new ModuleRepoMultiDevice(), 0);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected synchronized LinkedList<IModuleDef> initializeModuleRepo()
+            throws DeviceNotAvailableException, FileNotFoundException {
+        ((ModuleRepoMultiDevice) getModuleRepo()).setInvocationContext(getInvocationContext());
+        ((ModuleRepoMultiDevice) getModuleRepo()).setDeviceInfos(mDeviceInfos);
+
+        LinkedList<IModuleDef> modules = super.initializeModuleRepo();
+
+        for (IModuleDef module : modules) {
+            if (module instanceof IMultiDeviceTest) {
+                ((IMultiDeviceTest) module).setDeviceInfos(mDeviceInfos);
+            }
+
+            if (module instanceof IInvocationContextReceiver) {
+                ((IInvocationContextReceiver) module).setInvocationContext(getInvocationContext());
+            }
+        }
+
+        return modules;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public IRemoteTest getTestShard(int shardCount, int shardIndex) {
+        CompatibilityTestMultiDevice test =
+                new CompatibilityTestMultiDevice(shardCount, getModuleRepo(), shardIndex);
+        OptionCopier.copyOptionsNoThrow(this, test);
+        // Set the shard count because the copy option on the previous line
+        // copies over the mShard value
+        try {
+            OptionSetter setter = new OptionSetter(test);
+            setter.setOptionValue("shards", "0");
+        } catch (ConfigurationException e) {
+            CLog.e(e);
+        }
+        return test;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected RetryFilterHelper createRetryFilterHelper(Integer retrySessionId) {
+        return new VtsRetryFilterHelper(getBuildHelper(), retrySessionId, getSubPlan(),
+                getIncludeFilters(), getExcludeFilters(), getAbiName(), getModuleName(),
+                getTestName(), getRetryType());
+    }
+}
\ No newline at end of file
diff --git a/harnesses/tradefed/src/com/android/compatibility/common/tradefed/testtype/ModuleDefMultiDevice.java b/harnesses/tradefed/src/com/android/compatibility/common/tradefed/testtype/ModuleDefMultiDevice.java
new file mode 100644
index 0000000..22907d2
--- /dev/null
+++ b/harnesses/tradefed/src/com/android/compatibility/common/tradefed/testtype/ModuleDefMultiDevice.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compatibility.common.tradefed.testtype;
+
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.config.ConfigurationDescriptor;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.invoker.IInvocationContext;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.result.ITestInvocationListener;
+import com.android.tradefed.targetprep.BuildError;
+import com.android.tradefed.targetprep.multi.IMultiTargetPreparer;
+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.IInvocationContextReceiver;
+import com.android.tradefed.testtype.IMultiDeviceTest;
+import com.android.tradefed.testtype.IRemoteTest;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Container for Compatibility test module info.
+ */
+public class ModuleDefMultiDevice
+        extends ModuleDef implements IMultiDeviceTest, IInvocationContextReceiver {
+    private Map<ITestDevice, IBuildInfo> mDeviceInfos = null;
+    private IInvocationContext mInvocationContext = null;
+    private List<IMultiTargetPreparer> mMultiPreparers = new ArrayList<>();
+
+    public ModuleDefMultiDevice(String name, IAbi abi, IRemoteTest test,
+            List<ITargetPreparer> preparersSingleDevice, List<IMultiTargetPreparer> preparers,
+            ConfigurationDescriptor configurationDescriptor) {
+        super(name, abi, test, preparersSingleDevice, configurationDescriptor);
+
+        boolean hasAbiReceiver = false;
+        for (IMultiTargetPreparer preparer : preparers) {
+            if (preparer instanceof IAbiReceiver) {
+                hasAbiReceiver = true;
+                break;
+            }
+        }
+        for (ITargetPreparer preparer : preparersSingleDevice) {
+            if (preparer instanceof IAbiReceiver) {
+                hasAbiReceiver = true;
+                break;
+            }
+        }
+
+        mMultiPreparers = preparers;
+
+        // Required interfaces:
+        super.checkRequiredInterfaces(hasAbiReceiver);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void setDeviceInfos(Map<ITestDevice, IBuildInfo> deviceInfos) {
+        mDeviceInfos = deviceInfos;
+    }
+
+    /**
+     * Getter method for mDeviceInfos.
+     */
+    public Map<ITestDevice, IBuildInfo> getDeviceInfos() {
+        return mDeviceInfos;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void setInvocationContext(IInvocationContext invocationContext) {
+        mInvocationContext = invocationContext;
+    }
+
+    /**
+     * Getter method for mInvocationContext.
+     */
+    public IInvocationContext getInvocationContext() {
+        return mInvocationContext;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected void prepareTestClass() {
+        super.prepareTestClass();
+
+        IRemoteTest test = getTest();
+        if (test instanceof IMultiDeviceTest) {
+            ((IMultiDeviceTest) test).setDeviceInfos(mDeviceInfos);
+        }
+
+        if (test instanceof IInvocationContextReceiver) {
+            ((IInvocationContextReceiver) test).setInvocationContext(mInvocationContext);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected void runPreparerSetups() throws DeviceNotAvailableException {
+        super.runPreparerSetups();
+
+        for (IMultiTargetPreparer preparer : mMultiPreparers) {
+            String preparerName = preparer.getClass().getCanonicalName();
+            if (!getPreparerWhitelist().isEmpty()
+                    && !getPreparerWhitelist().contains(preparerName)) {
+                CLog.w("Skipping Preparer: %s since it is not in the whitelist %s", preparerName,
+                        getPreparerWhitelist());
+                return;
+            }
+
+            CLog.d("MultiDevicePreparer: %s", preparer.getClass().getSimpleName());
+
+            if (preparer instanceof IAbiReceiver) {
+                ((IAbiReceiver) preparer).setAbi(getAbi());
+            }
+
+            try {
+                preparer.setUp(mInvocationContext);
+            } catch (BuildError e) {
+                // This should only happen for flashing new build
+                CLog.e("Unexpected BuildError from multi-device preparer: %s",
+                        preparer.getClass().getCanonicalName());
+                throw new RuntimeException(e);
+            } catch (TargetSetupError e) {
+                // log preparer class then rethrow & let caller handle
+                CLog.e("TargetSetupError in multi-device preparer: %s",
+                        preparer.getClass().getCanonicalName());
+                throw new RuntimeException(e);
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void run(ITestInvocationListener listener) throws DeviceNotAvailableException {
+        super.run(listener);
+
+        // Tear down
+        for (int i = mMultiPreparers.size() - 1; i >= 0; i--) {
+            IMultiTargetPreparer cleaner = mMultiPreparers.get(i);
+            CLog.d("MultiDeviceCleaner: %s", cleaner.getClass().getSimpleName());
+            cleaner.tearDown(mInvocationContext, null);
+        }
+    }
+}
diff --git a/harnesses/tradefed/src/com/android/compatibility/common/tradefed/testtype/ModuleRepoMultiDevice.java b/harnesses/tradefed/src/com/android/compatibility/common/tradefed/testtype/ModuleRepoMultiDevice.java
new file mode 100644
index 0000000..f1f4bde
--- /dev/null
+++ b/harnesses/tradefed/src/com/android/compatibility/common/tradefed/testtype/ModuleRepoMultiDevice.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compatibility.common.tradefed.testtype;
+
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.config.ConfigurationException;
+import com.android.tradefed.config.IConfiguration;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.invoker.IInvocationContext;
+import com.android.tradefed.targetprep.ITargetPreparer;
+import com.android.tradefed.testtype.IAbi;
+import com.android.tradefed.testtype.IInvocationContextReceiver;
+import com.android.tradefed.testtype.IMultiDeviceTest;
+import com.android.tradefed.testtype.IRemoteTest;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Retrieves Compatibility multi-device test module definitions from the repository.
+ */
+public class ModuleRepoMultiDevice
+        extends ModuleRepo implements IMultiDeviceTest, IInvocationContextReceiver {
+    private Map<ITestDevice, IBuildInfo> mDeviceInfos = null;
+    private IInvocationContext mInvocationContext = null;
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void setDeviceInfos(Map<ITestDevice, IBuildInfo> deviceInfos) {
+        mDeviceInfos = deviceInfos;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void setInvocationContext(IInvocationContext invocationContext) {
+        mInvocationContext = invocationContext;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected void addModuleDef(String name, IAbi abi, IRemoteTest test, String[] configPaths)
+            throws ConfigurationException {
+        // Invokes parser to process the test module config file
+        IConfiguration config = getConfigFactory().createConfigurationFromArgs(configPaths);
+
+        List<ITargetPreparer> preparers = new ArrayList<ITargetPreparer>();
+
+        if (mDeviceInfos.size() <= 1) {
+            preparers = config.getTargetPreparers();
+        }
+
+        addModuleDef(new ModuleDefMultiDevice(name, abi, test, preparers,
+                config.getMultiTargetPreparers(), config.getConfigurationDescription()));
+    }
+}
diff --git a/harnesses/tradefed/src/com/android/compatibility/common/tradefed/util/VtsRetryFilterHelper.java b/harnesses/tradefed/src/com/android/compatibility/common/tradefed/util/VtsRetryFilterHelper.java
new file mode 100644
index 0000000..c573b1c
--- /dev/null
+++ b/harnesses/tradefed/src/com/android/compatibility/common/tradefed/util/VtsRetryFilterHelper.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compatibility.common.tradefed.util;
+
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.compatibility.common.tradefed.util.RetryFilterHelper;
+import com.android.compatibility.common.tradefed.util.RetryType;
+import com.android.compatibility.common.util.IInvocationResult;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import java.util.Set;
+
+/**
+ * Helper for generating --include-filter and --exclude-filter values on compatibility retry.
+ */
+public class VtsRetryFilterHelper extends RetryFilterHelper {
+    /* Instance variables for retrieving results to be retried. */
+    private int mSessionId;
+
+    /**
+     * Constructor for a {@link VtsRetryFilterHelper}.
+     *
+     * @param build a {@link CompatibilityBuildHelper} describing the build.
+     * @param sessionId The ID of the session to retry.
+     * @param subPlan The name of a subPlan to be used. Can be null.
+     * @param includeFilters The include module filters to apply
+     * @param excludeFilters The exclude module filters to apply
+     * @param abiName The name of abi to use. Can be null.
+     * @param moduleName The name of the module to run. Can be null.
+     * @param testName The name of the test to run. Can be null.
+     * @param retryType The type of results to retry. Can be null.
+     */
+    public VtsRetryFilterHelper(CompatibilityBuildHelper build, int sessionId, String subPlan,
+            Set<String> includeFilters, Set<String> excludeFilters, String abiName,
+            String moduleName, String testName, RetryType retryType) {
+        super(build, sessionId, subPlan, includeFilters, excludeFilters, abiName, moduleName,
+                testName, retryType);
+        mSessionId = sessionId;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void validateBuildFingerprint(ITestDevice device) throws DeviceNotAvailableException {
+        IInvocationResult result = getResult();
+        String oldVendorFingerprint = result.getBuildFingerprint();
+        String currentVendorFingerprint = device.getProperty("ro.vendor.build.fingerprint");
+        if (!oldVendorFingerprint.equals(currentVendorFingerprint)) {
+            throw new IllegalArgumentException(
+                    String.format("Device vendor fingerprint must match %s to retry session %d",
+                            oldVendorFingerprint, mSessionId));
+        }
+    }
+}
diff --git a/harnesses/tradefed/src/com/android/tradefed/targetprep/VtsCoveragePreparer.java b/harnesses/tradefed/src/com/android/tradefed/targetprep/VtsCoveragePreparer.java
index 0ae0019..1137500 100644
--- a/harnesses/tradefed/src/com/android/tradefed/targetprep/VtsCoveragePreparer.java
+++ b/harnesses/tradefed/src/com/android/tradefed/targetprep/VtsCoveragePreparer.java
@@ -16,22 +16,25 @@
 
 package com.android.tradefed.targetprep;
 
-import com.android.compatibility.common.tradefed.build.VtsCompatibilityInvocationHelper;
+import com.android.annotations.VisibleForTesting;
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
 import com.android.tradefed.build.IBuildInfo;
 import com.android.tradefed.config.OptionClass;
+import com.android.tradefed.config.Option;
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.targetprep.TargetSetupError;
 import com.android.tradefed.util.CommandResult;
 import com.android.tradefed.util.CommandStatus;
 import com.android.tradefed.util.FileUtil;
 import com.android.tradefed.util.IRunUtil;
 import com.android.tradefed.util.RunUtil;
 import com.android.tradefed.util.VtsVendorConfigFileUtil;
-import java.io.File;
-import java.io.IOException;
-import java.util.NoSuchElementException;
 
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
 /**
  * Preparer class for sanitizer and gcov coverage.
  *
@@ -42,38 +45,61 @@
  */
 @OptionClass(alias = "vts-coverage-preparer")
 public class VtsCoveragePreparer implements ITargetPreparer, ITargetCleaner {
-    private static final long BASE_TIMEOUT = 1000 * 60 * 20; // timeout for fetching artifacts
-    private static final String BUILD_INFO_ARTIFACT = "BUILD_INFO"; // name of build info artifact
-    private static final String GCOV_PROPERTY = "ro.vts.coverage"; // indicates gcov when val is 1
-    private static final String GCOV_ARTIFACT = "%s-coverage-%s.zip"; // gcov coverage artifact
-    private static final String GCOV_FILE_NAME = "gcov.zip"; // gcov zip file to pass to VTS
+    static final long BASE_TIMEOUT = 1000 * 60 * 20; // timeout for fetching artifacts
+    static final String BUILD_INFO_ARTIFACT = "BUILD_INFO"; // name of build info artifact
+    static final String GCOV_PROPERTY = "ro.vts.coverage"; // indicates gcov when val is 1
+    static final String GCOV_ARTIFACT = "%s-coverage-%s.zip"; // gcov coverage artifact
+    static final String GCOV_FILE_NAME = "gcov.zip"; // gcov zip file to pass to VTS
 
-    private static final String SELINUX_DISABLED = "Disabled"; // selinux disabled
-    private static final String SELINUX_ENFORCING = "Enforcing"; // selinux enforcing mode
-    private static final String SELINUX_PERMISSIVE = "Permissive"; // selinux permissive mode
+    static final String SELINUX_DISABLED = "Disabled"; // selinux disabled
+    static final String SELINUX_ENFORCING = "Enforcing"; // selinux enforcing mode
+    static final String SELINUX_PERMISSIVE = "Permissive"; // selinux permissive mode
 
-    private static final String SYMBOLS_ARTIFACT = "%s-symbols-%s.zip"; // symbolized binary zip
-    private static final String SYMBOLS_FILE_NAME = "symbols.zip"; // sancov zip to pass to VTS
-    private static final String SANCOV_FLAVOR = "_asan_coverage"; // sancov device build flavor
+    static final String SYMBOLS_ARTIFACT = "%s-symbols-%s.zip"; // symbolized binary zip
+    static final String SYMBOLS_FILE_NAME = "symbols.zip"; // sancov zip to pass to VTS
+    static final String SANCOV_FLAVOR = "_asan_coverage"; // sancov device build flavor
+
+    // Path to store coverage data on the target.
+    static final String COVERAGE_DATA_PATH = "/data/misc/trace/";
+    // Path to store coverage report.
+    static final String COVERAGE_REPORT_PATH = "coverage_report_path";
 
     // Build key for gcov resources
-    private static final String GCOV_RESOURCES_KEY = "gcov-resources-path-%s";
+    static final String GCOV_RESOURCES_KEY = "gcov-resources-path-%s";
 
     // Buid key for sancov resources
-    private static final String SANCOV_RESOURCES_KEY = "sancov-resources-path-%s";
+    static final String SANCOV_RESOURCES_KEY = "sancov-resources-path-%s";
 
     // Relative path to coverage configure binary in VTS package
-    private static final String COVERAGE_CONFIGURE_SRC = "DATA/bin/vts_coverage_configure";
+    static final String COVERAGE_CONFIGURE_SRC = "DATA/bin/vts_coverage_configure";
 
     // Target path for coverage configure binary, will be removed in teardown
-    private static final String COVERAGE_CONFIGURE_DST = "/data/local/tmp/vts_coverage_configure";
+    static final String COVERAGE_CONFIGURE_DST = "/data/local/tmp/vts_coverage_configure";
 
-    private File mDeviceInfoPath = null; // host path where gcov device artifacts are stored
+    // Default path to store coverage reports.
+    static final String DEFAULT_COVRAGE_REPORT_PATH = "/tmp/vts-coverage-report/";
+
+    // Default path to store coverage resource files locally.
+    static final String DEFAULT_LOCAL_COVERAGE_RESOURCE_PATH = "/tmp/coverage/";
+
+    private File mDeviceInfoPath = null; // host path where coverage device artifacts are stored
     private String mEnforcingState = null; // start state for selinux enforcement
+    private IRunUtil mRunUtil = null;
+
+    @Option(name = "use-local-artifects", description = "Whether to use local artifacts.")
+    private boolean mUseLocalArtifects = false;
+
+    @Option(name = "local-coverage-resource-path",
+            description = "Path to look for local artifacts.")
+    private String mLocalCoverageResourcePath = DEFAULT_LOCAL_COVERAGE_RESOURCE_PATH;
+
+    @Option(name = "coverage-report-dir", description = "Local directory to store coverage report.")
+    private String mCoverageReportDir = null;
 
     /** {@inheritDoc} */
     @Override
-    public void setUp(ITestDevice device, IBuildInfo buildInfo) throws DeviceNotAvailableException {
+    public void setUp(ITestDevice device, IBuildInfo buildInfo)
+            throws DeviceNotAvailableException, TargetSetupError {
         String flavor = device.getBuildFlavor();
         String buildId = device.getBuildId();
 
@@ -88,32 +114,37 @@
         boolean gcovEnabled = (coverageProperty != null) && coverageProperty.equals("1");
 
         if (!sancovEnabled && !gcovEnabled) {
+            CLog.d("Coverage disabled.");
             return;
         }
         if (sancovEnabled) {
-            CLog.i("Sanitizer coverage processing enabled.");
+            CLog.d("Sanitizer coverage processing enabled.");
         }
         if (gcovEnabled) {
-            CLog.i("Gcov coverage processing enabled.");
+            CLog.d("Gcov coverage processing enabled.");
         }
 
-        IRunUtil runUtil = new RunUtil();
-        VtsCompatibilityInvocationHelper invocationHelper = new VtsCompatibilityInvocationHelper();
+        if (mRunUtil == null) {
+            mRunUtil = new RunUtil();
+        }
 
-        try {
+        CompatibilityBuildHelper buildHelper = createBuildHelper(buildInfo);
+        if (!mUseLocalArtifects) {
             // Load the vendor configuration
-            String artifactFetcher = null;
-            VtsVendorConfigFileUtil configFileUtil = new VtsVendorConfigFileUtil();
-            if (configFileUtil.LoadVendorConfig(buildInfo)) {
-                artifactFetcher = configFileUtil.GetVendorConfigVariable("build_artifact_fetcher");
-            }
+            String artifactFetcher = getArtifactFetcher(buildInfo);
             if (artifactFetcher == null) {
-                CLog.e("Vendor configuration with build_artifact_fetcher required.");
-                return;
+                throw new TargetSetupError(
+                        "Vendor configuration with build_artifact_fetcher required.");
             }
 
-            // Create a temporary coverage directory
-            mDeviceInfoPath = FileUtil.createTempDir(device.getSerialNumber());
+            try {
+                // Create a temporary coverage directory
+                mDeviceInfoPath = createTempDir(device);
+            } catch (IOException e) {
+                cleanupCoverageData(device);
+                throw new TargetSetupError(
+                        "Failed to create temp dir to store coverage resource files.");
+            }
 
             if (sancovEnabled) {
                 // Fetch the symbolized binaries
@@ -124,11 +155,11 @@
                 String cmdString = String.format(artifactFetcher, buildId, flavor, artifactName,
                         artifactFile.getAbsolutePath().toString());
                 String[] cmd = cmdString.split("\\s+");
-                CommandResult commandResult = runUtil.runTimedCmd(BASE_TIMEOUT, cmd);
+                CommandResult commandResult = mRunUtil.runTimedCmd(BASE_TIMEOUT, cmd);
                 if (commandResult == null || commandResult.getStatus() != CommandStatus.SUCCESS
                         || !artifactFile.exists()) {
-                    CLog.e("Could not fetch unstripped binaries.");
-                    return;
+                    cleanupCoverageData(device);
+                    throw new TargetSetupError("Could not fetch unstripped binaries.");
                 }
             }
 
@@ -141,11 +172,11 @@
                 String cmdString = String.format(artifactFetcher, buildId, flavor, artifactName,
                         artifactFile.getAbsolutePath().toString());
                 String[] cmd = cmdString.split("\\s+");
-                CommandResult commandResult = runUtil.runTimedCmd(BASE_TIMEOUT, cmd);
+                CommandResult commandResult = mRunUtil.runTimedCmd(BASE_TIMEOUT, cmd);
                 if (commandResult == null || commandResult.getStatus() != CommandStatus.SUCCESS
                         || !artifactFile.exists()) {
-                    CLog.e("Could not fetch gcov build artifacts.");
-                    return;
+                    cleanupCoverageData(device);
+                    throw new TargetSetupError("Could not fetch gcov build artifacts.");
                 }
             }
 
@@ -153,37 +184,65 @@
             String cmdString = String.format(artifactFetcher, buildId, flavor, BUILD_INFO_ARTIFACT,
                     mDeviceInfoPath.getAbsolutePath().toString());
             String[] cmd = cmdString.split("\\s+");
-            CommandResult commandResult = runUtil.runTimedCmd(BASE_TIMEOUT, cmd);
+            CommandResult commandResult = mRunUtil.runTimedCmd(BASE_TIMEOUT, cmd);
             File artifactFile = new File(mDeviceInfoPath, BUILD_INFO_ARTIFACT);
             if (commandResult == null || commandResult.getStatus() != CommandStatus.SUCCESS
                     || !artifactFile.exists()) {
-                CLog.e("Could not fetch build info.");
-                mDeviceInfoPath = null;
-                return;
+                cleanupCoverageData(device);
+                throw new TargetSetupError("Could not fetch build info.");
             }
+        } else {
+            mDeviceInfoPath = new File(mLocalCoverageResourcePath);
+            String fileName = sancovEnabled ? SYMBOLS_FILE_NAME : GCOV_FILE_NAME;
+            File artifactFile = new File(mDeviceInfoPath, fileName);
+            if (!artifactFile.exists()) {
+                cleanupCoverageData(device);
+                throw new TargetSetupError(String.format("Could not find %s under %s.",
+                        GCOV_FILE_NAME, mDeviceInfoPath.getAbsolutePath()));
+            }
+            File buildInfoArtifact = new File(mDeviceInfoPath, BUILD_INFO_ARTIFACT);
+            if (!buildInfoArtifact.exists()) {
+                cleanupCoverageData(device);
+                throw new TargetSetupError(String.format("Could not find %s under %s.",
+                        BUILD_INFO_ARTIFACT, mDeviceInfoPath.getAbsolutePath()));
+            }
+        }
 
-            // Push the sancov flushing tool
-            File configureSrc = new File(invocationHelper.getTestsDir(), COVERAGE_CONFIGURE_SRC);
+        try {
+            // Push the coverage configure tool
+            File configureSrc = new File(buildHelper.getTestsDir(), COVERAGE_CONFIGURE_SRC);
             device.pushFile(configureSrc, COVERAGE_CONFIGURE_DST);
-            device.executeShellCommand("rm -rf /data/misc/trace/*");
-            mEnforcingState = device.executeShellCommand("getenforce");
-            if (!mEnforcingState.equals(SELINUX_DISABLED)
-                    && !mEnforcingState.equals(SELINUX_PERMISSIVE)) {
-                device.executeShellCommand("setenforce " + SELINUX_PERMISSIVE);
-            }
+        } catch (FileNotFoundException e) {
+            cleanupCoverageData(device);
+            throw new TargetSetupError("Failed to push the vts coverage configure tool.");
+        }
 
-            if (sancovEnabled) {
-                buildInfo.setFile(
-                        getSancovResourceDirKey(device), mDeviceInfoPath, buildInfo.getBuildId());
+        if (mCoverageReportDir != null) {
+            try {
+                File resultDir = buildHelper.getResultDir();
+                File coverageDir = new File(resultDir, mCoverageReportDir);
+                buildInfo.addBuildAttribute(COVERAGE_REPORT_PATH, coverageDir.getAbsolutePath());
+            } catch (FileNotFoundException e) {
+                cleanupCoverageData(device);
+                throw new TargetSetupError("Failed to get coverageDir.");
             }
+        }
 
-            if (gcovEnabled) {
-                buildInfo.setFile(
-                        getGcovResourceDirKey(device), mDeviceInfoPath, buildInfo.getBuildId());
-            }
-        } catch (IOException | NoSuchElementException e) {
-            CLog.e("Could not set up sanitizer coverage: " + e.toString());
-            mDeviceInfoPath = null;
+        device.executeShellCommand(String.format("rm -rf %s/*", COVERAGE_DATA_PATH));
+        mEnforcingState = device.executeShellCommand("getenforce");
+        if (!mEnforcingState.equals(SELINUX_DISABLED)
+                && !mEnforcingState.equals(SELINUX_PERMISSIVE)) {
+            device.executeShellCommand("setenforce " + SELINUX_PERMISSIVE);
+        }
+
+        if (sancovEnabled) {
+            buildInfo.setFile(
+                    getSancovResourceDirKey(device), mDeviceInfoPath, buildInfo.getBuildId());
+        }
+
+        if (gcovEnabled) {
+            buildInfo.setFile(
+                    getGcovResourceDirKey(device), mDeviceInfoPath, buildInfo.getBuildId());
         }
     }
 
@@ -191,15 +250,10 @@
     @Override
     public void tearDown(ITestDevice device, IBuildInfo buildInfo, Throwable e)
             throws DeviceNotAvailableException {
-        // Clear the temporary directories.
         if (mEnforcingState != null && !mEnforcingState.equals(SELINUX_DISABLED)) {
             device.executeShellCommand("setenforce " + mEnforcingState);
         }
-        if (mDeviceInfoPath != null) {
-            FileUtil.recursiveDelete(mDeviceInfoPath);
-            device.executeShellCommand("rm -r " + COVERAGE_CONFIGURE_DST);
-        }
-        device.executeShellCommand("rm -rf /data/misc/trace/*");
+        cleanupCoverageData(device);
     }
 
     /**
@@ -221,4 +275,39 @@
     public static String getGcovResourceDirKey(ITestDevice device) {
         return String.format(GCOV_RESOURCES_KEY, device.getSerialNumber());
     }
+
+    /**
+     * Cleanup the coverage data on target and host.
+     *
+     * @param device the target device.
+     */
+    private void cleanupCoverageData(ITestDevice device) throws DeviceNotAvailableException {
+        if (mDeviceInfoPath != null) {
+            FileUtil.recursiveDelete(mDeviceInfoPath);
+        }
+        device.executeShellCommand("rm -r " + COVERAGE_CONFIGURE_DST);
+        device.executeShellCommand(String.format("rm -rf %s/*", COVERAGE_DATA_PATH));
+    }
+
+    @VisibleForTesting
+    File createTempDir(ITestDevice device) throws IOException {
+        return FileUtil.createTempDir(device.getSerialNumber());
+    }
+
+    @VisibleForTesting
+    String getArtifactFetcher(IBuildInfo buildInfo) {
+        VtsVendorConfigFileUtil configFileUtil = new VtsVendorConfigFileUtil();
+        if (configFileUtil.LoadVendorConfig(buildInfo)) {
+            return configFileUtil.GetVendorConfigVariable("build_artifact_fetcher");
+        }
+        return null;
+    }
+
+    /**
+     * Create and return a {@link CompatibilityBuildHelper} to use during the preparer.
+     */
+    @VisibleForTesting
+    CompatibilityBuildHelper createBuildHelper(IBuildInfo buildInfo) {
+        return new CompatibilityBuildHelper(buildInfo);
+    }
 }
diff --git a/harnesses/tradefed/src/com/android/tradefed/targetprep/VtsDeviceInfoCollector.java b/harnesses/tradefed/src/com/android/tradefed/targetprep/VtsDeviceInfoCollector.java
index 8369a2d..49994fb 100644
--- a/harnesses/tradefed/src/com/android/tradefed/targetprep/VtsDeviceInfoCollector.java
+++ b/harnesses/tradefed/src/com/android/tradefed/targetprep/VtsDeviceInfoCollector.java
@@ -17,26 +17,27 @@
 package com.android.tradefed.targetprep;
 
 import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.config.Option;
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.targetprep.BuildError;
-import com.android.tradefed.targetprep.TargetSetupError;
 import com.android.tradefed.util.ArrayUtil;
-
-import java.io.FileNotFoundException;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Map.Entry;
 
 /**
- * Collects device info.
- * This's a fork of DeviceInfoCollector and is forked in order to simplify the change
- * deployment process and reduce the deployment time, which are critical for VTS services.
+ * Collects device info. This's a fork of DeviceInfoCollector and is forked in order to simplify the
+ * change deployment process and reduce the deployment time, which are critical for VTS services.
  */
-public class VtsDeviceInfoCollector implements ITargetPreparer {
-
+public class VtsDeviceInfoCollector implements ITargetPreparer, ITargetCleaner {
     // TODO(trong): remove "cts:" prefix, will need a custom ResultReporter.
     private static final Map<String, String> BUILD_KEYS = new HashMap<>();
+    private static final Map<String, String> BUILD_LEGACY_PROPERTIES = new HashMap<>();
+    private static final long REBOOT_TIMEOUT = 1000 * 60 * 2; // 2 minutes.
+
+    @Option(name = "disable-framework", description = "Initialize device by stopping framework.")
+    private boolean mDisableFramework = false;
+
     static {
         BUILD_KEYS.put("cts:build_id", "ro.build.id");
         BUILD_KEYS.put("cts:build_product", "ro.product.name");
@@ -61,16 +62,46 @@
         BUILD_KEYS.put("cts:build_reference_fingerprint", "ro.build.reference.fingerprint");
         BUILD_KEYS.put("cts:build_system_fingerprint", "ro.build.fingerprint");
         BUILD_KEYS.put("cts:build_vendor_fingerprint", "ro.vendor.build.fingerprint");
-        BUILD_KEYS.put("cts:build_vendor_manufacturer", "ro.vendor.product.manufacturer");
-        BUILD_KEYS.put("cts:build_vendor_model", "ro.vendor.product.model");
+        BUILD_KEYS.put("cts:build_vendor_manufacturer", "ro.product.vendor.manufacturer");
+        BUILD_KEYS.put("cts:build_vendor_model", "ro.product.vendor.model");
+
+        BUILD_LEGACY_PROPERTIES.put(
+                "ro.product.vendor.manufacturer", "ro.vendor.product.manufacturer");
+        BUILD_LEGACY_PROPERTIES.put("ro.product.vendor.model", "ro.vendor.product.model");
     }
 
     @Override
-    public void setUp(ITestDevice device, IBuildInfo buildInfo) throws TargetSetupError,
-            BuildError, DeviceNotAvailableException {
+    public void setUp(ITestDevice device, IBuildInfo buildInfo)
+            throws TargetSetupError, BuildError, DeviceNotAvailableException {
         for (Entry<String, String> entry : BUILD_KEYS.entrySet()) {
-            buildInfo.addBuildAttribute(entry.getKey(),
-                    ArrayUtil.join(",", device.getProperty(entry.getValue())));
+            String propertyValue = device.getProperty(entry.getValue());
+            if ((propertyValue == null || propertyValue.length() == 0)
+                    && BUILD_LEGACY_PROPERTIES.containsKey(entry.getValue())) {
+                propertyValue = device.getProperty(BUILD_LEGACY_PROPERTIES.get(entry.getValue()));
+            }
+            buildInfo.addBuildAttribute(entry.getKey(), ArrayUtil.join(",", propertyValue));
+        }
+
+        if (mDisableFramework) {
+            // Set the default device runtime state to stopped.
+            device.executeShellCommand("stop");
+            device.executeShellCommand("setprop sys.boot_completed 0");
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void tearDown(ITestDevice device, IBuildInfo buildInfo, Throwable e)
+            throws DeviceNotAvailableException {
+        if (mDisableFramework) {
+            // Restore the framework at test run completion.
+            long startTime = System.currentTimeMillis();
+            device.waitForDeviceOnline(REBOOT_TIMEOUT);
+            device.executeShellCommand("start");
+            if (!device.waitForBootComplete(
+                        REBOOT_TIMEOUT + startTime - System.currentTimeMillis())) {
+                throw new DeviceNotAvailableException("Framework irrecoverable after testing.");
+            }
         }
     }
 }
diff --git a/harnesses/tradefed/src/com/android/tradefed/targetprep/VtsHalAdapterPreparer.java b/harnesses/tradefed/src/com/android/tradefed/targetprep/VtsHalAdapterPreparer.java
new file mode 100644
index 0000000..9dfa63c
--- /dev/null
+++ b/harnesses/tradefed/src/com/android/tradefed/targetprep/VtsHalAdapterPreparer.java
@@ -0,0 +1,237 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tradefed.targetprep;
+
+import com.android.compatibility.common.tradefed.build.VtsCompatibilityInvocationHelper;
+import com.android.annotations.VisibleForTesting;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.config.Option;
+import com.android.tradefed.config.OptionClass;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.invoker.IInvocationContext;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.targetprep.TargetSetupError;
+import com.android.tradefed.targetprep.multi.IMultiTargetPreparer;
+import com.android.tradefed.testtype.IAbi;
+import com.android.tradefed.testtype.IAbiReceiver;
+import com.android.tradefed.util.CmdUtil;
+import com.android.tradefed.util.FileUtil;
+
+import org.junit.Assert;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.NoSuchElementException;
+import java.util.Vector;
+import java.util.function.Predicate;
+
+/**
+ * Starts and stops a HAL (Hardware Abstraction Layer) adapter.
+ */
+@OptionClass(alias = "vts-hal-adapter-preparer")
+public class VtsHalAdapterPreparer
+        implements ITargetPreparer, ITargetCleaner, IMultiTargetPreparer, IAbiReceiver {
+    static final int THREAD_COUNT_DEFAULT = 1;
+
+    static final String HAL_INTERFACE_SEP = "::";
+    static final String HAL_INSTANCE_SEP = "/";
+    // Relative path to vts native tests directory.
+    static final String VTS_NATIVE_TEST_DIR = "DATA/nativetest%s/";
+    // Path of native tests directory on target device.
+    static final String TARGET_NATIVE_TEST_DIR = "/data/nativetest%s/";
+    // Sysprop to stop HIDL adapaters. Currently, there's one global flag for all adapters.
+    static final String ADAPTER_SYSPROP = "test.hidl.adapters.deactivated";
+    // The wrapper script to start an adapter binary in the background.
+    static final String SCRIPT_PATH = "/data/local/tmp/vts_adapter.sh";
+    // Command to list the registered instance for the given hal@version.
+    static final String LIST_HAL_CMD =
+            "lshal -ti --neat | grep -E '^hwbinder' | awk '{print $2}' | grep %s";
+
+    @Option(name = "adapter-binary-name",
+            description = "Adapter binary file name (typically under /data/nativetest*/)")
+    private String mAdapterBinaryName = null;
+
+    @Option(name = "hal-package-name", description = "Target hal to adapter")
+    private String mPackageName = null;
+
+    @Option(name = "thread-count", description = "HAL adapter's thread count")
+    private int mThreadCount = THREAD_COUNT_DEFAULT;
+
+    // Application Binary Interface (ABI) info of the current test run.
+    private IAbi mAbi = null;
+
+    // CmdUtil help to verify the cmd results.
+    private CmdUtil mCmdUtil = null;
+    // Predicates to stop retrying cmd.
+    private Predicate<String> mCheckEmpty = (String str) -> {
+        return str.isEmpty();
+    };
+    private Predicate<String> mCheckNonEmpty = (String str) -> {
+        return !str.isEmpty();
+    };
+    private Vector<String> mCommands = new Vector<String>();
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void setUp(ITestDevice device, IBuildInfo buildInfo)
+            throws TargetSetupError, BuildError, DeviceNotAvailableException, RuntimeException {
+        String bitness =
+                (mAbi != null) ? ((mAbi.getBitness() == "32") ? "" : mAbi.getBitness()) : "";
+        try {
+            pushAdapter(device, bitness);
+        } catch (IOException | NoSuchElementException e) {
+            CLog.e("Could not push adapter: " + e.toString());
+            throw new TargetSetupError("Could not push adapter.");
+        }
+
+        mCmdUtil = mCmdUtil != null ? mCmdUtil : new CmdUtil();
+        mCmdUtil.setSystemProperty(device, ADAPTER_SYSPROP, "false");
+
+        String out = device.executeShellCommand(String.format(LIST_HAL_CMD, mPackageName));
+        for (String line : out.split("\n")) {
+            if (!line.isEmpty()) {
+                if (!line.contains(HAL_INTERFACE_SEP)) {
+                    throw new RuntimeException("HAL instance with wrong format.");
+                }
+                String interfaceInstance = line.split(HAL_INTERFACE_SEP, 2)[1];
+                if (!interfaceInstance.contains(HAL_INSTANCE_SEP)) {
+                    throw new RuntimeException("HAL instance with wrong format.");
+                }
+                String interfaceName = interfaceInstance.split(HAL_INSTANCE_SEP, 2)[0];
+                String instanceName = interfaceInstance.split(HAL_INSTANCE_SEP, 2)[1];
+                // starts adapter
+                String command = String.format("%s /data/nativetest%s/%s %s %s %d", SCRIPT_PATH,
+                        bitness, mAdapterBinaryName, interfaceName, instanceName, mThreadCount);
+                CLog.d("Trying to adapter for %s",
+                        mPackageName + "::" + interfaceName + "/" + instanceName);
+                mCommands.add(command);
+            }
+        }
+        if (mCommands.isEmpty()) {
+            CLog.w("The specific HAL service is not running.");
+            return;
+        }
+        if (!mCmdUtil.retry(
+                    device, mCommands, String.format(LIST_HAL_CMD, mPackageName), mCheckEmpty)) {
+            throw new TargetSetupError("HAL adapter setup failed.");
+        }
+
+        mCmdUtil.restartFramework(device);
+        if (!mCmdUtil.waitCmdResultWithDelay(
+                    device, "service list | grep IPackageManager", mCheckNonEmpty)) {
+            throw new TargetSetupError("Failed to start package service");
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void setUp(IInvocationContext context)
+            throws TargetSetupError, BuildError, DeviceNotAvailableException {
+        setUp(context.getDevices().get(0), context.getBuildInfos().get(0));
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void tearDown(ITestDevice device, IBuildInfo buildInfo, Throwable e)
+            throws DeviceNotAvailableException {
+        if (!mCommands.isEmpty()) {
+            // stops adapter(s)
+            String command = String.format("setprop %s %s", ADAPTER_SYSPROP, "true");
+            mCmdUtil = mCmdUtil != null ? mCmdUtil : new CmdUtil();
+            Assert.assertTrue("HAL restore failed.",
+                    mCmdUtil.retry(device, command, String.format(LIST_HAL_CMD, mPackageName),
+                            mCheckNonEmpty, mCommands.size() + mCmdUtil.MAX_RETRY_COUNT));
+
+            // TODO: cleanup the pushed adapter files.
+            mCmdUtil.restartFramework(device);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void tearDown(IInvocationContext context, Throwable e)
+            throws DeviceNotAvailableException {
+        tearDown(context.getDevices().get(0), context.getBuildInfos().get(0), e);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void setAbi(IAbi abi) {
+        mAbi = abi;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public IAbi getAbi() {
+        return mAbi;
+    }
+
+    /**
+     * Push the required adapter binary to device.
+     *
+     * @param device device object.
+     * @param bitness ABI bitness.
+     * @throws DeviceNotAvailableException.
+     * @throws IOException.
+     * @throws NoSuchElementException.
+     */
+    private void pushAdapter(ITestDevice device, String bitness)
+            throws DeviceNotAvailableException, IOException, NoSuchElementException {
+        VtsCompatibilityInvocationHelper invocationHelper = createVtsHelper();
+        File adapterDir = new File(
+                invocationHelper.getTestsDir(), String.format(VTS_NATIVE_TEST_DIR, bitness));
+        File adapter = FileUtil.findFile(adapterDir, mAdapterBinaryName);
+        if (adapter != null) {
+            CLog.d("Pushing %s", mAdapterBinaryName);
+            device.pushFile(
+                    adapter, String.format(TARGET_NATIVE_TEST_DIR, bitness) + mAdapterBinaryName);
+        } else {
+            throw new NoSuchElementException("Could not find adapter: " + mAdapterBinaryName);
+        }
+    }
+
+    /**
+     * Create and return a {@link VtsCompatibilityInvocationHelper} to use during the preparer.
+     */
+    @VisibleForTesting
+    VtsCompatibilityInvocationHelper createVtsHelper() {
+        return new VtsCompatibilityInvocationHelper();
+    }
+
+    @VisibleForTesting
+    void setCmdUtil(CmdUtil cmdUtil) {
+        mCmdUtil = cmdUtil;
+    }
+
+    @VisibleForTesting
+    void addCommand(String command) {
+        mCommands.add(command);
+    }
+}
diff --git a/harnesses/tradefed/src/com/android/tradefed/targetprep/VtsPythonVirtualenvPreparer.java b/harnesses/tradefed/src/com/android/tradefed/targetprep/VtsPythonVirtualenvPreparer.java
index fc425c9..e269435 100644
--- a/harnesses/tradefed/src/com/android/tradefed/targetprep/VtsPythonVirtualenvPreparer.java
+++ b/harnesses/tradefed/src/com/android/tradefed/targetprep/VtsPythonVirtualenvPreparer.java
@@ -16,33 +16,33 @@
 
 package com.android.tradefed.targetprep;
 
+import com.android.annotations.VisibleForTesting;
 import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.command.remote.DeviceDescriptor;
 import com.android.tradefed.config.Option;
 import com.android.tradefed.config.OptionClass;
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.invoker.IInvocationContext;
 import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.targetprep.multi.IMultiTargetPreparer;
 import com.android.tradefed.util.CommandResult;
 import com.android.tradefed.util.CommandStatus;
+import com.android.tradefed.util.EnvUtil;
 import com.android.tradefed.util.FileUtil;
 import com.android.tradefed.util.IRunUtil;
 import com.android.tradefed.util.RunUtil;
-import com.android.tradefed.util.StreamUtil;
+import com.android.tradefed.util.VtsFileUtil;
+import com.android.tradefed.util.VtsPythonRunnerHelper;
 import com.android.tradefed.util.VtsVendorConfigFileUtil;
 
-import org.json.JSONException;
-import org.json.JSONObject;
-
 import java.io.File;
-import java.io.InputStream;
+import java.io.IOException;
 import java.nio.file.FileVisitResult;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.SimpleFileVisitor;
 import java.nio.file.attribute.BasicFileAttributes;
-import java.io.IOException;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.NoSuchElementException;
@@ -57,22 +57,15 @@
  * That means changes here will be upstreamed gradually.
  */
 @OptionClass(alias = "python-venv")
-public class VtsPythonVirtualenvPreparer implements ITargetPreparer, ITargetCleaner {
-
-    private static final String PIP = "pip";
-    private static final String PATH = "PATH";
-    private static final String OS_NAME = "os.name";
-    private static final String WINDOWS = "Windows";
+public class VtsPythonVirtualenvPreparer implements IMultiTargetPreparer {
     private static final String LOCAL_PYPI_PATH_ENV_VAR_NAME = "VTS_PYPI_PATH";
     private static final String LOCAL_PYPI_PATH_KEY = "pypi_packages_path";
-    protected static final String PYTHONPATH = "PYTHONPATH";
-    protected static final String VIRTUAL_ENV_PATH = "VIRTUALENVPATH";
     private static final int BASE_TIMEOUT = 1000 * 60;
-    private static final String[] DEFAULT_DEP_MODULES = {"enum", "future", "futures",
-            "google-api-python-client", "httplib2", "oauth2client", "protobuf", "requests"};
+    public static final String VIRTUAL_ENV_V3 = "VIRTUAL_ENV_V3";
+    public static final String VIRTUAL_ENV = "VIRTUAL_ENV";
 
     @Option(name = "venv-dir", description = "path of an existing virtualenv to use")
-    private File mVenvDir = null;
+    protected File mVenvDir = null;
 
     @Option(name = "requirements-file", description = "pip-formatted requirements file")
     private File mRequirementsFile = null;
@@ -81,41 +74,67 @@
     private Collection<String> mScriptFiles = new TreeSet<>();
 
     @Option(name = "dep-module", description = "modules which need to be installed by pip")
-    private Collection<String> mDepModules = new TreeSet<>(Arrays.asList(DEFAULT_DEP_MODULES));
+    protected Collection<String> mDepModules = new TreeSet<>();
 
     @Option(name = "no-dep-module", description = "modules which should not be installed by pip")
     private Collection<String> mNoDepModules = new TreeSet<>(Arrays.asList());
 
-    @Option(name = "python-version", description = "The version of a Python interpreter to use.")
-    private String mPythonVersion = "";
+    @Option(name = "python-version",
+            description = "The version of a Python interpreter to use."
+                    + "Currently, only major version number is fully supported."
+                    + "Example: \"2\", or \"3\".")
+    private String mPythonVersion = "2";
 
-    IBuildInfo mBuildInfo = null;
-    IRunUtil mRunUtil = new RunUtil();
-    String mPip = PIP;
+    private IBuildInfo mBuildInfo = null;
+    private DeviceDescriptor mDescriptor = null;
+    private IRunUtil mRunUtil = new RunUtil();
+
     String mLocalPypiPath = null;
+    String mPipPath = null;
+
+    // Since we allow virtual env path to be reused during a test plan/module, only the preparer
+    // which created the directory should be the one to delete it.
+    private boolean mIsDirCreator = false;
+
+    // If the same object is used in multiple threads (in sharding mode), the class
+    // needs to know when it is safe to call the teardown method.
+    private int mNumOfInstances = 0;
 
     /**
      * {@inheritDoc}
      */
     @Override
-    public void setUp(ITestDevice device, IBuildInfo buildInfo)
+    public synchronized void setUp(IInvocationContext context)
             throws TargetSetupError, BuildError, DeviceNotAvailableException {
-        mBuildInfo = buildInfo;
-        startVirtualenv(buildInfo);
-        setLocalPypiPath();
-        installDeps(buildInfo);
+        ++mNumOfInstances;
+        mBuildInfo = context.getBuildInfos().get(0);
+        if (mNumOfInstances == 1) {
+            CLog.i("Preparing python dependencies...");
+            ITestDevice device = context.getDevices().get(0);
+            mDescriptor = device.getDeviceDescriptor();
+            createVirtualenv(mBuildInfo);
+            VtsPythonRunnerHelper.activateVirtualenv(getRunUtil(), mVenvDir.getAbsolutePath());
+            setLocalPypiPath();
+            installDeps();
+        }
+        addPathToBuild(mBuildInfo);
     }
 
     /**
      * {@inheritDoc}
      */
     @Override
-    public void tearDown(ITestDevice device, IBuildInfo buildInfo, Throwable e)
+    public synchronized void tearDown(IInvocationContext context, Throwable e)
             throws DeviceNotAvailableException {
-        if (mVenvDir != null) {
+        --mNumOfInstances;
+        if (mNumOfInstances > 0) {
+            // Since this is a host side preparer, no need to repeat
+            return;
+        }
+        if (mVenvDir != null && mIsDirCreator) {
             try {
                 recursiveDelete(mVenvDir.toPath());
-                CLog.i("Deleted the virtual env's temp working dir, %s.", mVenvDir);
+                CLog.d("Deleted the virtual env's temp working dir, %s.", mVenvDir);
             } catch (IOException exception) {
                 CLog.e("Failed to delete %s: %s", mVenvDir, exception);
             }
@@ -126,11 +145,8 @@
     /**
      * This method sets mLocalPypiPath, the local PyPI package directory to
      * install python packages from in the installDeps method.
-     *
-     * @throws IOException
-     * @throws JSONException
      */
-    protected void setLocalPypiPath() throws RuntimeException {
+    protected void setLocalPypiPath() {
         VtsVendorConfigFileUtil configReader = new VtsVendorConfigFileUtil();
         if (configReader.LoadVendorConfig(mBuildInfo)) {
             // First try to load local PyPI directory path from vendor config file
@@ -138,7 +154,7 @@
                 String pypiPath = configReader.GetVendorConfigVariable(LOCAL_PYPI_PATH_KEY);
                 if (pypiPath.length() > 0 && dirExistsAndHaveReadAccess(pypiPath)) {
                     mLocalPypiPath = pypiPath;
-                    CLog.i(String.format("Loaded %s: %s", LOCAL_PYPI_PATH_KEY, mLocalPypiPath));
+                    CLog.d(String.format("Loaded %s: %s", LOCAL_PYPI_PATH_KEY, mLocalPypiPath));
                 }
             } catch (NoSuchElementException e) {
                 /* continue */
@@ -148,19 +164,19 @@
         // If loading path from vendor config file is unsuccessful,
         // check local pypi path defined by LOCAL_PYPI_PATH_ENV_VAR_NAME
         if (mLocalPypiPath == null) {
-            CLog.i("Checking whether local pypi packages directory exists");
+            CLog.d("Checking whether local pypi packages directory exists");
             String pypiPath = System.getenv(LOCAL_PYPI_PATH_ENV_VAR_NAME);
             if (pypiPath == null) {
-                CLog.i("Local pypi packages directory not specified by env var %s",
+                CLog.d("Local pypi packages directory not specified by env var %s",
                         LOCAL_PYPI_PATH_ENV_VAR_NAME);
             } else if (dirExistsAndHaveReadAccess(pypiPath)) {
                 mLocalPypiPath = pypiPath;
-                CLog.i("Set local pypi packages directory to %s", pypiPath);
+                CLog.d("Set local pypi packages directory to %s", pypiPath);
             }
         }
 
         if (mLocalPypiPath == null) {
-            CLog.i("Failed to set local pypi packages path. Therefore internet connection to "
+            CLog.d("Failed to set local pypi packages path. Therefore internet connection to "
                     + "https://pypi.python.org/simple/ must be available to run VTS tests.");
         }
     }
@@ -171,14 +187,14 @@
     private boolean dirExistsAndHaveReadAccess(String path) {
         File pathDir = new File(path);
         if (!pathDir.exists() || !pathDir.isDirectory()) {
-            CLog.i("Directory %s does not exist.", pathDir);
+            CLog.d("Directory %s does not exist.", pathDir);
             return false;
         }
 
-        if (!isOnWindows()) {
-            CommandResult c = mRunUtil.runTimedCmd(BASE_TIMEOUT * 5, "ls", path);
+        if (!EnvUtil.isOnWindows()) {
+            CommandResult c = getRunUtil().runTimedCmd(BASE_TIMEOUT * 5, "ls", path);
             if (c.getStatus() != CommandStatus.SUCCESS) {
-                CLog.i(String.format("Failed to read dir: %s. Result %s. stdout: %s, stderr: %s",
+                CLog.d(String.format("Failed to read dir: %s. Result %s. stdout: %s, stderr: %s",
                         path, c.getStatus(), c.getStdout(), c.getStderr()));
                 return false;
             }
@@ -187,11 +203,11 @@
             try {
                 String[] pathDirList = pathDir.list();
                 if (pathDirList == null) {
-                    CLog.i("Failed to read dir: %s. Please check access permission.", pathDir);
+                    CLog.d("Failed to read dir: %s. Please check access permission.", pathDir);
                     return false;
                 }
             } catch (SecurityException e) {
-                CLog.i(String.format(
+                CLog.d(String.format(
                         "Failed to read dir %s with SecurityException %s", pathDir, e));
                 return false;
             }
@@ -199,25 +215,25 @@
         }
     }
 
-    protected void installDeps(IBuildInfo buildInfo) throws TargetSetupError {
+    protected void installDeps() throws TargetSetupError {
         boolean hasDependencies = false;
         if (!mScriptFiles.isEmpty()) {
             for (String scriptFile : mScriptFiles) {
-                CLog.i("Attempting to execute a script, %s", scriptFile);
-                CommandResult c = mRunUtil.runTimedCmd(BASE_TIMEOUT * 5, scriptFile);
+                CLog.d("Attempting to execute a script, %s", scriptFile);
+                CommandResult c = getRunUtil().runTimedCmd(BASE_TIMEOUT * 5, scriptFile);
                 if (c.getStatus() != CommandStatus.SUCCESS) {
                     CLog.e("Executing script %s failed", scriptFile);
-                    throw new TargetSetupError("Failed to source a script");
+                    throw new TargetSetupError("Failed to source a script", mDescriptor);
                 }
             }
         }
         if (mRequirementsFile != null) {
-            CommandResult c = mRunUtil.runTimedCmd(BASE_TIMEOUT * 5, mPip,
-                    "install", "-r", mRequirementsFile.getAbsolutePath());
-            if (c.getStatus() != CommandStatus.SUCCESS) {
-                CLog.e("Installing dependencies from %s failed",
-                        mRequirementsFile.getAbsolutePath());
-                throw new TargetSetupError("Failed to install dependencies with pip");
+            CommandResult c = getRunUtil().runTimedCmd(BASE_TIMEOUT * 5, getPipPath(), "install",
+                    "-r", mRequirementsFile.getAbsolutePath());
+            if (!CommandStatus.SUCCESS.equals(c.getStatus())) {
+                CLog.e("Installing dependencies from %s failed with error: %s",
+                        mRequirementsFile.getAbsolutePath(), c.getStderr());
+                throw new TargetSetupError("Failed to install dependencies with pip", mDescriptor);
             }
             hasDependencies = true;
         }
@@ -228,32 +244,35 @@
                 }
                 CommandResult result = null;
                 if (mLocalPypiPath != null) {
-                    CLog.i("Attempting installation of %s from local directory", dep);
-                    result = mRunUtil.runTimedCmd(BASE_TIMEOUT * 5, mPip, "install", dep,
-                            "--no-index", "--find-links=" + mLocalPypiPath);
-                    CLog.i(String.format("Result %s. stdout: %s, stderr: %s", result.getStatus(),
+                    CLog.d("Attempting installation of %s from local directory", dep);
+                    result = getRunUtil().runTimedCmd(BASE_TIMEOUT * 5, getPipPath(), "install",
+                            dep, "--no-index", "--find-links=" + mLocalPypiPath);
+                    CLog.d(String.format("Result %s. stdout: %s, stderr: %s", result.getStatus(),
                             result.getStdout(), result.getStderr()));
                     if (result.getStatus() != CommandStatus.SUCCESS) {
                         CLog.e(String.format("Installing %s from %s failed", dep, mLocalPypiPath));
                     }
                 }
                 if (mLocalPypiPath == null || result.getStatus() != CommandStatus.SUCCESS) {
-                    CLog.i("Attempting installation of %s from PyPI", dep);
-                    result = mRunUtil.runTimedCmd(BASE_TIMEOUT * 5, mPip, "install", dep);
-                    CLog.i(String.format("Result %s. stdout: %s, stderr: %s", result.getStatus(),
-                            result.getStdout(), result.getStderr()));
+                    CLog.d("Attempting installation of %s from PyPI", dep);
+                    result = getRunUtil().runTimedCmd(
+                            BASE_TIMEOUT * 5, getPipPath(), "install", dep);
+                    CLog.d("Result %s. stdout: %s, stderr: %s", result.getStatus(),
+                            result.getStdout(), result.getStderr());
                     if (result.getStatus() != CommandStatus.SUCCESS) {
                         CLog.e("Installing %s from PyPI failed.", dep);
-                        CLog.i("Attempting to upgrade %s", dep);
-                        result = mRunUtil.runTimedCmd(
-                                BASE_TIMEOUT * 5, mPip, "install", "--upgrade", dep);
+                        CLog.d("Attempting to upgrade %s", dep);
+                        result = getRunUtil().runTimedCmd(
+                                BASE_TIMEOUT * 5, getPipPath(), "install", "--upgrade", dep);
                         if (result.getStatus() != CommandStatus.SUCCESS) {
-                            throw new TargetSetupError(String.format(
-                                    "Failed to install dependencies with pip. "
-                                            + "Result %s. stdout: %s, stderr: %s",
-                                    result.getStatus(), result.getStdout(), result.getStderr()));
+                            throw new TargetSetupError(
+                                    String.format("Failed to install dependencies with pip. "
+                                                    + "Result %s. stdout: %s, stderr: %s",
+                                            result.getStatus(), result.getStdout(),
+                                            result.getStderr()),
+                                    mDescriptor);
                         } else {
-                            CLog.i(String.format("Result %s. stdout: %s, stderr: %s",
+                            CLog.d(String.format("Result %s. stdout: %s, stderr: %s",
                                     result.getStatus(), result.getStdout(), result.getStderr()));
                         }
                     }
@@ -262,69 +281,109 @@
             }
         }
         if (!hasDependencies) {
-            CLog.i("No dependencies to install");
-        } else {
-            // make the install directory of new packages available to other classes that
-            // receive the build
-            buildInfo.setFile(PYTHONPATH, new File(mVenvDir,
-                    "local/lib/python2.7/site-packages"),
-                    buildInfo.getBuildId());
-        }
-    }
-
-    protected void startVirtualenv(IBuildInfo buildInfo) throws TargetSetupError {
-        if (mVenvDir != null) {
-            CLog.i("Using existing virtualenv based at %s", mVenvDir.getAbsolutePath());
-            activate();
-            return;
-        }
-        try {
-            mVenvDir = buildInfo.getFile(VIRTUAL_ENV_PATH);
-            if (mVenvDir == null) {
-                mVenvDir = FileUtil.createTempDir(getMD5(buildInfo.getTestTag()) + "-virtualenv");
-            }
-            String virtualEnvPath = mVenvDir.getAbsolutePath();
-            CommandResult c;
-            if (mPythonVersion.length() == 0) {
-                c = mRunUtil.runTimedCmd(BASE_TIMEOUT, "virtualenv", virtualEnvPath);
-            } else {
-                String[] cmd;
-                cmd = new String[4];
-                cmd[0] = "virtualenv";
-                cmd[1] = "-p";
-                cmd[2] = "python" + mPythonVersion;
-                cmd[3] = virtualEnvPath;
-                c = mRunUtil.runTimedCmd(BASE_TIMEOUT, cmd);
-            }
-            if (c.getStatus() != CommandStatus.SUCCESS) {
-                CLog.e(String.format("Failed to create virtualenv with : %s.", virtualEnvPath));
-                throw new TargetSetupError("Failed to create virtualenv");
-            }
-            CLog.i(VIRTUAL_ENV_PATH + " = " + virtualEnvPath + "\n");
-            buildInfo.setFile(VIRTUAL_ENV_PATH, new File(virtualEnvPath),
-                              buildInfo.getBuildId());
-            activate();
-        } catch (IOException | RuntimeException e) {
-            CLog.e("Failed to create temp directory for virtualenv");
-            throw new TargetSetupError("Error creating virtualenv", e);
+            CLog.d("No dependencies to install");
         }
     }
 
     /**
-     * This method returns a MD5 hash string for the given string.
+     * This method returns absolute pip path in virtualenv.
+     *
+     * This method is needed because although PATH is set in IRunUtil, IRunUtil will still
+     * use pip from system path.
+     *
+     * @return absolute pip path in virtualenv. null if virtualenv not available.
      */
-    private String getMD5(String str) throws RuntimeException {
-        try {
-            java.security.MessageDigest md = java.security.MessageDigest.getInstance("MD5");
-            byte[] array = md.digest(str.getBytes());
-            StringBuffer sb = new StringBuffer();
-            for (int i = 0; i < array.length; ++i) {
-                sb.append(Integer.toHexString((array[i] & 0xFF) | 0x100).substring(1, 3));
-            }
-            return sb.toString();
-        } catch (java.security.NoSuchAlgorithmException e) {
-            throw new RuntimeException("Error generating MD5 hash.", e);
+    public String getPipPath() {
+        if (mPipPath != null) {
+            return mPipPath;
         }
+
+        String virtualenvPath = mVenvDir.getAbsolutePath();
+        if (virtualenvPath == null) {
+            return null;
+        }
+        mPipPath = new File(VtsPythonRunnerHelper.getPythonBinDir(virtualenvPath), "pip")
+                           .getAbsolutePath();
+        return mPipPath;
+    }
+
+    /**
+     * Get the major python version from option.
+     *
+     * Currently, only 2 and 3 are supported.
+     *
+     * @return major version number
+     * @throws TargetSetupError
+     */
+    protected int getConfiguredPythonVersionMajor() throws TargetSetupError {
+        if (mPythonVersion.startsWith("3.") || mPythonVersion.equals("3")) {
+            return 3;
+        } else if (mPythonVersion.startsWith("2.") || mPythonVersion.equals("2")) {
+            return 2;
+        } else {
+            throw new TargetSetupError("Unsupported python version " + mPythonVersion);
+        }
+    }
+
+    /**
+     * Add PYTHONPATH and VIRTUAL_ENV_PATH to BuildInfo.
+     * @param buildInfo
+     * @throws TargetSetupError
+     */
+    protected void addPathToBuild(IBuildInfo buildInfo) throws TargetSetupError {
+        String target = null;
+        switch (getConfiguredPythonVersionMajor()) {
+            case 2:
+                target = VtsPythonVirtualenvPreparer.VIRTUAL_ENV;
+                break;
+            case 3:
+                target = VtsPythonVirtualenvPreparer.VIRTUAL_ENV_V3;
+                break;
+        }
+
+        if (buildInfo.getFile(target) == null) {
+            buildInfo.setFile(target, new File(mVenvDir.getAbsolutePath()), buildInfo.getBuildId());
+        }
+    }
+
+    /**
+     * Create virtualenv directory by executing virtualenv command.
+     * @param buildInfo
+     * @throws TargetSetupError
+     */
+    protected void createVirtualenv(IBuildInfo buildInfo) throws TargetSetupError {
+        if (mVenvDir == null) {
+            switch (getConfiguredPythonVersionMajor()) {
+                case 2:
+                    mVenvDir = buildInfo.getFile(VtsPythonVirtualenvPreparer.VIRTUAL_ENV);
+                    break;
+                case 3:
+                    mVenvDir = buildInfo.getFile(VtsPythonVirtualenvPreparer.VIRTUAL_ENV_V3);
+                    break;
+            }
+        }
+
+        if (mVenvDir == null) {
+            CLog.d("Creating virtualenv for version " + mPythonVersion);
+            try {
+                mVenvDir = FileUtil.createTempDir("vts-virtualenv-" + mPythonVersion + "-"
+                        + VtsFileUtil.normalizeFileName(buildInfo.getTestTag()) + "_");
+                mIsDirCreator = true;
+                String virtualEnvPath = mVenvDir.getAbsolutePath();
+                String[] cmd = new String[] {
+                        "virtualenv", "-p", "python" + mPythonVersion, virtualEnvPath};
+                CommandResult c = getRunUtil().runTimedCmd(BASE_TIMEOUT, cmd);
+                if (c.getStatus() != CommandStatus.SUCCESS) {
+                    CLog.e(String.format("Failed to create virtualenv with : %s.", virtualEnvPath));
+                    throw new TargetSetupError("Failed to create virtualenv", mDescriptor);
+                }
+            } catch (IOException | RuntimeException e) {
+                CLog.e("Failed to create temp directory for virtualenv");
+                throw new TargetSetupError("Error creating virtualenv", e, mDescriptor);
+            }
+        }
+
+        CLog.d("Python virtualenv path is: " + mVenvDir);
     }
 
     protected void addDepModule(String module) {
@@ -336,6 +395,17 @@
     }
 
     /**
+     * Get an instance of {@link IRunUtil}.
+     */
+    @VisibleForTesting
+    IRunUtil getRunUtil() {
+        if (mRunUtil == null) {
+            mRunUtil = new RunUtil();
+        }
+        return mRunUtil;
+    }
+
+    /**
      * This method recursively deletes a file tree without following symbolic links.
      *
      * @param rootPath the path to delete.
@@ -359,21 +429,4 @@
             }
         });
     }
-
-    /**
-     * This method returns whether the OS is Windows.
-     */
-    private static boolean isOnWindows() {
-        return System.getProperty(OS_NAME).contains(WINDOWS);
-    }
-
-    private void activate() {
-        File binDir = new File(mVenvDir, isOnWindows() ? "Scripts" : "bin");
-        mRunUtil.setWorkingDir(binDir);
-        String path = System.getenv(PATH);
-        mRunUtil.setEnvVariable(PATH, binDir + File.pathSeparator + path);
-        File pipFile = new File(binDir, PIP);
-        pipFile.setExecutable(true);
-        mPip = pipFile.getAbsolutePath();
-    }
 }
diff --git a/harnesses/tradefed/src/com/android/tradefed/targetprep/VtsTestPlanResultReporter.java b/harnesses/tradefed/src/com/android/tradefed/targetprep/VtsTestPlanResultReporter.java
index ece5c13..d7fabc2 100644
--- a/harnesses/tradefed/src/com/android/tradefed/targetprep/VtsTestPlanResultReporter.java
+++ b/harnesses/tradefed/src/com/android/tradefed/targetprep/VtsTestPlanResultReporter.java
@@ -31,8 +31,11 @@
 import com.android.tradefed.build.IBuildInfo;
 import com.android.tradefed.config.Option;
 import com.android.tradefed.config.OptionClass;
+import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.invoker.IInvocationContext;
 import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.targetprep.multi.IMultiTargetPreparer;
 import com.android.tradefed.util.CommandResult;
 import com.android.tradefed.util.CommandStatus;
 import com.android.tradefed.util.FileUtil;
@@ -49,10 +52,12 @@
  * an OAuth2 credential kept in a json file.
  */
 @OptionClass(alias = "vts-plan-result")
-public class VtsTestPlanResultReporter implements ITargetPreparer, ITargetCleaner {
+public class VtsTestPlanResultReporter
+        implements ITargetPreparer, ITargetCleaner, IMultiTargetPreparer {
     private static final String PLUS_ME = "https://www.googleapis.com/auth/plus.me";
     private static final String TEST_PLAN_EXECUTION_RESULT = "vts-test-plan-execution-result";
     private static final String TEST_PLAN_REPORT_FILE = "TEST_PLAN_REPORT_FILE";
+    private static final String TEST_PLAN_REPORT_FILE_NAME = "status.json";
     private static VtsVendorConfigFileUtil configReader = null;
     private static VtsDashboardUtil dashboardUtil = null;
     private static final int BASE_TIMEOUT_MSECS = 1000 * 60;
@@ -77,7 +82,7 @@
         try {
             mStatusDir = FileUtil.createTempDir(TEST_PLAN_EXECUTION_RESULT);
             if (mStatusDir != null) {
-                File statusFile = new File(mStatusDir, "status.json");
+                File statusFile = new File(mStatusDir, TEST_PLAN_REPORT_FILE_NAME);
                 buildInfo.setFile(TEST_PLAN_REPORT_FILE, statusFile, buildInfo.getBuildId());
             }
         } catch (IOException ex) {
@@ -99,6 +104,15 @@
      * {@inheritDoc}
      */
     @Override
+    public void setUp(IInvocationContext context)
+            throws TargetSetupError, DeviceNotAvailableException {
+        setUp(context.getDevices().get(0), context.getBuildInfos().get(0));
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
     public void tearDown(ITestDevice device, IBuildInfo buildInfo, Throwable e) {
         File reportFile = buildInfo.getFile(TEST_PLAN_REPORT_FILE);
         String repotFilePath = reportFile.getAbsolutePath();
@@ -131,4 +145,12 @@
         }
     }
 
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void tearDown(IInvocationContext context, Throwable e)
+            throws DeviceNotAvailableException {
+        tearDown(context.getDevices().get(0), context.getBuildInfos().get(0), e);
+    }
 }
diff --git a/harnesses/tradefed/src/com/android/tradefed/targetprep/VtsTraceCollectPreparer.java b/harnesses/tradefed/src/com/android/tradefed/targetprep/VtsTraceCollectPreparer.java
new file mode 100644
index 0000000..eceae7f
--- /dev/null
+++ b/harnesses/tradefed/src/com/android/tradefed/targetprep/VtsTraceCollectPreparer.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tradefed.targetprep;
+
+import com.android.annotations.VisibleForTesting;
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.config.Option;
+import com.android.tradefed.config.OptionClass;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.targetprep.TargetSetupError;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.NoSuchElementException;
+
+/**
+ * Preparer class for collect HAL traces during the test run.
+ *
+ * Currently used for collecting HAL traces for CTS tests. In test setup, pushes
+ * the profiler libs to device, sets up the permissions, and calls
+ * vts_profiling_configure to enable profiling for HAL services. In test
+ * tear down, pulls the traces to the host, resets permissions and cleans up the
+ * trace files and profiler libs on the device.
+ */
+@OptionClass(alias = "vts-trace-collect-preparer")
+public class VtsTraceCollectPreparer implements ITargetPreparer, ITargetCleaner {
+    static final String SELINUX_DISABLED = "Disabled"; // selinux disabled
+    static final String SELINUX_PERMISSIVE = "Permissive"; // selinux permissive mode
+
+    // Relative path to vts 32 bit lib directory.
+    static final String VTS_LIB_DIR_32 = "DATA/lib/";
+    // Relative path to vts 64 bit lib directory.
+    static final String VTS_LIB_DIR_64 = "DATA/lib64/";
+    // Relative path to vts binary directory.
+    static final String VTS_BINARY_DIR = "DATA/bin/";
+    // Path of 32 bit test libs on target device.
+    static final String VTS_TMP_LIB_DIR_32 = "/data/local/tmp/32/";
+    // Path of 64 bit test libs on target device.
+    static final String VTS_TMP_LIB_DIR_64 = "/data/local/tmp/64/";
+    // Path of vts test directory on target device.
+    static final String VTS_TMP_DIR = "/data/local/tmp/";
+    // Default path to store trace files locally.
+    static final String LOCAL_TRACE_DIR = "vts-profiling";
+
+    static final String VTS_PROFILER_SUFFIX = "vts.profiler.so";
+    static final String VTS_LIB_PREFIX = "libvts";
+    static final String PROFILING_CONFIGURE_BINARY = "vts_profiling_configure";
+    static final String TRACE_PATH = "trace_path";
+
+    private String mEnforcingState = null; // start state for selinux enforcement
+
+    @Option(name = "local-trace-dir", description = "Local directory to store trace.")
+    private String mLocalTraceDir = LOCAL_TRACE_DIR;
+
+    /** {@inheritDoc} */
+    @Override
+    public void setUp(ITestDevice device, IBuildInfo buildInfo)
+            throws DeviceNotAvailableException, TargetSetupError {
+        CompatibilityBuildHelper buildHelper = createBuildHelper(buildInfo);
+        try {
+            // adb root.
+            device.enableAdbRoot();
+            // Push 32 bit profiler libs.
+            pushProfilerLib(device, new File(buildHelper.getTestsDir(), VTS_LIB_DIR_32),
+                    VTS_TMP_LIB_DIR_32);
+            // Push 64 bit profiler libs.
+            pushProfilerLib(device, new File(buildHelper.getTestsDir(), VTS_LIB_DIR_64),
+                    VTS_TMP_LIB_DIR_64);
+            // Push vts_profiling_configure
+            device.pushFile(new File(buildHelper.getTestsDir(),
+                                    VTS_BINARY_DIR + PROFILING_CONFIGURE_BINARY),
+                    VTS_TMP_DIR + PROFILING_CONFIGURE_BINARY);
+        } catch (IOException | NoSuchElementException e) {
+            // Cleanup profiler libs.
+            removeProfilerLib(device);
+            throw new TargetSetupError("Could not push profiler.");
+        }
+        // Create directory to store the trace files.
+        try {
+            File resultDir = buildHelper.getResultDir();
+            File traceDir = new File(resultDir, mLocalTraceDir);
+            buildInfo.addBuildAttribute(TRACE_PATH, traceDir.getAbsolutePath());
+        } catch (FileNotFoundException e) {
+            CLog.e("Failed to get traceDir: " + e.getMessage());
+            // Cleanup profiler libs.
+            removeProfilerLib(device);
+            throw new TargetSetupError("Failed to get traceDir.");
+        }
+        // Set selinux permissive mode.
+        mEnforcingState = device.executeShellCommand("getenforce");
+        if (mEnforcingState == null
+                || (!mEnforcingState.equals(SELINUX_DISABLED)
+                           && !mEnforcingState.equals(SELINUX_PERMISSIVE))) {
+            device.executeShellCommand("setenforce " + SELINUX_PERMISSIVE);
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void tearDown(ITestDevice device, IBuildInfo buildInfo, Throwable e)
+            throws DeviceNotAvailableException {
+        // Reset selinux permission mode.
+        if (mEnforcingState != null && !mEnforcingState.equals(SELINUX_DISABLED)) {
+            device.executeShellCommand("setenforce " + mEnforcingState);
+        }
+        // Cleanup profiler libs.
+        removeProfilerLib(device);
+    }
+
+    /**
+     * Create and return a {@link CompatibilityBuildHelper} to use during the preparer.
+     */
+    @VisibleForTesting
+    CompatibilityBuildHelper createBuildHelper(IBuildInfo buildInfo) {
+        return new CompatibilityBuildHelper(buildInfo);
+    }
+
+    /**
+     * Push all the profiler libraries (with pattern *.vts-profiler.so) and VTS
+     * profiling related libraries (with pattern libvts*.so) to device.
+     *
+     * @param device device object
+     * @param profilerLibDir directory to lookup for profiler libraries.
+     * @param destDirName target directory on device to push to.
+     * @throws DeviceNotAvailableException
+     */
+    private void pushProfilerLib(ITestDevice device, File profilerLibDir, String destDirName)
+            throws DeviceNotAvailableException {
+        File[] files = profilerLibDir.listFiles();
+        if (files == null) {
+            CLog.d("No files found in %s", profilerLibDir.getAbsolutePath());
+            return;
+        }
+        for (File f : files) {
+            String fileName = f.getName();
+            if (f.isFile()
+                    && (fileName.endsWith(VTS_PROFILER_SUFFIX)
+                               || fileName.startsWith(VTS_LIB_PREFIX))) {
+                CLog.d("Pushing %s", fileName);
+                device.pushFile(f, destDirName + fileName);
+            }
+        }
+    }
+
+    /**
+     * Remove all profiler and VTS profiling libraries from device.
+     *
+     * @param device device object
+     * @throws DeviceNotAvailableException
+     */
+    private void removeProfilerLib(ITestDevice device) throws DeviceNotAvailableException {
+        device.executeShellCommand(String.format("rm -rf %s/*vts.profiler.so", VTS_TMP_LIB_DIR_32));
+        device.executeShellCommand(String.format("rm -rf %s/*vts.profiler.so", VTS_TMP_LIB_DIR_64));
+        device.executeShellCommand(String.format("rm -rf %s/libvts_*.so", VTS_TMP_LIB_DIR_32));
+        device.executeShellCommand(String.format("rm -rf %s/libvts_*.so", VTS_TMP_LIB_DIR_64));
+        device.executeShellCommand(String.format("rm %s/vts_profiling_configure", VTS_TMP_DIR));
+    }
+}
diff --git a/harnesses/tradefed/src/com/android/tradefed/testtype/VtsMultiDeviceTest.java b/harnesses/tradefed/src/com/android/tradefed/testtype/VtsMultiDeviceTest.java
index 1299e28..d827087 100644
--- a/harnesses/tradefed/src/com/android/tradefed/testtype/VtsMultiDeviceTest.java
+++ b/harnesses/tradefed/src/com/android/tradefed/testtype/VtsMultiDeviceTest.java
@@ -23,28 +23,30 @@
 import com.android.tradefed.config.OptionClass;
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.invoker.IInvocationContext;
 import com.android.tradefed.log.LogUtil.CLog;
 import com.android.ddmlib.Log.LogLevel;
 import com.android.tradefed.result.ITestInvocationListener;
 import com.android.tradefed.targetprep.VtsCoveragePreparer;
-import com.android.tradefed.util.ArrayUtil;
+import com.android.tradefed.targetprep.VtsPythonVirtualenvPreparer;
 import com.android.tradefed.util.CommandResult;
 import com.android.tradefed.util.CommandStatus;
 import com.android.tradefed.util.FileUtil;
 import com.android.tradefed.util.JsonUtil;
-import com.android.tradefed.util.ProcessHelper;
+import com.android.tradefed.util.OutputUtil;
 import com.android.tradefed.util.RunInterruptedException;
-import com.android.tradefed.util.IRunUtil;
-import com.android.tradefed.util.RunUtil;
 import com.android.tradefed.util.VtsDashboardUtil;
+import com.android.tradefed.util.VtsPythonRunnerHelper;
 import com.android.tradefed.util.VtsVendorConfigFileUtil;
 import com.android.tradefed.testtype.IAbi;
+import com.android.tradefed.testtype.IInvocationContextReceiver;
 
 import org.json.JSONArray;
 import org.json.JSONObject;
 import org.json.JSONException;
 
 import java.io.BufferedWriter;
+import java.util.Collection;
 import java.io.File;
 import java.io.FileNotFoundException;
 import java.io.FileWriter;
@@ -52,9 +54,10 @@
 import java.io.PrintWriter;
 import java.nio.file.Paths;
 import java.util.Map;
+import java.util.TreeMap;
 import java.util.TreeSet;
 import java.util.Set;
-import java.util.Collection;
+import java.util.List;
 import java.util.ArrayList;
 import java.util.Arrays;
 
@@ -64,9 +67,11 @@
  */
 
 @OptionClass(alias = "vtsmultidevicetest")
-public class VtsMultiDeviceTest implements IDeviceTest, IRemoteTest, ITestFilterReceiver,
-IRuntimeHintProvider, ITestCollector, IBuildReceiver, IAbiReceiver {
-
+public class VtsMultiDeviceTest
+        implements IDeviceTest, IRemoteTest, ITestFilterReceiver, IRuntimeHintProvider,
+                   ITestCollector, IBuildReceiver, IAbiReceiver, IInvocationContextReceiver {
+    static final String ACTS_TEST_MODULE = "ACTS_TEST_MODULE";
+    static final String ADAPTER_ACTS_PATH = "vts/runners/adapters/acts/acts_adapter";
     static final String ANDROIDDEVICE = "AndroidDevice";
     static final String BUILD = "build";
     static final String BUILD_ID = "build_id";
@@ -76,23 +81,20 @@
     static final String LOG_PATH = "log_path";
     static final String LOG_SEVERITY = "log_severity";
     static final String NAME = "name";
-    static final String OS_NAME = "os.name";
-    static final String WINDOWS = "Windows";
-    static final String PYTHONPATH = "PYTHONPATH";
     static final String SERIAL = "serial";
     static final String TESTMODULE = "TestModule";
     static final String TEST_BED = "test_bed";
     static final String TEST_PLAN_REPORT_FILE = "TEST_PLAN_REPORT_FILE";
     static final String TEST_SUITE = "test_suite";
-    static final String TEST_MAX_TIMEOUT = "test_max_timeout";
-    static final String VIRTUAL_ENV_PATH = "VIRTUALENVPATH";
     static final String ABI_NAME = "abi_name";
     static final String ABI_BITNESS = "abi_bitness";
     static final String SKIP_ON_32BIT_ABI = "skip_on_32bit_abi";
     static final String SKIP_ON_64BIT_ABI = "skip_on_64bit_abi";
     static final String SKIP_IF_THERMAL_THROTTLING = "skip_if_thermal_throttling";
+    static final String DISABLE_CPU_FREQUENCY_SCALING = "disable_cpu_frequency_scaling";
+    static final String DISABLE_FRAMEWORK = "DISABLE_FRAMEWORK";
+    static final String STOP_NATIVE_SERVERS = "STOP_NATIVE_SERVERS";
     static final String RUN_32BIT_ON_64BIT_ABI = "run_32bit_on_64bit_abi";
-    static final String VTS = "vts";
     static final String CONFIG_FILE_EXTENSION = ".config";
     static final String INCLUDE_FILTER = "include_filter";
     static final String EXCLUDE_FILTER = "exclude_filter";
@@ -103,28 +105,39 @@
     static final String BINARY_TEST_ARGS = "binary_test_args";
     static final String BINARY_TEST_LD_LIBRARY_PATH = "binary_test_ld_library_path";
     static final String BINARY_TEST_PROFILING_LIBRARY_PATH = "binary_test_profiling_library_path";
-    static final String BINARY_TEST_DISABLE_FRAMEWORK = "binary_test_disable_framework";
+    @Deprecated static final String BINARY_TEST_DISABLE_FRAMEWORK = "binary_test_disable_framework";
+    @Deprecated
     static final String BINARY_TEST_STOP_NATIVE_SERVERS = "binary_test_stop_native_servers";
     static final String BINARY_TEST_TYPE_GTEST = "gtest";
     static final String BINARY_TEST_TYPE_LLVMFUZZER = "llvmfuzzer";
     static final String BINARY_TEST_TYPE_HAL_HIDL_GTEST = "hal_hidl_gtest";
     static final String BINARY_TEST_TYPE_HAL_HIDL_REPLAY_TEST = "hal_hidl_replay_test";
     static final String BINARY_TEST_TYPE_HOST_BINARY_TEST = "host_binary_test";
-    static final String BUG_REPORT_ON_FAILURE = "bug_report_on_failure";
+    static final String BUG_REPORT_ON_FAILURE = "BUG_REPORT_ON_FAILURE";
+    static final String COLLECT_TESTS_ONLY = "collect_tests_only";
+    static final String CONFIG_STR = "CONFIG_STR";
+    static final String CONFIG_INT = "CONFIG_INT";
+    static final String CONFIG_BOOL = "CONFIG_BOOL";
+    static final String LOGCAT_ON_FAILURE = "LOGCAT_ON_FAILURE";
     static final String ENABLE_COVERAGE = "enable_coverage";
+    static final String EXCLUDE_COVERAGE_PATH = "exclude_coverage_path";
     static final String ENABLE_PROFILING = "enable_profiling";
     static final String ENABLE_SANCOV = "enable_sancov";
     static final String GTEST_BATCH_MODE = "gtest_batch_mode";
     static final String SAVE_TRACE_FIEL_REMOTE = "save_trace_file_remote";
     static final String OUTPUT_COVERAGE_REPORT = "output_coverage_report";
+    static final String COVERAGE_REPORT_PATH = "coverage_report_path";
     static final String GLOBAL_COVERAGE = "global_coverage";
     static final String LTP_NUMBER_OF_THREADS = "ltp_number_of_threads";
+    static final String MOBLY_TEST_MODULE = "MOBLY_TEST_MODULE";
     static final String NATIVE_SERVER_PROCESS_NAME = "native_server_process_name";
     static final String PASSTHROUGH_MODE = "passthrough_mode";
     static final String PRECONDITION_HWBINDER_SERVICE = "precondition_hwbinder_service";
     static final String PRECONDITION_FEATURE = "precondition_feature";
     static final String PRECONDITION_FILE_PATH_PREFIX = "precondition_file_path_prefix";
+    static final String PRECONDITION_FIRST_API_LEVEL = "precondition_first_api_level";
     static final String PRECONDITION_LSHAL = "precondition_lshal";
+    static final String PRECONDITION_SYSPROP = "precondition_sysprop";
     static final String PRECONDITION_VINTF = "precondition_vintf";
     static final String ENABLE_SYSTRACE = "enable_systrace";
     static final String HAL_HIDL_REPLAY_TEST_TRACE_PATHS = "hal_hidl_replay_test_trace_paths";
@@ -136,22 +149,24 @@
     static final String TEMPLATE_BINARY_TEST_PATH = "vts/testcases/template/binary_test/binary_test";
     static final String TEMPLATE_GTEST_BINARY_TEST_PATH = "vts/testcases/template/gtest_binary_test/gtest_binary_test";
     static final String TEMPLATE_LLVMFUZZER_TEST_PATH = "vts/testcases/template/llvmfuzzer_test/llvmfuzzer_test";
+    static final String TEMPLATE_MOBLY_TEST_PATH = "vts/testcases/template/mobly/mobly_test";
     static final String TEMPLATE_HAL_HIDL_GTEST_PATH = "vts/testcases/template/hal_hidl_gtest/hal_hidl_gtest";
     static final String TEMPLATE_HAL_HIDL_REPLAY_TEST_PATH = "vts/testcases/template/hal_hidl_replay_test/hal_hidl_replay_test";
     static final String TEMPLATE_HOST_BINARY_TEST_PATH = "vts/testcases/template/host_binary_test/host_binary_test";
     static final long TEST_ABORT_TIMEOUT_MSECS = 1000 * 15;
     static final String TEST_RUN_SUMMARY_FILE_NAME = "test_run_summary.json";
     static final float DEFAULT_TARGET_VERSION = -1;
-    static final String DEFAULT_TESTCASE_CONFIG_PATH = "vts/tools/vts-tradefed/res/default/DefaultTestCase.config";
+    static final String DEFAULT_TESTCASE_CONFIG_PATH =
+            "vts/tools/vts-tradefed/res/default/DefaultTestCase.runner_conf";
 
     private ITestDevice mDevice = null;
     private IAbi mAbi = null;
 
     @Option(name = "test-timeout",
             description = "The amount of time (in milliseconds) for a test invocation. "
-                    + "If the test cannot finish before timeout, it should interrupt itself and "
-                    + "clean up in " + TEST_ABORT_TIMEOUT_MSECS + "ms. Hence the actual timeout "
-                    + "is the specified value + " + TEST_ABORT_TIMEOUT_MSECS + "ms.",
+                    + "If the test cannot finish before timeout, it is interrupted and cleans up "
+                    + "in " + TEST_ABORT_TIMEOUT_MSECS + "ms. Hence the actual timeout is the "
+                    + "specified value + " + TEST_ABORT_TIMEOUT_MSECS + "ms.",
             isTimeVal = true)
     private long mTestTimeout = 1000 * 60 * 60 * 3;
 
@@ -167,9 +182,6 @@
             description = "The type of test case path ('module' by default or 'file').")
     private String mTestCasePathType = null;
 
-    @Option(name = "python-version", description = "The version of a Python interpreter to use.")
-    private String mPythonVersion = "";
-
     @Option(name = "test-config-path",
             description = "The path for test case config file.")
     private String mTestConfigPath = null;
@@ -192,10 +204,19 @@
                     + "    <source>: absolute path of file prefix on device")
     private Collection<String> mPreconditionFilePathPrefix = new ArrayList<>();
 
+    @Option(name = "precondition-first-api-level",
+            description = "The lowest first API level required to run the test.")
+    private int mPreconditionFirstApiLevel = 0;
+
     @Option(name = "precondition-lshal",
         description = "The name of a `lshal`-listable feature needed to run the test.")
     private String mPreconditionLshal = null;
 
+    @Option(name = "precondition-sysprop",
+            description = "The name=value for a system property configuration that needs "
+                    + "to be met to run the test.")
+    private String mPreconditionSysProp = null;
+
     @Option(name = "precondition-vintf",
             description = "The full name of a HAL specified in vendor/manifest.xml and "
                     + "needed to run the test (e.g., android.hardware.graphics.mapper@2.0). "
@@ -277,6 +298,10 @@
             description = "Whether to skip tests if target device suffers from thermal throttling.")
     private boolean mSkipIfThermalThrottling = false;
 
+    @Option(name = "disable-cpu-frequency-scaling",
+            description = "Whether to disable cpu frequency scaling for test.")
+    private boolean mDisableCpuFrequencyScaling = true;
+
     @Option(name = "run-32bit-on-64bit-abi",
             description = "Whether to run 32bit tests on 64bit ABI.")
     private boolean mRun32bBitOn64BitAbi = false;
@@ -349,19 +374,35 @@
             + "specified, default directories will be used for files with different tags.")
     private Collection<String> mBinaryTestProfilingLibraryPath = new ArrayList<>();
 
-    @Option(name = "binary-test-disable-framework", description = "Adb stop/start before/after test.")
+    @Deprecated
+    @Option(name = "binary-test-disable-framework",
+            description = "Adb stop/start before/after test.")
     private boolean mBinaryTestDisableFramework = false;
 
+    @Deprecated
     @Option(name = "binary-test-stop-native-servers",
             description = "Set to stop all properly configured native servers during the testing.")
     private boolean mBinaryTestStopNativeServers = false;
 
+    @Option(name = "disable-framework", description = "Adb stop/start before/after test.")
+    private boolean mDisableFramework = false;
+
+    @Option(name = "stop-native-servers",
+            description = "Set to stop all properly configured native servers during the testing.")
+    private boolean mStopNativeServers = false;
+
     @Option(name = "bug-report-on-failure",
             description = "To catch bugreport zip file at the end of failed test cases. "
-                    + "If set to true, a report will be caught through adh shell command at the end of each failed "
-                    + "test cases.")
+                    + "If set to true, a report will be caught through adh shell command at the "
+                    + "end of each failed test cases.")
     private boolean mBugReportOnFailure = false;
 
+    @Option(name = "logcat-on-failure",
+            description = "To catch logcat from each buffers at the end of failed test cases. "
+                    + "If set to true, a report will be caught through adh shell command at the "
+                    + "end of each failed test cases.")
+    private boolean mLogcatOnFailure = true;
+
     @Option(name = "native-server-process-name",
             description = "Name of a native server process. The runner checks to make sure "
                     + "each specified native server process is not running after the framework stop.")
@@ -382,9 +423,8 @@
     private String mSystraceProcessName = null;
 
     @Option(name = "collect-tests-only",
-            description = "Only invoke the test binary to collect list of applicable test cases. "
-                    + "All test run callbacks will be triggered, but test execution will "
-                    + "not be actually carried out.")
+            description = "Only invoke setUpClass, generate*, and tearDownClass to collect list "
+                    + "of applicable test cases. All collected tests pass without being executed.")
     private boolean mCollectTestsOnly = false;
 
     @Option(name = "gtest-batch-mode", description = "Run Gtest binaries in batch mode.")
@@ -393,15 +433,6 @@
     @Option(name = "log-severity", description = "Set the log severity level.")
     private String mLogSeverity = "INFO";
 
-    // This variable is set in order to include the directory that contains the
-    // python test cases. This is set before calling the method.
-    // {@link #doRunTest(IRunUtil, String, String)}.
-    public String mPythonPath = null;
-
-    @Option(name = "python-binary", description = "python binary to use "
-            + "(optional)")
-    private String mPythonBin = null;
-
     @Option(name = "run-as-vts-self-test",
             description = "Run the module as vts-selftest. "
                     + "When the value is set to true, only setUpClass and tearDownClass function "
@@ -409,26 +440,73 @@
                     + "Note that exception in tearDownClass will not be reported as failure.")
     private boolean mRunAsVtsSelfTest = false;
 
-    private IRunUtil mRunUtil = null;
+    @Option(name = "exclude-coverage-path",
+            description = "The coverage path that should be excluded in results. "
+                    + "Used only when enable-coverage is true.")
+    private Collection<String> mExcludeCoveragePath = new ArrayList<>();
+
+    @Option(name = "mobly-test-module",
+            description = "Mobly test module name. "
+                    + "If this value is specified, VTS will use mobly test template "
+                    + "with the configurations."
+                    + "Multiple values can be added by repeatly using this option.")
+    private Collection<String> mMoblyTestModule = new ArrayList<>();
+
+    @Option(name = "acts-test-module",
+            description = "Acts test module name. "
+                    + "If this value is specified, VTS will use acts test adapter "
+                    + "with the configurations."
+                    + "Multiple values can be added by repeatly using this option.")
+    private String mActsTestModule = null;
+
+    @Option(name = "config-str",
+            description = "Key-value map of custom config string. "
+                    + "The map will be passed directly to python runner and test module. "
+                    + "Only one value per key is stored."
+                    + "If the value for the same key is set multiple times, only the last value is "
+                    + "used.")
+    private TreeMap<String, String> mConfigStr = new TreeMap<>();
+
+    @Option(name = "config-int",
+            description = "Key-value map of custom config integer. "
+                    + "The map will be passed directly to python runner and test module. "
+                    + "Only one value per key is stored."
+                    + "If the value for the same key is set multiple times, only the last value is "
+                    + "used.")
+    private TreeMap<String, Integer> mConfigInt = new TreeMap<>();
+
+    @Option(name = "config-bool",
+            description = "Key-value map of custom config boolean. "
+                    + "The map will be passed directly to python runner and test module. "
+                    + "Only one value per key is stored."
+                    + "If the value for the same key is set multiple times, only the last value is "
+                    + "used.")
+    private TreeMap<String, Boolean> mConfigBool = new TreeMap<>();
+
     private IBuildInfo mBuildInfo = null;
     private String mRunName = "VtsHostDrivenTest";
     // the path of a dir which contains the test data files.
     private String mTestCaseDataDir = "./";
 
     private VtsVendorConfigFileUtil configReader = null;
+    private IInvocationContext mInvocationContext = null;
+    private OutputUtil mOutputUtil = null;
 
     /**
-     * @return the mRunUtil
+     * {@inheritDoc}
      */
-    public IRunUtil getRunUtil() {
-        return mRunUtil;
+    @Override
+    public void setInvocationContext(IInvocationContext context) {
+        mInvocationContext = context;
+        setDevice(context.getDevices().get(0));
+        setBuild(context.getBuildInfos().get(0));
     }
 
     /**
-     * @param mRunUtil the mRunUtil to set
+     * @return the mInvocationContext
      */
-    public void setRunUtil(IRunUtil mRunUtil) {
-        this.mRunUtil = mRunUtil;
+    public IInvocationContext getInvocationContext() {
+        return mInvocationContext;
     }
 
     /**
@@ -516,6 +594,29 @@
     }
 
     /**
+     * Generate a device json object from ITestDevice object.
+     *
+     * @param device device object
+     * @throws RuntimeException
+     * @throws JSONException
+     */
+    private JSONObject generateJsonDeviceItem(ITestDevice device) throws JSONException {
+        JSONObject deviceItemObject = new JSONObject();
+        deviceItemObject.put(SERIAL, device.getSerialNumber());
+        try {
+            deviceItemObject.put("product_type", device.getProductType());
+            deviceItemObject.put("product_variant", device.getProductVariant());
+            deviceItemObject.put("build_alias", device.getBuildAlias());
+            deviceItemObject.put("build_id", device.getBuildId());
+            deviceItemObject.put("build_flavor", device.getBuildFlavor());
+        } catch (DeviceNotAvailableException e) {
+            CLog.e("Device %s not available.", device.getSerialNumber());
+            throw new RuntimeException("Failed to get device information");
+        }
+        return deviceItemObject;
+    }
+
+    /**
      * {@inheritDoc}
      */
     @SuppressWarnings("deprecation")
@@ -523,7 +624,17 @@
     public void run(ITestInvocationListener listener)
             throws IllegalArgumentException, DeviceNotAvailableException {
         if (mDevice == null) {
-            throw new DeviceNotAvailableException("Device has not been set");
+            throw new DeviceNotAvailableException("Device has not been set.");
+        }
+
+        if (mBuildInfo == null) {
+            throw new RuntimeException("BuildInfo has not been set.");
+        }
+
+        mOutputUtil = new OutputUtil(listener);
+        mOutputUtil.setTestModuleName(mTestModuleName);
+        if (mAbi != null) {
+            mOutputUtil.setAbiName(mAbi.getName());
         }
 
         if (mTestCasePath == null) {
@@ -542,7 +653,7 @@
                     default:
                         template = TEMPLATE_BINARY_TEST_PATH;
                 }
-                CLog.i("Using default test case template at %s.", template);
+                CLog.d("Using default test case template at %s.", template);
                 setTestCasePath(template);
                 if (mEnableCoverage && !mGlobalCoverage) {
                     CLog.e("Only global coverage is supported for test type %s.", mBinaryTestType);
@@ -553,22 +664,15 @@
             } else if (mBinaryTestType.equals(BINARY_TEST_TYPE_LLVMFUZZER)) {
                 // Fuzz test don't need test-case-path.
                 setTestCasePath(TEMPLATE_LLVMFUZZER_TEST_PATH);
+            } else if (!mMoblyTestModule.isEmpty()) {
+                setTestCasePath(TEMPLATE_MOBLY_TEST_PATH);
+            } else if (mActsTestModule != null) {
+                setTestCasePath(ADAPTER_ACTS_PATH);
             } else {
                 throw new IllegalArgumentException("test-case-path is not set.");
             }
         }
 
-        setPythonPath();
-
-        if (mPythonBin == null) {
-            mPythonBin = getPythonBinary();
-        }
-
-        if (mRunUtil == null){
-            mRunUtil = new RunUtil();
-            mRunUtil.setEnvVariable(PYTHONPATH, mPythonPath);
-        }
-
         doRunTest(listener);
     }
 
@@ -593,7 +697,7 @@
      */
     private void populateDefaultJsonFields(JSONObject jsonObject, String testCaseDataDir)
             throws IOException, JSONException {
-        CLog.i("Populating default fields to json object from %s", DEFAULT_TESTCASE_CONFIG_PATH);
+        CLog.d("Populating default fields to json object from %s", DEFAULT_TESTCASE_CONFIG_PATH);
         String content = FileUtil.readStringFromFile(new File(mTestCaseDataDir, DEFAULT_TESTCASE_CONFIG_PATH));
         JSONObject defaultJsonObject = new JSONObject(content);
 
@@ -611,7 +715,7 @@
      * @param log_path the path of a directory to store the VTS runner logs.
      * @return the updated JSONObject as the new test config.
      */
-    private void updateVtsRunnerTestConfig(JSONObject jsonObject)
+    protected void updateVtsRunnerTestConfig(JSONObject jsonObject)
             throws IOException, JSONException, RuntimeException {
         configReader = new VtsVendorConfigFileUtil();
         if (configReader.LoadVendorConfig(mBuildInfo)) {
@@ -621,51 +725,61 @@
             }
         }
 
-        CLog.i("Load original test config %s %s", mTestCaseDataDir, mTestConfigPath);
+        CLog.d("Load original test config %s %s", mTestCaseDataDir, mTestConfigPath);
         String content = null;
 
         if (mTestConfigPath != null) {
             content = FileUtil.readStringFromFile(
                     new File(Paths.get(mTestCaseDataDir, mTestConfigPath).toString()));
-            CLog.i("Loaded original test config %s", content);
+            CLog.d("Loaded original test config %s", content);
             if (content != null) {
                 JsonUtil.deepMergeJsonObjects(jsonObject, new JSONObject(content));
             }
         }
 
         populateDefaultJsonFields(jsonObject, mTestCaseDataDir);
-        CLog.i("Built a Json object using the loaded original test config");
+        CLog.d("Built a Json object using the loaded original test config");
 
         JSONArray deviceArray = new JSONArray();
-        JSONObject deviceItemObject = new JSONObject();
-        deviceItemObject.put(SERIAL, mDevice.getSerialNumber());
 
         boolean coverageBuild = false;
         boolean sancovBuild = false;
 
-        try {
-            deviceItemObject.put("product_type", mDevice.getProductType());
-            deviceItemObject.put("product_variant", mDevice.getProductVariant());
-            deviceItemObject.put("build_alias", mDevice.getBuildAlias());
-            deviceItemObject.put("build_id", mDevice.getBuildId());
-            deviceItemObject.put("build_flavor", mDevice.getBuildFlavor());
+        boolean first_device = true;
+        for (ITestDevice device : mInvocationContext.getDevices()) {
+            JSONObject deviceJson = generateJsonDeviceItem(device);
+            try {
+                String coverageProperty = device.getProperty(COVERAGE_PROPERTY);
+                boolean enable_coverage_for_device =
+                        coverageProperty != null && coverageProperty.equals("1");
+                if (first_device) {
+                    coverageBuild = enable_coverage_for_device;
+                    first_device = false;
+                } else {
+                    if (coverageBuild && (!enable_coverage_for_device)) {
+                        CLog.e("Device %s is not coverage build while others are.",
+                                device.getSerialNumber());
+                        throw new RuntimeException("Device build not the same.");
+                    }
+                }
+            } catch (DeviceNotAvailableException e) {
+                CLog.e("Device %s not available.", device.getSerialNumber());
+                throw new RuntimeException("Failed to get device information");
+            }
 
             File sancovDir =
-                    mBuildInfo.getFile(VtsCoveragePreparer.getSancovResourceDirKey(mDevice));
+                    mBuildInfo.getFile(VtsCoveragePreparer.getSancovResourceDirKey(device));
             if (sancovDir != null) {
-                deviceItemObject.put("sancov_resources_path", sancovDir.getAbsolutePath());
+                deviceJson.put("sancov_resources_path", sancovDir.getAbsolutePath());
                 sancovBuild = true;
             }
-            File gcovDir = mBuildInfo.getFile(VtsCoveragePreparer.getGcovResourceDirKey(mDevice));
+            File gcovDir = mBuildInfo.getFile(VtsCoveragePreparer.getGcovResourceDirKey(device));
             if (gcovDir != null) {
-                deviceItemObject.put("gcov_resources_path", gcovDir.getAbsolutePath());
+                deviceJson.put("gcov_resources_path", gcovDir.getAbsolutePath());
                 coverageBuild = true;
             }
-        } catch (DeviceNotAvailableException e) {
-            CLog.e("A device not available - continuing");
-            throw new RuntimeException("Failed to get device information");
+            deviceArray.put(deviceJson);
         }
-        deviceArray.put(deviceItemObject);
 
         JSONArray testBedArray = (JSONArray) jsonObject.get(TEST_BED);
         if (testBedArray.length() == 0) {
@@ -685,7 +799,7 @@
                         "Failed to derive test module name; use --test-module-name option");
                 }
             }
-            CLog.logAndDisplay(LogLevel.INFO, "Setting test name as %s", testName);
+            CLog.d("Setting test module name as %s", testName);
             testBedItemObject.put(NAME, testName);
             testBedItemObject.put(ANDROIDDEVICE, deviceArray);
             testBedArray.put(testBedItemObject);
@@ -712,31 +826,36 @@
             throw new RuntimeException("Failed to produce VTS runner test config");
         }
         jsonObject.put(DATA_FILE_PATH, mTestCaseDataDir);
-        CLog.i("Added %s = %s to the Json object", DATA_FILE_PATH, mTestCaseDataDir);
+        CLog.d("Added %s = %s to the Json object", DATA_FILE_PATH, mTestCaseDataDir);
 
         JSONObject build = new JSONObject();
         build.put(BUILD_ID, mBuildInfo.getBuildId());
         build.put(BUILD_TARGET, mBuildInfo.getBuildTargetName());
         jsonObject.put(BUILD, build);
-        CLog.i("Added %s to the Json object", BUILD);
+        CLog.d("Added %s to the Json object", BUILD);
 
         JSONObject suite = new JSONObject();
         suite.put(NAME, mBuildInfo.getTestTag());
         suite.put(INCLUDE_FILTER, new JSONArray(mIncludeFilters));
-        CLog.i("Added include filter to test suite: %s", mIncludeFilters);
+        CLog.d("Added include filter to test suite: %s", mIncludeFilters);
         suite.put(EXCLUDE_FILTER, new JSONArray(mExcludeFilters));
-        CLog.i("Added exclude filter to test suite: %s", mExcludeFilters);
+        CLog.d("Added exclude filter to test suite: %s", mExcludeFilters);
+
+        String coverageReportPath = mBuildInfo.getBuildAttributes().get("coverage_report_path");
+        if (coverageReportPath != null) {
+            jsonObject.put(OUTPUT_COVERAGE_REPORT, true);
+            CLog.d("Added %s to the Json object", OUTPUT_COVERAGE_REPORT);
+            jsonObject.put(COVERAGE_REPORT_PATH, coverageReportPath);
+            CLog.d("Added %s to the Json object", COVERAGE_REPORT_PATH);
+        }
 
         if (mExcludeOverInclude) {
             jsonObject.put(EXCLUDE_OVER_INCLUDE, mExcludeOverInclude);
-            CLog.i("Added %s to the Json object", EXCLUDE_OVER_INCLUDE);
+            CLog.d("Added %s to the Json object", EXCLUDE_OVER_INCLUDE);
         }
 
         jsonObject.put(TEST_SUITE, suite);
-        CLog.i("Added %s to the Json object", TEST_SUITE);
-
-        jsonObject.put(TEST_MAX_TIMEOUT, mTestTimeout);
-        CLog.i("Added %s to the Json object: %d", TEST_MAX_TIMEOUT, mTestTimeout);
+        CLog.d("Added %s to the Json object", TEST_SUITE);
 
         if (!mLogSeverity.isEmpty()) {
             String logSeverity = mLogSeverity.toUpperCase();
@@ -748,191 +867,260 @@
                 logSeverity = "INFO";
             }
             jsonObject.put(LOG_SEVERITY, logSeverity);
-            CLog.i("Added %s to the Json object: %s", LOG_SEVERITY, logSeverity);
+            CLog.d("Added %s to the Json object: %s", LOG_SEVERITY, logSeverity);
         }
 
         if (mAbi != null) {
             jsonObject.put(ABI_NAME, mAbi.getName());
-            CLog.i("Added %s to the Json object", ABI_NAME);
+            CLog.d("Added %s to the Json object", ABI_NAME);
             jsonObject.put(ABI_BITNESS, mAbi.getBitness());
-            CLog.i("Added %s to the Json object", ABI_BITNESS);
+            CLog.d("Added %s to the Json object", ABI_BITNESS);
         }
 
         if (mSkipOn32BitAbi) {
             jsonObject.put(SKIP_ON_32BIT_ABI, mSkipOn32BitAbi);
-            CLog.i("Added %s to the Json object", SKIP_ON_32BIT_ABI);
+            CLog.d("Added %s to the Json object", SKIP_ON_32BIT_ABI);
         }
 
         if (mSkipOn64BitAbi) {
             jsonObject.put(SKIP_ON_64BIT_ABI, mSkipOn64BitAbi);
-            CLog.i("Added %s to the Json object", SKIP_ON_64BIT_ABI);
+            CLog.d("Added %s to the Json object", SKIP_ON_64BIT_ABI);
         } else if (mRun32bBitOn64BitAbi) {
             jsonObject.put(RUN_32BIT_ON_64BIT_ABI, mRun32bBitOn64BitAbi);
-            CLog.i("Added %s to the Json object", RUN_32BIT_ON_64BIT_ABI);
+            CLog.d("Added %s to the Json object", RUN_32BIT_ON_64BIT_ABI);
         }
 
         if (mSkipIfThermalThrottling) {
             jsonObject.put(SKIP_IF_THERMAL_THROTTLING, mSkipIfThermalThrottling);
-            CLog.i("Added %s to the Json object", SKIP_IF_THERMAL_THROTTLING);
+            CLog.d("Added %s to the Json object", SKIP_IF_THERMAL_THROTTLING);
         }
 
+        jsonObject.put(DISABLE_CPU_FREQUENCY_SCALING, mDisableCpuFrequencyScaling);
+        CLog.d("Added %s to the Json object, value: %s", DISABLE_CPU_FREQUENCY_SCALING,
+                mDisableCpuFrequencyScaling);
+
         if (!mBinaryTestSource.isEmpty()) {
             jsonObject.put(BINARY_TEST_SOURCE, new JSONArray(mBinaryTestSource));
-            CLog.i("Added %s to the Json object", BINARY_TEST_SOURCE);
+            CLog.d("Added %s to the Json object", BINARY_TEST_SOURCE);
         }
 
         if (!mBinaryTestWorkingDirectory.isEmpty()) {
             jsonObject.put(BINARY_TEST_WORKING_DIRECTORY,
                     new JSONArray(mBinaryTestWorkingDirectory));
-            CLog.i("Added %s to the Json object", BINARY_TEST_WORKING_DIRECTORY);
+            CLog.d("Added %s to the Json object", BINARY_TEST_WORKING_DIRECTORY);
         }
 
         if (!mBinaryTestEnvp.isEmpty()) {
             jsonObject.put(BINARY_TEST_ENVP, new JSONArray(mBinaryTestEnvp));
-            CLog.i("Added %s to the Json object", BINARY_TEST_ENVP);
+            CLog.d("Added %s to the Json object", BINARY_TEST_ENVP);
         }
 
         if (!mBinaryTestArgs.isEmpty()) {
             jsonObject.put(BINARY_TEST_ARGS, new JSONArray(mBinaryTestArgs));
-            CLog.i("Added %s to the Json object", BINARY_TEST_ARGS);
+            CLog.d("Added %s to the Json object", BINARY_TEST_ARGS);
         }
 
         if (!mBinaryTestLdLibraryPath.isEmpty()) {
             jsonObject.put(BINARY_TEST_LD_LIBRARY_PATH,
                     new JSONArray(mBinaryTestLdLibraryPath));
-            CLog.i("Added %s to the Json object", BINARY_TEST_LD_LIBRARY_PATH);
+            CLog.d("Added %s to the Json object", BINARY_TEST_LD_LIBRARY_PATH);
         }
 
         if (mBugReportOnFailure) {
             jsonObject.put(BUG_REPORT_ON_FAILURE, mBugReportOnFailure);
-            CLog.i("Added %s to the Json object", BUG_REPORT_ON_FAILURE);
+            CLog.d("Added %s to the Json object", BUG_REPORT_ON_FAILURE);
+        }
+
+        if (!mLogcatOnFailure) {
+            jsonObject.put(LOGCAT_ON_FAILURE, mLogcatOnFailure);
+            CLog.d("Added %s to the Json object", LOGCAT_ON_FAILURE);
         }
 
         if (mEnableProfiling) {
             jsonObject.put(ENABLE_PROFILING, mEnableProfiling);
-            CLog.i("Added %s to the Json object", ENABLE_PROFILING);
+            CLog.d("Added %s to the Json object", ENABLE_PROFILING);
         }
 
         if (mSaveTraceFileRemote) {
             jsonObject.put(SAVE_TRACE_FIEL_REMOTE, mSaveTraceFileRemote);
-            CLog.i("Added %s to the Json object", SAVE_TRACE_FIEL_REMOTE);
+            CLog.d("Added %s to the Json object", SAVE_TRACE_FIEL_REMOTE);
         }
 
         if (mEnableSystrace) {
             jsonObject.put(ENABLE_SYSTRACE, mEnableSystrace);
-            CLog.i("Added %s to the Json object", ENABLE_SYSTRACE);
+            CLog.d("Added %s to the Json object", ENABLE_SYSTRACE);
         }
 
         if (mEnableCoverage) {
             jsonObject.put(GLOBAL_COVERAGE, mGlobalCoverage);
+            if (!mExcludeCoveragePath.isEmpty()) {
+                jsonObject.put(EXCLUDE_COVERAGE_PATH, new JSONArray(mExcludeCoveragePath));
+                CLog.d("Added %s to the Json object", EXCLUDE_COVERAGE_PATH);
+            }
             if (coverageBuild) {
                 jsonObject.put(ENABLE_COVERAGE, mEnableCoverage);
-                CLog.i("Added %s to the Json object", ENABLE_COVERAGE);
+                CLog.d("Added %s to the Json object", ENABLE_COVERAGE);
             } else {
-                CLog.i("Device build has coverage disabled");
+                CLog.d("Device build has coverage disabled");
             }
         }
 
         if (mEnableSancov) {
             if (sancovBuild) {
                 jsonObject.put(ENABLE_SANCOV, mEnableSancov);
-                CLog.i("Added %s to the Json object", ENABLE_SANCOV);
+                CLog.d("Added %s to the Json object", ENABLE_SANCOV);
             } else {
-                CLog.i("Device build has sancov disabled");
+                CLog.d("Device build has sancov disabled");
             }
         }
 
-        if (mOutputCoverageReport) {
-            jsonObject.put(OUTPUT_COVERAGE_REPORT, mOutputCoverageReport);
-            CLog.i("Added %s to the Json object", OUTPUT_COVERAGE_REPORT);
-        }
-
         if (mPreconditionHwBinderServiceName != null) {
             jsonObject.put(PRECONDITION_HWBINDER_SERVICE, mPreconditionHwBinderServiceName);
-            CLog.i("Added %s to the Json object", PRECONDITION_HWBINDER_SERVICE);
+            CLog.d("Added %s to the Json object", PRECONDITION_HWBINDER_SERVICE);
         }
 
         if (mPreconditionFeature != null) {
             jsonObject.put(PRECONDITION_FEATURE, mPreconditionFeature);
-            CLog.i("Added %s to the Json object", PRECONDITION_FEATURE);
+            CLog.d("Added %s to the Json object", PRECONDITION_FEATURE);
         }
 
         if (!mPreconditionFilePathPrefix.isEmpty()) {
             jsonObject.put(
                     PRECONDITION_FILE_PATH_PREFIX, new JSONArray(mPreconditionFilePathPrefix));
-            CLog.i("Added %s to the Json object", PRECONDITION_FILE_PATH_PREFIX);
+            CLog.d("Added %s to the Json object", PRECONDITION_FILE_PATH_PREFIX);
+        }
+
+        if (mPreconditionFirstApiLevel != 0) {
+            jsonObject.put(PRECONDITION_FIRST_API_LEVEL, mPreconditionFirstApiLevel);
+            CLog.d("Added %s to the Json object", PRECONDITION_FIRST_API_LEVEL);
         }
 
         if (mPreconditionLshal != null) {
             jsonObject.put(PRECONDITION_LSHAL, mPreconditionLshal);
-            CLog.i("Added %s to the Json object", PRECONDITION_LSHAL);
+            CLog.d("Added %s to the Json object", PRECONDITION_LSHAL);
         }
 
         if (mPreconditionVintf != null) {
             jsonObject.put(PRECONDITION_VINTF, mPreconditionVintf);
-            CLog.i("Added %s to the Json object", PRECONDITION_VINTF);
+            CLog.d("Added %s to the Json object", PRECONDITION_VINTF);
+        }
+
+        if (mPreconditionSysProp != null) {
+            jsonObject.put(PRECONDITION_SYSPROP, mPreconditionSysProp);
+            CLog.d("Added %s to the Json object", PRECONDITION_SYSPROP);
         }
 
         if (!mBinaryTestProfilingLibraryPath.isEmpty()) {
             jsonObject.put(BINARY_TEST_PROFILING_LIBRARY_PATH,
                     new JSONArray(mBinaryTestProfilingLibraryPath));
-            CLog.i("Added %s to the Json object", BINARY_TEST_PROFILING_LIBRARY_PATH);
+            CLog.d("Added %s to the Json object", BINARY_TEST_PROFILING_LIBRARY_PATH);
+        }
+
+        if (mDisableFramework) {
+            jsonObject.put(DISABLE_FRAMEWORK, mDisableFramework);
+            CLog.d("Added %s to the Json object", DISABLE_FRAMEWORK);
+        }
+
+        if (mStopNativeServers) {
+            jsonObject.put(STOP_NATIVE_SERVERS, mStopNativeServers);
+            CLog.d("Added %s to the Json object", STOP_NATIVE_SERVERS);
         }
 
         if (mBinaryTestDisableFramework) {
             jsonObject.put(BINARY_TEST_DISABLE_FRAMEWORK, mBinaryTestDisableFramework);
-            CLog.i("Added %s to the Json object", BINARY_TEST_DISABLE_FRAMEWORK);
+            CLog.d("Added %s to the Json object", BINARY_TEST_DISABLE_FRAMEWORK);
         }
 
         if (mBinaryTestStopNativeServers) {
             jsonObject.put(BINARY_TEST_STOP_NATIVE_SERVERS, mBinaryTestStopNativeServers);
-            CLog.i("Added %s to the Json object", BINARY_TEST_STOP_NATIVE_SERVERS);
+            CLog.d("Added %s to the Json object", BINARY_TEST_STOP_NATIVE_SERVERS);
         }
 
         if (!mNativeServerProcessName.isEmpty()) {
             jsonObject.put(NATIVE_SERVER_PROCESS_NAME, new JSONArray(mNativeServerProcessName));
-            CLog.i("Added %s to the Json object", NATIVE_SERVER_PROCESS_NAME);
+            CLog.d("Added %s to the Json object", NATIVE_SERVER_PROCESS_NAME);
         }
 
         if (!mHalHidlReplayTestTracePaths.isEmpty()) {
             jsonObject.put(HAL_HIDL_REPLAY_TEST_TRACE_PATHS,
                     new JSONArray(mHalHidlReplayTestTracePaths));
-            CLog.i("Added %s to the Json object", HAL_HIDL_REPLAY_TEST_TRACE_PATHS);
+            CLog.d("Added %s to the Json object", HAL_HIDL_REPLAY_TEST_TRACE_PATHS);
         }
 
         if (mHalHidlPackageName != null) {
             jsonObject.put(HAL_HIDL_PACKAGE_NAME, mHalHidlPackageName);
-            CLog.i("Added %s to the Json object", SYSTRACE_PROCESS_NAME);
+            CLog.d("Added %s to the Json object", SYSTRACE_PROCESS_NAME);
         }
 
         if (mSystraceProcessName != null) {
             jsonObject.put(SYSTRACE_PROCESS_NAME, mSystraceProcessName);
-            CLog.i("Added %s to the Json object", SYSTRACE_PROCESS_NAME);
+            CLog.d("Added %s to the Json object", SYSTRACE_PROCESS_NAME);
         }
 
         if (mPassthroughMode) {
             jsonObject.put(PASSTHROUGH_MODE, mPassthroughMode);
-            CLog.i("Added %s to the Json object", PASSTHROUGH_MODE);
+            CLog.d("Added %s to the Json object", PASSTHROUGH_MODE);
+        }
+
+        if (mCollectTestsOnly) {
+            jsonObject.put(COLLECT_TESTS_ONLY, mCollectTestsOnly);
+            CLog.d("Added %s to the Json object", COLLECT_TESTS_ONLY);
         }
 
         if (mGtestBatchMode) {
             jsonObject.put(GTEST_BATCH_MODE, mGtestBatchMode);
-            CLog.i("Added %s to the Json object", GTEST_BATCH_MODE);
+            CLog.d("Added %s to the Json object", GTEST_BATCH_MODE);
         }
 
         if (mLtpNumberOfThreads >= 0) {
             jsonObject.put(LTP_NUMBER_OF_THREADS, mLtpNumberOfThreads);
-            CLog.i("Added %s to the Json object", LTP_NUMBER_OF_THREADS);
+            CLog.d("Added %s to the Json object", LTP_NUMBER_OF_THREADS);
         }
 
         if (mRunAsVtsSelfTest) {
             jsonObject.put(RUN_AS_VTS_SELF_TEST, mRunAsVtsSelfTest);
-            CLog.i("Added %s to the Json object", RUN_AS_VTS_SELF_TEST);
+            CLog.d("Added %s to the Json object", RUN_AS_VTS_SELF_TEST);
         }
 
         if ("vts".equals(mBuildInfo.getTestTag())) {
             jsonObject.put(RUN_AS_COMPLIANCE_TEST, true);
-            CLog.i("Added %s to the Json object", RUN_AS_COMPLIANCE_TEST);
+            CLog.d("Added %s to the Json object", RUN_AS_COMPLIANCE_TEST);
+        }
+
+        if (!mMoblyTestModule.isEmpty()) {
+            jsonObject.put(MOBLY_TEST_MODULE, new JSONArray(mMoblyTestModule));
+            CLog.d("Added %s to the Json object", MOBLY_TEST_MODULE);
+        }
+
+        if (mActsTestModule != null) {
+            jsonObject.put(ACTS_TEST_MODULE, mActsTestModule);
+            CLog.d("Added %s to the Json object", ACTS_TEST_MODULE);
+        }
+
+        if (mBuildInfo.getFile(VtsPythonVirtualenvPreparer.VIRTUAL_ENV) != null) {
+            jsonObject.put(VtsPythonVirtualenvPreparer.VIRTUAL_ENV,
+                    mBuildInfo.getFile(VtsPythonVirtualenvPreparer.VIRTUAL_ENV).getAbsolutePath());
+        }
+
+        if (mBuildInfo.getFile(VtsPythonVirtualenvPreparer.VIRTUAL_ENV_V3) != null) {
+            jsonObject.put(VtsPythonVirtualenvPreparer.VIRTUAL_ENV_V3,
+                    mBuildInfo.getFile(VtsPythonVirtualenvPreparer.VIRTUAL_ENV_V3)
+                            .getAbsolutePath());
+        }
+
+        if (!mConfigStr.isEmpty()) {
+            jsonObject.put(CONFIG_STR, new JSONObject(mConfigStr));
+            CLog.d("Added %s to the Json object", CONFIG_STR);
+        }
+
+        if (!mConfigInt.isEmpty()) {
+            jsonObject.put(CONFIG_INT, new JSONObject(mConfigInt));
+            CLog.d("Added %s to the Json object", CONFIG_INT);
+        }
+
+        if (!mConfigBool.isEmpty()) {
+            jsonObject.put(CONFIG_BOOL, new JSONObject(mConfigBool));
+            CLog.d("Added %s to the Json object", CONFIG_BOOL);
         }
     }
 
@@ -961,80 +1149,23 @@
         }
         File reportFile = mBuildInfo.getFile(TEST_PLAN_REPORT_FILE);
 
-        try (FileWriter fw = new FileWriter(reportFile.getAbsoluteFile(), true);
-                BufferedWriter bw = new BufferedWriter(fw); PrintWriter out = new PrintWriter(bw)) {
-            out.println(String.format("%s %s", test_module_name, test_module_timestamp));
-        } catch (IOException e) {
-            CLog.e(String.format(
-                    "Can't write to the test plan result file, %s", TEST_PLAN_REPORT_FILE));
-            return false;
+        if (reportFile != null) {
+            try (FileWriter fw = new FileWriter(reportFile.getAbsoluteFile(), true);
+                    BufferedWriter bw = new BufferedWriter(fw);
+                    PrintWriter out = new PrintWriter(bw)) {
+                out.println(String.format("%s %s", test_module_name, test_module_timestamp));
+            } catch (IOException e) {
+                CLog.e(String.format(
+                        "Can't write to the test plan result file, %s", TEST_PLAN_REPORT_FILE));
+                return false;
+            }
+        } else {
+            CLog.w("No test plan report file configured.");
         }
         return true;
     }
 
     /**
-     * Create a {@link ProcessHelper} from mRunUtil.
-     *
-     * @param cmd the command to run.
-     * @throws IOException if fails to start Process.
-     */
-    protected ProcessHelper createProcessHelper(String[] cmd) throws IOException {
-        return new ProcessHelper(mRunUtil.runCmdInBackground(cmd));
-    }
-
-    /**
-     * Run VTS Python runner and handle interrupt from TradeFed.
-     *
-     * @param cmd the command to start VTS Python runner.
-     * @param commandResult the object containing the command result.
-     * @return null if the command terminates or times out; a message string if the command is
-     * interrupted by TradeFed.
-     */
-    private String runPythonRunner(String[] cmd, CommandResult commandResult) {
-        ProcessHelper process;
-        try {
-            process = createProcessHelper(cmd);
-        } catch (IOException e) {
-            CLog.e(e);
-            commandResult.setStatus(CommandStatus.EXCEPTION);
-            commandResult.setStdout("");
-            commandResult.setStderr("");
-            return null;
-        }
-
-        String interruptMessage;
-        try {
-            CommandStatus commandStatus;
-            try {
-                commandStatus = process.waitForProcess(mTestTimeout);
-                interruptMessage = null;
-            } catch (RunInterruptedException e) {
-                CLog.e("Python process is interrupted.");
-                commandStatus = CommandStatus.TIMED_OUT;
-                interruptMessage = (e.getMessage() != null ? e.getMessage() : "");
-            }
-            if (process.isRunning()) {
-                CLog.e("Cancel Python process and wait %d seconds.",
-                        TEST_ABORT_TIMEOUT_MSECS / 1000);
-                try {
-                    process.closeStdin();
-                    // Wait for the process to clean up and ignore the CommandStatus.
-                    // Propagate RunInterruptedException if this is interrupted again.
-                    process.waitForProcess(TEST_ABORT_TIMEOUT_MSECS);
-                } catch (IOException e) {
-                    CLog.e("Fail to cancel Python process.");
-                }
-            }
-            commandResult.setStatus(commandStatus);
-        } finally {
-            process.cleanUp();
-        }
-        commandResult.setStdout(process.getStdout());
-        commandResult.setStderr(process.getStderr());
-        return interruptMessage;
-    }
-
-    /**
      * This method prepares a command for the test and runs the python file as
      * given in the arguments.
      *
@@ -1043,7 +1174,9 @@
      * @throws IllegalArgumentException
      */
     private void doRunTest(ITestRunListener listener) throws RuntimeException, IllegalArgumentException {
-        CLog.i("Device serial number: " + mDevice.getSerialNumber());
+        CLog.d("Device serial number: " + mDevice.getSerialNumber());
+
+        setTestCaseDataDir();
 
         JSONObject jsonObject = new JSONObject();
         File vtsRunnerLogDir = null;
@@ -1052,21 +1185,21 @@
             updateVtsRunnerTestConfig(jsonObject);
 
             jsonObject.put(LOG_PATH,  vtsRunnerLogDir.getAbsolutePath());
-            CLog.i("Added %s to the Json object", LOG_PATH);
+            CLog.d("Added %s to the Json object", LOG_PATH);
         } catch(IOException e) {
             throw new RuntimeException("Failed to read test config json file");
         } catch(JSONException e) {
             throw new RuntimeException("Failed to build updated test config json data");
         }
 
-        CLog.i("config json: %s", jsonObject.toString());
+        CLog.d("config json: %s", jsonObject.toString());
 
         String jsonFilePath = null;
         try {
             File tmpFile = FileUtil.createTempFile(
                     mBuildInfo.getTestTag() + "-config-" + mBuildInfo.getDeviceSerial(), ".json");
             jsonFilePath = tmpFile.getAbsolutePath();
-            CLog.i("config json file path: %s", jsonFilePath);
+            CLog.d("config json file path: %s", jsonFilePath);
             FileWriter fw = new FileWriter(jsonFilePath);
             fw.write(jsonObject.toString());
             fw.close();
@@ -1074,48 +1207,41 @@
             throw new RuntimeException("Failed to create device config json file");
         }
 
-        if (mPythonBin == null){
-            mPythonBin = getPythonBinary();
-        }
-        mRunUtil.setEnvVariable("VTS", "1");
-        String[] baseOpts = {
-                mPythonBin,
-        };
-        String[] testModule = new String[2];
-        String[] cmd;
+        VtsPythonRunnerHelper vtsPythonRunnerHelper = createVtsPythonRunnerHelper();
+
+        List<String> cmd = new ArrayList<>();
+        cmd.add("python");
         if (mTestCasePathType != null && mTestCasePathType.toLowerCase().equals("file")) {
-            testModule[0] = mTestCasePath;
-            if (!mTestCasePath.endsWith(".py")) {
-                testModule[0] += ".py";
+            String testScript = mTestCasePath;
+            if (!testScript.endsWith(".py")) {
+                testScript += ".py";
             }
+            cmd.add(testScript);
         } else {
-            baseOpts = new String[2];
-            baseOpts[0] = mPythonBin;
-            baseOpts[1] = "-m";
-            testModule[0] = mTestCasePath.replace("/", ".");
+            cmd.add("-m");
+            cmd.add(mTestCasePath.replace("/", "."));
         }
-        testModule[1] = jsonFilePath;
-        cmd = ArrayUtil.buildArray(baseOpts, testModule);
+        cmd.add(jsonFilePath);
 
         printToDeviceLogcatAboutTestModuleStatus("BEGIN");
 
         CommandResult commandResult = new CommandResult();
-        String interruptMessage = runPythonRunner(cmd, commandResult);
+        String interruptMessage = vtsPythonRunnerHelper.runPythonRunner(
+                cmd.toArray(new String[0]), commandResult, mTestTimeout);
 
         if (commandResult != null) {
             CommandStatus commandStatus = commandResult.getStatus();
             if (commandStatus != CommandStatus.SUCCESS
                 && commandStatus != CommandStatus.TIMED_OUT) {
                 CLog.e("Python process failed");
-                CLog.e("Python path: %s", mPythonPath);
-                CLog.e("Stderr: %s", commandResult.getStderr());
-                CLog.e("Stdout: %s", commandResult.getStdout());
-                printVtsLogs(vtsRunnerLogDir);
+                CLog.e("Command stdout: " + commandResult.getStdout());
+                CLog.e("Command stderr: " + commandResult.getStderr());
+                CLog.e("Command status: " + commandStatus);
+                CLog.e("Python log: ");
+                mOutputUtil.collectVtsRunnerOutputs(vtsRunnerLogDir);
                 printToDeviceLogcatAboutTestModuleStatus("ERROR");
                 throw new RuntimeException("Failed to run VTS test");
             }
-            CLog.i("Standard output is: %s", commandResult.getStdout());
-            CLog.i("Parsing test result: %s", commandResult.getStderr());
             printToDeviceLogcatAboutTestModuleStatus("END");
         }
 
@@ -1134,49 +1260,37 @@
             JSONObject object = null;
             File testRunSummary = getFileTestRunSummary(vtsRunnerLogDir);
             if (testRunSummary == null) {
-                throw new RuntimeException("Couldn't locate the file : " +
-                        TEST_RUN_SUMMARY_FILE_NAME);
-            }
-            try {
-                jsonData = FileUtil.readStringFromFile(testRunSummary);
-                CLog.i("Test Result Summary: %s", jsonData);
-                object = new JSONObject(jsonData);
-            } catch (IOException e) {
-                CLog.e("Error occurred in parsing Json file : %s", testRunSummary.toPath());
-            } catch (JSONException e) {
-                CLog.e("Error occurred in parsing Json String : %s", jsonData);
-            }
-            if (object == null) {
-                CLog.e("Json object is null.");
-                throw new RuntimeException("Json object is null.");
-            }
-            parser.processJsonFile(object);
+                CLog.e("Couldn't locate the file : " + TEST_RUN_SUMMARY_FILE_NAME);
+            } else {
+                try {
+                    jsonData = FileUtil.readStringFromFile(testRunSummary);
+                    CLog.d("Test Result Summary: %s", jsonData);
+                    object = new JSONObject(jsonData);
+                } catch (IOException e) {
+                    CLog.e("Error occurred in parsing Json file : %s", testRunSummary.toPath());
+                } catch (JSONException e) {
+                    CLog.e("Error occurred in parsing Json String : %s", jsonData);
+                }
+                if (object == null) {
+                    CLog.e("Json object is null.");
+                    throw new RuntimeException("Json object is null.");
+                }
+                parser.processJsonFile(object);
 
-            try {
-                JSONObject planObject = object.getJSONObject(TESTMODULE);
-                String test_module_name = planObject.getString("Name");
-                long test_module_timestamp = planObject.getLong("Timestamp");
-                AddTestModuleKeys(test_module_name, test_module_timestamp);
-            } catch (JSONException e) {
-                CLog.d("Key '%s' not found in result json summary", TESTMODULE);
+                try {
+                    JSONObject planObject = object.getJSONObject(TESTMODULE);
+                    String test_module_name = planObject.getString("Name");
+                    long test_module_timestamp = planObject.getLong("Timestamp");
+                    AddTestModuleKeys(test_module_name, test_module_timestamp);
+                } catch (JSONException e) {
+                    CLog.d("Key '%s' not found in result json summary", TESTMODULE);
+                }
             }
         }
-        printVtsLogs(vtsRunnerLogDir);
+        mOutputUtil.collectVtsRunnerOutputs(vtsRunnerLogDir);
 
-        File reportMsg;
-        int waitCount = 0;
-        // Wait python process to finish for 3 minutes at most
-        while ((reportMsg = FileUtil.findFile(vtsRunnerLogDir, REPORT_MESSAGE_FILE_NAME)) == null
-                && waitCount < 180) {
-            try {
-                Thread.sleep(1000);
-            } catch (Exception e) {
-                System.out.println(e);
-            }
-            waitCount++;
-        }
-
-        CLog.i("Report message path: %s", reportMsg);
+        File reportMsg = FileUtil.findFile(vtsRunnerLogDir, REPORT_MESSAGE_FILE_NAME);
+        CLog.d("Report message path: %s", reportMsg);
 
         if (reportMsg == null) {
             CLog.e("Cannot find report message proto file.");
@@ -1185,14 +1299,14 @@
             VtsDashboardUtil dashboardUtil = new VtsDashboardUtil(configReader);
             dashboardUtil.Upload(reportMsg.getAbsolutePath());
         } else {
-            CLog.i("Result uploading is not enabled.");
+            CLog.d("Result uploading is not enabled.");
         }
 
         FileUtil.recursiveDelete(vtsRunnerLogDir);
-        CLog.i("Deleted the runner log dir, %s.", vtsRunnerLogDir);
+        CLog.d("Deleted the runner log dir, %s.", vtsRunnerLogDir);
         if (jsonFilePath != null) {
           FileUtil.deleteFile(new File(jsonFilePath));
-          CLog.i("Deleted the runner json config file, %s.", jsonFilePath);
+          CLog.d("Deleted the runner json config file, %s.", jsonFilePath);
         }
 
         if (interruptMessage != null) {
@@ -1201,10 +1315,10 @@
     }
 
     /**
-     * This method return the file test_run_details.txt which is then used to parse logs.
+     * This method return the file test_run_summary.json which is then used to parse logs.
      *
      * @param logDir : The file that needs to be converted
-     * @return the file named test_run_details.txt
+     * @return the file named test_run_summary.json
      * @throws IllegalArgumentException
      */
     private File getFileTestRunSummary(File logDir) throws IllegalArgumentException {
@@ -1231,133 +1345,33 @@
     }
 
     /**
-     * The method prints all VTS runner log files
-     *
-     * @param logDir the File instance of the base log dir.
+     * Set the path for android-vts/testcases/ which keeps the VTS python code under vts.
      */
-    private void printVtsLogs(File logDir) {
-        File[] children;
-        if (logDir == null) {
-            CLog.e("Scan VTS log dir: null\n");
-            return;
+    private void setTestCaseDataDir() {
+        CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mBuildInfo);
+        File testDir = null;
+        try {
+            testDir = buildHelper.getTestsDir();
+        } catch (FileNotFoundException e) {
+            /* pass */
         }
-        CLog.i("Scan VTS log dir %s\n", logDir.getAbsolutePath());
-        children = logDir.listFiles();
-        if (children != null) {
-            for (File child : children) {
-                if (child.isDirectory()) {
-                    printVtsLogs(child);
-                } else {
-                    CLog.i("VTS log file %s\n", child.getAbsolutePath());
-                    try {
-                        if (child.getName().startsWith("vts_agent") &&
-                                child.getName().endsWith(".log")) {
-                            CLog.i("Content: %s\n", FileUtil.readStringFromFile(child));
-                        } else {
-                            CLog.i("skip %s\n", child.getName());
-                        }
-                    } catch (IOException e) {
-                        CLog.e("I/O error\n");
-                    }
-                }
-            }
+        if (testDir != null) {
+            mTestCaseDataDir = testDir.getAbsolutePath();
         }
     }
 
     /**
-     * This method returns whether the OS is Windows.
-     */
-    private static boolean isOnWindows() {
-        return System.getProperty(OS_NAME).contains(WINDOWS);
-    }
-
-    /**
-     * This method sets the python path. It's based on the
-     * assumption that the environment variable $ANDROID_BUILD_TOP is set.
-     */
-    private void setPythonPath() {
-        StringBuilder sb = new StringBuilder();
-        String separator = File.pathSeparator;
-        if (System.getenv(PYTHONPATH) != null) {
-            sb.append(separator);
-            sb.append(System.getenv(PYTHONPATH));
-        }
-
-        // to get the path for android-vts/testcases/ which keeps the VTS python code under vts.
-        if (mBuildInfo != null) {
-            CompatibilityBuildHelper mBuildHelper = new CompatibilityBuildHelper(mBuildInfo);
-
-            File testDir = null;
-            try {
-                testDir = mBuildHelper.getTestsDir();
-            } catch (FileNotFoundException e) {
-                /* pass */
-            }
-            if (testDir != null) {
-                sb.append(separator);
-                mTestCaseDataDir = testDir.getAbsolutePath();
-                sb.append(mTestCaseDataDir);
-            } else if (mBuildInfo.getFile(VTS) != null) {
-                sb.append(separator);
-                sb.append(mBuildInfo.getFile(VTS).getAbsolutePath()).append("/..");
-            }
-        }
-
-        // for when one uses PythonVirtualenvPreparer.
-        if (mBuildInfo.getFile(PYTHONPATH) != null) {
-            sb.append(separator);
-            sb.append(mBuildInfo.getFile(PYTHONPATH).getAbsolutePath());
-        }
-        if (System.getenv("ANDROID_BUILD_TOP") != null) {
-            sb.append(separator);
-            sb.append(System.getenv("ANDROID_BUILD_TOP")).append("/test");
-        }
-        if (sb.length() == 0) {
-            throw new RuntimeException("Could not find python path on host machine");
-        }
-        mPythonPath = sb.substring(1);
-        CLog.i("mPythonPath: %s", mPythonPath);
-    }
-
-    /**
-     * This method gets the python binary.
-     */
-    private String getPythonBinary() {
-        boolean isWindows = isOnWindows();
-        String python = (isWindows ? "python.exe" : "python" + mPythonVersion);
-        File venvDir = mBuildInfo.getFile(VIRTUAL_ENV_PATH);
-        if (venvDir != null) {
-            String binDir = (isWindows? "Scripts": "bin");
-            File pythonBinaryFile = new File(venvDir.getAbsolutePath(),
-                    binDir + File.separator + python);
-            String pythonBinPath = pythonBinaryFile.getAbsolutePath();
-            if (pythonBinaryFile.exists()) {
-                CLog.i("Python path " + pythonBinPath + ".\n");
-                return pythonBinPath;
-            }
-            CLog.e(python + " doesn't exist under the " +
-                   "created virtualenv dir (" + pythonBinPath + ").\n");
-        } else {
-          CLog.e(VIRTUAL_ENV_PATH + " not available in BuildInfo. " +
-                 "Please use VtsPythonVirtualenvPreparer tartget preparer.\n");
-        }
-
-        IRunUtil runUtil = (mRunUtil == null ? RunUtil.getDefault() : mRunUtil);
-        CommandResult c = runUtil.runTimedCmd(1000,
-                (isWindows ? "where" : "which"), python);
-        String pythonBin = c.getStdout().trim();
-        if (pythonBin.length() == 0) {
-            throw new RuntimeException("Could not find python binary on host "
-                    + "machine");
-        }
-        return pythonBin;
-    }
-
-    /**
      * {@inheritDoc}
      */
     @Override
     public void setAbi(IAbi abi){
         mAbi = abi;
     }
+
+    /**
+     * Creates VtsPythonRunnerHelper.
+     */
+    protected VtsPythonRunnerHelper createVtsPythonRunnerHelper() {
+        return new VtsPythonRunnerHelper(mBuildInfo);
+    }
 }
diff --git a/harnesses/tradefed/src/com/android/tradefed/testtype/VtsMultiDeviceTestResultParser.java b/harnesses/tradefed/src/com/android/tradefed/testtype/VtsMultiDeviceTestResultParser.java
index 3a2bb3a..f018c9f 100644
--- a/harnesses/tradefed/src/com/android/tradefed/testtype/VtsMultiDeviceTestResultParser.java
+++ b/harnesses/tradefed/src/com/android/tradefed/testtype/VtsMultiDeviceTestResultParser.java
@@ -322,10 +322,16 @@
                     listener.testEnded(test.getKey(), Collections.<String, String>emptyMap());
                 } else if (test.getValue() == TIMEOUT) {
                     listener.testFailed(test.getKey(), test.getValue());
+                    // Always call testEnded at the end of the test case
+                    listener.testEnded(test.getKey(), Collections.emptyMap());
                 } else if (test.getValue() == SKIP) {
                     listener.testAssumptionFailure(test.getKey(), test.getValue());
+                    // Always call testEnded at the end of the test case
+                    listener.testEnded(test.getKey(), Collections.emptyMap());
                 } else {
                     listener.testFailed(test.getKey(), test.getValue());
+                    // Always call testEnded at the end of the test case
+                    listener.testEnded(test.getKey(), Collections.emptyMap());
                 }
             }
             listener.testRunEnded(mTotalElapsedTime, Collections.<String, String>emptyMap());
@@ -365,7 +371,7 @@
             }
             sb.append("\n");
         }
-        CLog.logAndDisplay(LogLevel.INFO, sb.toString());
+        CLog.d("JSON table: %s", sb.toString());
     }
 
     /**
@@ -414,6 +420,8 @@
                                recognized in TF, it is converted to FAIL. */
                             listener.testFailed(
                                     testIdentifier, details.isEmpty() ? UNKNOWN_ERROR : details);
+                            // Always call testEnded at the end of the test case
+                            listener.testEnded(testIdentifier, Collections.emptyMap());
                         case PASS :
                             listener.testEnded(testIdentifier, Collections.<String, String>emptyMap());
                             break;
@@ -421,6 +429,8 @@
                             /* Timeout is not recognized in TF. Use FAIL instead. */
                             listener.testFailed(
                                     testIdentifier, details.isEmpty() ? UNKNOWN_TIMEOUT : details);
+                            // Always call testEnded at the end of the test case
+                            listener.testEnded(testIdentifier, Collections.emptyMap());
                             break;
                         case SKIP :
                             /* Skip is not recognized in TF */
@@ -429,6 +439,8 @@
                             /* Indicates a test failure. */
                             listener.testFailed(
                                     testIdentifier, details.isEmpty() ? UNKNOWN_FAILURE : details);
+                            // Always call testEnded at the end of the test case
+                            listener.testEnded(testIdentifier, Collections.emptyMap());
                         default:
                             break;
                     }
diff --git a/harnesses/tradefed/src/com/android/tradefed/util/CmdUtil.java b/harnesses/tradefed/src/com/android/tradefed/util/CmdUtil.java
new file mode 100644
index 0000000..c7a2256
--- /dev/null
+++ b/harnesses/tradefed/src/com/android/tradefed/util/CmdUtil.java
@@ -0,0 +1,206 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tradefed.util;
+
+import com.android.annotations.VisibleForTesting;
+import com.android.ddmlib.CollectingOutputReceiver;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.log.LogUtil.CLog;
+
+import java.util.Vector;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Predicate;
+
+public class CmdUtil {
+    public static final int MAX_RETRY_COUNT = 10;
+    static final int DELAY_BETWEEN_RETRY_IN_SECS = 3;
+    static final long FRAMEWORK_START_TIMEOUT = 1000 * 60 * 2; // 2 minutes.
+    static final String BOOTCOMPLETE_PROP = "dev.bootcomplete";
+
+    // An interface to wait with delay. Used for testing purpose.
+    public interface ISleeper { public void sleep(int seconds) throws InterruptedException; }
+    private ISleeper mSleeper = null;
+
+    /**
+     * Helper method to retry given cmd until the expected results are satisfied.
+     * An example usage it to retry 'lshal' until the expected hal service appears.
+     *
+     * @param device testing device.
+     * @param cmd the command string to be executed on device.
+     * @param predicate function that checks the exit condition.
+     * @return true if the exit condition is satisfied, false otherwise.
+     * @throws DeviceNotAvailableException
+     */
+    public boolean waitCmdResultWithDelay(ITestDevice device, String cmd,
+            Predicate<String> predicate) throws DeviceNotAvailableException {
+        for (int count = 0; count < MAX_RETRY_COUNT; count++) {
+            if (validateCmdSuccess(device, cmd, predicate)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Helper method to retry a given set of commands, cmds.
+     *
+     * @param device testing device.
+     * @param cmds a vector of the command strings to be executed on device.
+     * @param validation_cmd the validation command string to be executed on device.
+     * @param predicate function that checks the exit condition.
+     * @return true if the exit condition is satisfied, false otherwise.
+     * @throws DeviceNotAvailableException
+     */
+    public boolean retry(ITestDevice device, Vector<String> cmds, String validation_cmd,
+            Predicate<String> predicate) throws DeviceNotAvailableException {
+        if (cmds.isEmpty()) {
+            CLog.w("retry() called but cmd is an epmty vector.");
+            return false;
+        }
+        for (int count = 0; count < MAX_RETRY_COUNT; count++) {
+            for (String cmd : cmds) {
+                CLog.d("Running a command: %s", cmd);
+                device.executeShellCommand(cmd);
+            }
+            if (validateCmdSuccess(device, validation_cmd, predicate)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Helper method to retry a given cmd.
+     *
+     * @param device testing device.
+     * @param cmd the command string to be executed on device.
+     * @param validation_cmd the validation command string to be executed on device.
+     * @param predicate function that checks the exit condition.
+     * @return true if the exit condition is satisfied, false otherwise.
+     * @throws DeviceNotAvailableException
+     */
+    public boolean retry(ITestDevice device, String cmd, String validation_cmd,
+            Predicate<String> predicate) throws DeviceNotAvailableException {
+        return retry(device, cmd, validation_cmd, predicate, MAX_RETRY_COUNT);
+    }
+
+    /**
+     * Helper method to retry a given cmd.
+     *
+     * @param device testing device.
+     * @param cmd the command string to be executed on device.
+     * @param validation_cmd the validation command string to be executed on device.
+     * @param predicate function that checks the exit condition.
+     * @param retry_count the max number of times to try
+     * @return true if the exit condition is satisfied, false otherwise.
+     * @throws DeviceNotAvailableException
+     */
+    public boolean retry(ITestDevice device, String cmd, String validation_cmd,
+            Predicate<String> predicate, int retry_count) throws DeviceNotAvailableException {
+        for (int count = 0; count < retry_count; count++) {
+            CLog.d("Running a command: %s", cmd);
+            device.executeShellCommand(cmd);
+            if (validateCmdSuccess(device, validation_cmd, predicate)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Validates the device status and waits if the validation fails.
+     *
+     * @param device testing device.
+     * @param cmd the command string to be executed on device.
+     * @param predicate function that checks the exit condition.
+     * @return true if the exit condition is satisfied, false otherwise.
+     * @throws DeviceNotAvailableException
+     */
+    protected boolean validateCmdSuccess(ITestDevice device, String cmd,
+            Predicate<String> predicate) throws DeviceNotAvailableException {
+        if (cmd == null) {
+            CLog.w("validateCmdSuccess() called but cmd is null");
+            return false;
+        }
+        String out = device.executeShellCommand(cmd);
+        CLog.d("validating cmd output: %s", out);
+        if (out != null && predicate.test(out)) {
+            CLog.d("Exit condition satisfied.");
+            return true;
+        } else {
+            CLog.d("Exit condition not satisfied. Waiting for %s more seconds.",
+                    DELAY_BETWEEN_RETRY_IN_SECS);
+            try {
+                if (mSleeper != null) {
+                    mSleeper.sleep(DELAY_BETWEEN_RETRY_IN_SECS);
+                } else {
+                    TimeUnit.SECONDS.sleep(DELAY_BETWEEN_RETRY_IN_SECS);
+                }
+            } catch (InterruptedException ex) {
+                /* pass */
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Restarts the Andriod framework and waits for the device boot completion.
+     *
+     * @param device the test device instance.
+     * @throws DeviceNotAvailableException
+     */
+    public void restartFramework(ITestDevice device) throws DeviceNotAvailableException {
+        device.executeShellCommand("stop");
+        setSystemProperty(device, BOOTCOMPLETE_PROP, "0");
+        device.executeShellCommand("start");
+        device.waitForDeviceAvailable(FRAMEWORK_START_TIMEOUT);
+    }
+
+    /**
+     * Gets a sysprop from the device.
+     *
+     * @param device the test device instance.
+     * @param name the name of a sysprop.
+     * @return the device sysprop value.
+     * @throws DeviceNotAvailableException
+     */
+    public String getSystemProperty(ITestDevice device, String name)
+            throws DeviceNotAvailableException {
+        CollectingOutputReceiver receiver = new CollectingOutputReceiver();
+        device.executeShellCommand(String.format("getprop %s", name), receiver);
+        return receiver.getOutput();
+    }
+
+    /**
+     * Sets a sysprop on the device.
+     *
+     * @param device the test device instance.
+     * @param name the name of a sysprop.
+     * @param value the value of a sysprop.
+     * @throws DeviceNotAvailableException
+     */
+    public void setSystemProperty(ITestDevice device, String name, String value)
+            throws DeviceNotAvailableException {
+        device.executeShellCommand(String.format("setprop %s %s", name, value));
+    }
+
+    @VisibleForTesting
+    void setSleeper(ISleeper sleeper) {
+        mSleeper = sleeper;
+    }
+}
diff --git a/harnesses/tradefed/src/com/android/tradefed/util/EnvUtil.java b/harnesses/tradefed/src/com/android/tradefed/util/EnvUtil.java
new file mode 100644
index 0000000..03c1b4b
--- /dev/null
+++ b/harnesses/tradefed/src/com/android/tradefed/util/EnvUtil.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tradefed.util;
+
+/**
+ * Utility class to deal with system environment
+ */
+public class EnvUtil {
+    static final String OS_NAME = "os.name";
+    static final String WINDOWS = "Windows"; // Not officially supported OS.
+
+    /**
+     * This method returns whether the OS is Windows.
+     */
+    public static boolean isOnWindows() {
+        return System.getProperty(OS_NAME).contains(WINDOWS);
+    }
+}
diff --git a/harnesses/tradefed/src/com/android/tradefed/util/JsonUtil.java b/harnesses/tradefed/src/com/android/tradefed/util/JsonUtil.java
index 5373965..77ef13f 100644
--- a/harnesses/tradefed/src/com/android/tradefed/util/JsonUtil.java
+++ b/harnesses/tradefed/src/com/android/tradefed/util/JsonUtil.java
@@ -45,7 +45,7 @@
             try {
                 target_value = target.get(key);
             } catch (JSONException e) {
-                CLog.i("Merging Json key '%s' into target object.", key);
+                CLog.d("Merging Json key '%s' into target object.", key);
                 target.put(key, source_value);
                 continue;
             }
diff --git a/harnesses/tradefed/src/com/android/tradefed/util/OutputUtil.java b/harnesses/tradefed/src/com/android/tradefed/util/OutputUtil.java
new file mode 100644
index 0000000..9d463c1
--- /dev/null
+++ b/harnesses/tradefed/src/com/android/tradefed/util/OutputUtil.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tradefed.util;
+
+import com.android.tradefed.log.ITestLogger;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.result.FileInputStreamSource;
+import com.android.tradefed.result.LogDataType;
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * Utility class to add output file to TradeFed log directory.
+ */
+public class OutputUtil {
+    // Test logger object from test invocation
+    ITestLogger mListener;
+    private String mTestModuleName = null;
+    private String mAbiName = null;
+
+    // Python output file patterns to be included in results
+    static private String[] PYTHON_OUTPUT_PATTERNS = new String[] {"test_run_details.*\\.txt",
+            "vts_agent_.*\\.log", "systrace_.*\\.html", "logcat.*\\.txt", "bugreport.*\\.zip"};
+    // Python folder pattern in which any files will be included in results
+    static private String PYTHON_OUTPUT_ADDITIONAL = ".*additional_output_files";
+
+    public OutputUtil(ITestLogger listener) {
+        mListener = listener;
+    }
+
+    /**
+     * Add a text file to log directory.
+     * @param outputFileName output file base name.
+     *                       The actual output name will contain a hash postfix.
+     * @param source text file source
+     */
+    public void addOutputFromTextFile(String outputFileName, File source) {
+        FileInputStreamSource inputSource = new FileInputStreamSource(source);
+        mListener.testLog(outputFileName, LogDataType.TEXT, inputSource);
+    }
+
+    /**
+     * Collect all VTS python runner log output files
+     * @param logDirectory
+     */
+    public void collectVtsRunnerOutputs(File logDirectory) {
+        // First, collect known patterns.
+        for (String pattern : PYTHON_OUTPUT_PATTERNS) {
+            try {
+                FileUtil.findFiles(logDirectory, pattern)
+                        .forEach(path -> addVtsRunnerOutputFile(new File(path)));
+            } catch (IOException e) {
+                CLog.e("Error reading log directory: %s", logDirectory);
+                CLog.e(e);
+            }
+        }
+
+        // Next, collect any additional files produced by tests.
+        try {
+            for (String path : FileUtil.findFiles(logDirectory, PYTHON_OUTPUT_ADDITIONAL)) {
+                for (File f : new File(path).listFiles()) {
+                    addVtsRunnerOutputFile(f);
+                }
+
+                // Only use the first result, if many were found.
+                break;
+            }
+        } catch (IOException e) {
+            CLog.e("Error reading log directory: %s", logDirectory);
+            CLog.e(e);
+        }
+    }
+
+    /**
+     *
+     * @param logFile
+     */
+    private void addVtsRunnerOutputFile(File logFile) {
+        String fileName = logFile.getName();
+
+        LogDataType type;
+        if (fileName.endsWith(".html")) {
+            type = LogDataType.HTML;
+        } else if (fileName.startsWith("logcat")) {
+            type = LogDataType.LOGCAT;
+        } else if (fileName.startsWith("bugreport") && fileName.endsWith(".zip")) {
+            type = LogDataType.BUGREPORTZ;
+        } else if (fileName.endsWith(".txt") || fileName.endsWith(".log")) {
+            type = LogDataType.TEXT;
+        } else if (fileName.endsWith(".zip")) {
+            type = LogDataType.ZIP;
+        } else {
+            CLog.w("Unknown output file type. Skipping %s", logFile);
+            return;
+        }
+
+        String outputFileName = mTestModuleName + "_" + fileName + "_" + mAbiName;
+        FileInputStreamSource inputSource = new FileInputStreamSource(logFile);
+        mListener.testLog(outputFileName, type, inputSource);
+    }
+
+    /**
+     * @param testModuleName
+     */
+    public void setTestModuleName(String testModuleName) {
+        mTestModuleName = testModuleName;
+    }
+
+    /**
+     * @param bitness
+     */
+    public void setAbiName(String abiName) {
+        mAbiName = abiName;
+    }
+}
\ No newline at end of file
diff --git a/harnesses/tradefed/src/com/android/tradefed/util/ProcessHelper.java b/harnesses/tradefed/src/com/android/tradefed/util/ProcessHelper.java
index 13ea165..5df6279 100644
--- a/harnesses/tradefed/src/com/android/tradefed/util/ProcessHelper.java
+++ b/harnesses/tradefed/src/com/android/tradefed/util/ProcessHelper.java
@@ -17,10 +17,6 @@
 package com.android.tradefed.util;
 
 import com.android.tradefed.log.LogUtil.CLog;
-import com.android.tradefed.util.CommandStatus;
-import com.android.tradefed.util.IRunUtil;
-import com.android.tradefed.util.RunInterruptedException;
-import com.android.tradefed.util.RunUtil;
 
 import java.io.IOException;
 import java.io.InputStreamReader;
@@ -65,15 +61,24 @@
         private Reader mReader;
         private StringBuilder mBuffer;
 
+        static enum LogType {
+            STDOUT,
+            STDERR;
+        }
+
+        private LogType mLogType;
+
         /**
          * @param reader the input stream to read from.
          * @param buffer the buffer containing the input data.
          * @param name the name of the thread.
+         * @param logType enum, type of log output.
          */
-        public ReaderThread(Reader reader, StringBuilder buffer, String name) {
+        public ReaderThread(Reader reader, StringBuilder buffer, String name, LogType logType) {
+            super(name);
             mReader = reader;
             mBuffer = buffer;
-            setName(name);
+            mLogType = logType;
         }
 
         /**
@@ -89,10 +94,29 @@
                     if (readCount < 0) {
                         break;
                     }
-                    mBuffer.append(charBuffer, 0, readCount);
+                    String newRead = new String(charBuffer, 0, readCount);
+
+                    int newLineLen = 0;
+                    if (newRead.endsWith("\r\n")) {
+                        newLineLen = 2;
+                    } else if (newRead.endsWith("\n")) {
+                        newLineLen = 1;
+                    }
+
+                    String newReadPrint = newRead.substring(0, newRead.length() - newLineLen);
+                    switch (mLogType) {
+                        case STDOUT:
+                            CLog.i(newReadPrint);
+                            break;
+                        case STDERR:
+                            CLog.e(newReadPrint);
+                            break;
+                    }
+                    mBuffer.append(newRead);
                 }
             } catch (IOException e) {
-                CLog.e("%s: %s", getName(), e.toString());
+                CLog.e("IOException during ProcessHelper#ReaderThread run.");
+                CLog.e(e);
             }
         }
     }
@@ -115,17 +139,17 @@
             synchronized (mLock) {
                 mExecutionThread = Thread.currentThread();
                 if (mCancelled) {
-                    CLog.i("Process was cancelled before being awaited.");
+                    CLog.w("Process was cancelled before being awaited.");
                     return false;
                 }
             }
             boolean success;
             try {
                 success = (mProcess.waitFor() == 0);
-                CLog.i("Process terminates normally.");
+                CLog.d("Process terminates normally.");
             } catch (InterruptedException e) {
                 success = false;
-                CLog.i("Process is interrupted.");
+                CLog.e("Process is interrupted.");
             }
             return success;
         }
@@ -137,17 +161,17 @@
          */
         @Override
         public void cancel() {
-            CLog.i("Attempt to interrupt execution thread.");
+            CLog.w("Attempt to interrupt execution thread.");
             synchronized (mLock) {
                 if (!mCancelled) {
                     mCancelled = true;
                     if (mExecutionThread != null) {
                         mExecutionThread.interrupt();
                     } else {
-                        CLog.i("Execution thread has not started.");
+                        CLog.d("Execution thread has not started.");
                     }
                 } else {
-                    CLog.i("Execution thread has been cancelled.");
+                    CLog.e("Execution thread has been cancelled.");
                 }
             }
         }
@@ -174,8 +198,10 @@
         mStdinWriter = new OutputStreamWriter(mProcess.getOutputStream());
         mStdoutReader = new InputStreamReader(mProcess.getInputStream());
         mStderrReader = new InputStreamReader(mProcess.getErrorStream());
-        mStdoutThread = new ReaderThread(mStdoutReader, mStdout, "process-helper-stdout");
-        mStderrThread = new ReaderThread(mStderrReader, mStderr, "process-helper-stderr");
+        mStdoutThread = new ReaderThread(
+                mStdoutReader, mStdout, "process-helper-stdout", ReaderThread.LogType.STDOUT);
+        mStderrThread = new ReaderThread(
+                mStderrReader, mStderr, "process-helper-stderr", ReaderThread.LogType.STDERR);
         mStdoutThread.start();
         mStderrThread.start();
     }
@@ -191,9 +217,10 @@
      */
     public CommandStatus waitForProcess(long timeoutMsecs) throws RunInterruptedException {
         VtsRunnable vtsRunnable = new VtsRunnable();
+        CommandStatus status;
         // Use default RunUtil because it can receive the notification of "invocation stop".
         try {
-            return RunUtil.getDefault().runTimed(timeoutMsecs, vtsRunnable, true);
+            status = RunUtil.getDefault().runTimed(timeoutMsecs, vtsRunnable, true);
         } catch (RunInterruptedException e) {
             // clear the flag set by default RunUtil.
             Thread.interrupted();
@@ -205,6 +232,14 @@
             }
             throw e;
         }
+        if (CommandStatus.SUCCESS.equals(status) || CommandStatus.FAILED.equals(status)) {
+            // Join the receiver threads otherwise output might not be available yet.
+            joinThread(mStdoutThread, THREAD_JOIN_TIMEOUT_MSECS);
+            joinThread(mStderrThread, THREAD_JOIN_TIMEOUT_MSECS);
+        } else {
+            CLog.w("Process status is %s", status);
+        }
+        return status;
     }
 
     /**
@@ -228,16 +263,18 @@
     }
 
     /**
-     * @return the stdout of the process. As the buffer is not thread safe, this method should be
-     * called after {@link #cleanUp()}.
+     * @return the stdout of the process. As the buffer is not thread safe, the caller must call
+     * {@link #cleanUp()} or {@link #waitForProcess(long)} to ensure process termination before
+     * calling this method.
      */
     public String getStdout() {
         return mStdout.toString();
     }
 
     /**
-     * @return the stderr of the process. As the buffer is not thread safe, this method should be
-     * called after {@link #cleanUp()}.
+     * @return the stderr of the process. As the buffer is not thread safe, the caller must call
+     * {@link #cleanUp()} or {@link #waitForProcess(long)} to ensure process termination before
+     * calling this method.
      */
     public String getStderr() {
         return mStderr.toString();
diff --git a/harnesses/tradefed/src/com/android/tradefed/util/VtsDashboardUtil.java b/harnesses/tradefed/src/com/android/tradefed/util/VtsDashboardUtil.java
index c6b3c79..ab02739 100644
--- a/harnesses/tradefed/src/com/android/tradefed/util/VtsDashboardUtil.java
+++ b/harnesses/tradefed/src/com/android/tradefed/util/VtsDashboardUtil.java
@@ -23,10 +23,13 @@
 import java.io.IOException;
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
+import java.util.ArrayList;
 import java.util.Base64;
 import java.util.List;
 import java.util.LinkedList;
 import java.util.NoSuchElementException;
+import java.util.regex.Pattern;
+import java.util.regex.Matcher;
 
 import com.android.tradefed.build.IBuildInfo;
 import com.android.tradefed.config.Option;
@@ -119,10 +122,16 @@
             String commandTemplate =
                     mConfigReader.GetVendorConfigVariable("dashboard_post_command");
             commandTemplate = commandTemplate.replace("{path}", messageFilePath);
+            // removes ', while keeping any substrings quoted by "".
             commandTemplate = commandTemplate.replace("'", "");
-            CLog.i(String.format("Upload command: %s", commandTemplate));
-            CommandResult c =
-                    mRunUtil.runTimedCmd(BASE_TIMEOUT_MSECS * 3, commandTemplate.split(" "));
+            CLog.d(String.format("Upload command: %s", commandTemplate));
+            List<String> commandList = new ArrayList<String>();
+            Matcher matcher = Pattern.compile("([^\"]\\S*|\".+?\")\\s*").matcher(commandTemplate);
+            while (matcher.find()) {
+                commandList.add(matcher.group(1));
+            }
+            CommandResult c = mRunUtil.runTimedCmd(BASE_TIMEOUT_MSECS * 3,
+                    (String[]) commandList.toArray(new String[commandList.size()]));
             if (c == null || c.getStatus() != CommandStatus.SUCCESS) {
                 CLog.e("Uploading the test plan execution result to GAE DB faiied.");
                 CLog.e("Stdout: %s", c.getStdout());
diff --git a/harnesses/tradefed/src/com/android/tradefed/util/VtsFileUtil.java b/harnesses/tradefed/src/com/android/tradefed/util/VtsFileUtil.java
new file mode 100644
index 0000000..0ae05b0
--- /dev/null
+++ b/harnesses/tradefed/src/com/android/tradefed/util/VtsFileUtil.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tradefed.util;
+
+import com.android.tradefed.log.LogUtil.CLog;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Paths;
+
+public class VtsFileUtil extends FileUtil {
+    /**
+     * Replacing characters in a string to make it a valid file name.
+     *
+     * The current method is to replace any non-word character with '_' except '.' and '-'.
+     *
+     * @param filename the potential name of a file to normalize.
+     *                 Do not use path here as path delimitor will be replaced
+     * @return normalized file name
+     */
+    public static String normalizeFileName(String filename) {
+        return filename.replaceAll("[^\\w.-]", "_");
+    }
+
+    /**
+     * Save a resource file to a directory.
+     *
+     * TODO(yuexima): This method is to be removed when it becomes available in FileUtil.
+     *
+     * @param resourceStream a {link InputStream} object to the resource to be saved.
+     * @param destDir a {@link File} object of a directory to where the resource file will be saved.
+     * @param targetFileName a {@link String} for the name of the file to be saved to.
+     * @return a {@link File} object of the file saved.
+     * @throws IOException if the file failed to be saved.
+     */
+    public static File saveResourceFile(
+            InputStream resourceStream, File destDir, String targetFileName) throws IOException {
+        FileWriter writer = null;
+        File file = Paths.get(destDir.getAbsolutePath(), targetFileName).toFile();
+        try {
+            writer = new FileWriter(file);
+            StreamUtil.copyStreamToWriter(resourceStream, writer);
+            return file;
+        } catch (IOException e) {
+            CLog.e("IOException while saving resource %s/%s", destDir, targetFileName);
+            deleteFile(file);
+            throw e;
+        } finally {
+            if (writer != null) {
+                writer.close();
+            }
+            if (resourceStream != null) {
+                resourceStream.close();
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/harnesses/tradefed/src/com/android/tradefed/util/VtsPythonRunnerHelper.java b/harnesses/tradefed/src/com/android/tradefed/util/VtsPythonRunnerHelper.java
new file mode 100644
index 0000000..4f05a7a
--- /dev/null
+++ b/harnesses/tradefed/src/com/android/tradefed/util/VtsPythonRunnerHelper.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tradefed.util;
+
+import com.android.compatibility.common.tradefed.build.VtsCompatibilityInvocationHelper;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.targetprep.VtsPythonVirtualenvPreparer;
+import com.android.tradefed.util.CommandResult;
+import com.android.tradefed.util.CommandStatus;
+import com.android.tradefed.util.EnvUtil;
+import com.android.tradefed.util.IRunUtil;
+import com.android.tradefed.util.ProcessHelper;
+import com.android.tradefed.util.RunInterruptedException;
+import com.android.tradefed.util.RunUtil;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+
+/**
+ * A helper class for executing VTS python scripts.
+ */
+public class VtsPythonRunnerHelper {
+    static final String PATH = "PATH";
+    static final String PYTHONHOME = "PYTHONHOME";
+    static final String VTS = "vts";
+    static final long TEST_ABORT_TIMEOUT_MSECS = 1000 * 15;
+
+    // Python virtual environment root path
+    private File mVirtualenvPath;
+    protected IRunUtil mRunUtil;
+
+    public VtsPythonRunnerHelper(IBuildInfo buildInfo) {
+        this(buildInfo.getFile(VtsPythonVirtualenvPreparer.VIRTUAL_ENV));
+    }
+
+    public VtsPythonRunnerHelper(File virtualEnvPath) {
+        mVirtualenvPath = virtualEnvPath;
+        mRunUtil = new RunUtil();
+        activateVirtualenv(mRunUtil, getPythonVirtualEnv());
+        VtsCompatibilityInvocationHelper invocationHelper = new VtsCompatibilityInvocationHelper();
+        try {
+            mRunUtil.setWorkingDir(invocationHelper.getTestsDir());
+        } catch (FileNotFoundException e) {
+            CLog.e("VtsCompatibilityInvocationHelper cannot find test case directory. "
+                    + "Command working directory not set.");
+        }
+    }
+
+    /**
+     * Create a {@link ProcessHelper} from mRunUtil.
+     *
+     * @param cmd the command to run.
+     * @throws IOException if fails to start Process.
+     */
+    protected ProcessHelper createProcessHelper(String[] cmd) throws IOException {
+        return new ProcessHelper(mRunUtil.runCmdInBackground(cmd));
+    }
+
+    /**
+     * Run VTS Python runner and handle interrupt from TradeFed.
+     *
+     * @param cmd the command to start VTS Python runner.
+     * @param commandResult the object containing the command result.
+     * @param timeout command timeout value.
+     * @return null if the command terminates or times out; a message string if the command is
+     * interrupted by TradeFed.
+     */
+    public String runPythonRunner(String[] cmd, CommandResult commandResult, long timeout) {
+        ProcessHelper process;
+        try {
+            process = createProcessHelper(cmd);
+        } catch (IOException e) {
+            CLog.e(e);
+            commandResult.setStatus(CommandStatus.EXCEPTION);
+            commandResult.setStdout("");
+            commandResult.setStderr("");
+            return null;
+        }
+
+        String interruptMessage;
+        try {
+            CommandStatus commandStatus;
+            try {
+                commandStatus = process.waitForProcess(timeout);
+                interruptMessage = null;
+            } catch (RunInterruptedException e) {
+                CLog.e("Python process is interrupted.");
+                commandStatus = CommandStatus.TIMED_OUT;
+                interruptMessage = (e.getMessage() != null ? e.getMessage() : "");
+            }
+            if (process.isRunning()) {
+                CLog.e("Cancel Python process and wait %d seconds.",
+                        TEST_ABORT_TIMEOUT_MSECS / 1000);
+                try {
+                    process.closeStdin();
+                    // Wait for the process to clean up and ignore the CommandStatus.
+                    // Propagate RunInterruptedException if this is interrupted again.
+                    process.waitForProcess(TEST_ABORT_TIMEOUT_MSECS);
+                } catch (IOException e) {
+                    CLog.e("Fail to cancel Python process.");
+                }
+            }
+            commandResult.setStatus(commandStatus);
+        } finally {
+            process.cleanUp();
+        }
+        commandResult.setStdout(process.getStdout());
+        commandResult.setStderr(process.getStderr());
+        return interruptMessage;
+    }
+
+    /**
+     * Gets python bin directory path.
+     *
+     * This method will check the directory existence.
+     *
+     * @return python bin directory; null if not exist.
+     */
+    public static String getPythonBinDir(String virtualenvPath) {
+        if (virtualenvPath == null) {
+            return null;
+        }
+        String binDirName = EnvUtil.isOnWindows() ? "Scripts" : "bin";
+        File res = new File(virtualenvPath, binDirName);
+        if (!res.exists()) {
+            return null;
+        }
+        return res.getAbsolutePath();
+    }
+
+    /**
+     * Get python virtualenv path
+     * @return virutalenv path. null if doesn't exist
+     */
+    public String getPythonVirtualEnv() {
+        if (mVirtualenvPath == null) {
+            return null;
+        }
+        return mVirtualenvPath.getAbsolutePath();
+    }
+
+    /**
+     * Activate virtualenv for a RunUtil.
+     *
+     * This method will check for python bin directory existence
+     *
+     * @param runUtil
+     * @param virtualenvPath
+     */
+    public static void activateVirtualenv(IRunUtil runUtil, String virtualenvPath) {
+        String pythonBinDir = getPythonBinDir(virtualenvPath);
+        if (pythonBinDir == null || !new File(pythonBinDir).exists()) {
+            CLog.e("Invalid python virtualenv path. Using python from system path.");
+        } else {
+            String separater = EnvUtil.isOnWindows() ? ";" : ":";
+            runUtil.setEnvVariable(PATH, pythonBinDir + separater + System.getenv().get(PATH));
+            runUtil.setEnvVariable(VtsPythonVirtualenvPreparer.VIRTUAL_ENV, virtualenvPath);
+            runUtil.unsetEnvVariable(PYTHONHOME);
+        }
+    }
+}
diff --git a/harnesses/tradefed/src/com/android/tradefed/util/VtsVendorConfigFileUtil.java b/harnesses/tradefed/src/com/android/tradefed/util/VtsVendorConfigFileUtil.java
index 788baa6..6495fd2 100644
--- a/harnesses/tradefed/src/com/android/tradefed/util/VtsVendorConfigFileUtil.java
+++ b/harnesses/tradefed/src/com/android/tradefed/util/VtsVendorConfigFileUtil.java
@@ -73,10 +73,10 @@
         if (configPath == null || configPath.length() == 0) {
             configPath = GetVendorConfigFilePath();
         }
-        CLog.i("Loading vendor test config %s", configPath);
+        CLog.d("Loading vendor test config %s", configPath);
         InputStream config = getClass().getResourceAsStream(configPath);
         if (config == null) {
-            CLog.e("Vendor test config file %s does not exist", configPath);
+            CLog.d("Vendor test config file %s does not exist", configPath);
             return false;
         }
         try {
@@ -85,7 +85,7 @@
                 CLog.e("Loaded vendor test config is empty");
                 return false;
             }
-            CLog.i("Loaded vendor test config %s", content);
+            CLog.d("Loaded vendor test config %s", content);
             vendorConfigJson = new JSONObject(content);
         } catch (IOException e) {
             throw new RuntimeException("Failed to read vendor config json file");
@@ -121,7 +121,7 @@
         if (attrs.containsKey(KEY_VENDOR_TEST_CONFIG_DEFAULT_TYPE)) {
             mDefaultType = attrs.get(KEY_VENDOR_TEST_CONFIG_DEFAULT_TYPE);
         } else {
-            CLog.i("No default vendor test configuration provided. Defaulting to prod.");
+            CLog.d("No default vendor test configuration provided. Defaulting to prod.");
         }
         mVendorConfigFilePath = attrs.get(KEY_VENDOR_TEST_CONFIG_FILE_PATH);
         return LoadVendorConfig(mDefaultType, mVendorConfigFilePath);
diff --git a/harnesses/tradefed/tests/Android.mk b/harnesses/tradefed/tests/Android.mk
index f0dc12a..41c6dc5 100644
--- a/harnesses/tradefed/tests/Android.mk
+++ b/harnesses/tradefed/tests/Android.mk
@@ -20,8 +20,9 @@
 
 LOCAL_MODULE := vts-tradefed-tests
 LOCAL_MODULE_TAGS := optional
-LOCAL_STATIC_JAVA_LIBRARIES := easymock
+LOCAL_STATIC_JAVA_LIBRARIES := easymock mockito-host
 LOCAL_JAVA_LIBRARIES := tradefed vts-tradefed
+LOCAL_JAVA_RESOURCE_DIRS := res
 
 include $(BUILD_HOST_JAVA_LIBRARY)
 
diff --git a/harnesses/tradefed/tests/res/util/results/2017.09.01_17.30.00/test_result.xml b/harnesses/tradefed/tests/res/util/results/2017.09.01_17.30.00/test_result.xml
new file mode 100644
index 0000000..31203b2
--- /dev/null
+++ b/harnesses/tradefed/tests/res/util/results/2017.09.01_17.30.00/test_result.xml
@@ -0,0 +1,16 @@
+<?xml version='1.0' encoding='UTF-8' standalone='no' ?><?xml-stylesheet type="text/xsl" href="vts_result.xsl"?>

+<Result start="1504258200493" end="1504258240603" start_display="Fri Sep 01 17:30:00 CST 2017" end_display="Fri Sep 01 17:30:40 CST 2017" suite_name="VTS" suite_version="8.0" suite_plan="vts" suite_build_number="test123456" report_version="5.0" command_line_args="vts -m SampleShellTest -a arm64-v8a" devices="TEST123456" host_name="unit.test.google.com" os_name="Linux" os_version="4.4.0-92-generic" os_arch="amd64" java_vendor="JetBrains s.r.o" java_version="1.8.0_152-android">

+  <Build build_abis_64="arm64-v8a" build_manufacturer="unknown" build_abis_32="armeabi-v7a,armeabi" build_product="aosp_arm64_ab" build_brand="Android" build_board="sailfish" build_serial="TEST123456" build_version_security_patch="2017-10-05" build_system_fingerprint="Android/aosp_arm64_ab/generic_arm64_ab:8.0.0/OC/4311089:userdebug/test-keys" build_reference_fingerprint="null" build_fingerprint="Android/aosp_sailfish/sailfish:8.0.0/OC/4311111:userdebug/test-keys" build_version_sdk="26" build_abis="arm64-v8a,armeabi-v7a,armeabi" build_device="generic_arm64_ab" build_abi="arm64-v8a" build_model="AOSP on ARM64" build_id="OC" build_abi2="null" build_vendor_fingerprint="Android/aosp_sailfish/sailfish:8.0.0/OC/4311111:userdebug/test-keys" build_version_release="8.0.0" build_version_base_os="" build_type="userdebug" build_tags="test-keys" />

+  <Summary pass="7" failed="0" modules_done="1" modules_total="1" />

+  <Module name="SampleShellTest" abi="arm64-v8a" runtime="21450" done="true" pass="7">

+    <TestCase name="SampleShellTest">

+      <Test result="pass" name="testCommandList" />

+      <Test result="pass" name="testCommandSequenceCd" />

+      <Test result="pass" name="testCommandSequenceExport" />

+      <Test result="pass" name="testCommandSequenceMktemp" />

+      <Test result="pass" name="testMultipleCommands" />

+      <Test result="pass" name="testMultipleShells" />

+      <Test result="pass" name="testOneCommand" />

+    </TestCase>

+  </Module>

+</Result>

diff --git a/harnesses/tradefed/tests/src/com/android/compatibility/common/tradefed/VtsUnitTests.java b/harnesses/tradefed/tests/src/com/android/compatibility/common/tradefed/VtsUnitTests.java
new file mode 100644
index 0000000..1c98e6f
--- /dev/null
+++ b/harnesses/tradefed/tests/src/com/android/compatibility/common/tradefed/VtsUnitTests.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.compatibility.common.tradefed;
+
+import com.android.compatibility.common.tradefed.testtype.CompatibilityTestMultiDeviceTest;
+import com.android.compatibility.common.tradefed.testtype.ModuleDefMultiDeviceTest;
+import com.android.compatibility.common.tradefed.util.VtsRetryFilterHelperTest;
+
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+import org.junit.runners.Suite.SuiteClasses;
+/**
+ * A test suite for all VTS Tradefed unit tests.
+ *
+ * <p>All tests listed here should be self-contained, and do not require any external dependencies.
+ */
+@RunWith(Suite.class)
+@SuiteClasses({
+    // NOTE: please keep classes sorted lexicographically in each group
+    // testtype
+    CompatibilityTestMultiDeviceTest.class,
+    ModuleDefMultiDeviceTest.class,
+
+    // util
+    VtsRetryFilterHelperTest.class,
+})
+public class VtsUnitTests {
+    // empty on purpose
+}
diff --git a/harnesses/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/CompatibilityTestMultiDeviceTest.java b/harnesses/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/CompatibilityTestMultiDeviceTest.java
new file mode 100644
index 0000000..0c426e5
--- /dev/null
+++ b/harnesses/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/CompatibilityTestMultiDeviceTest.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compatibility.common.tradefed.testtype;
+
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.invoker.IInvocationContext;
+import com.android.tradefed.testtype.VtsMultiDeviceTest;
+import com.android.tradefed.testtype.IAbi;
+
+import java.io.FileNotFoundException;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.ArrayList;
+import java.util.HashMap;
+import org.easymock.EasyMock;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Unit tests for {@link CompatibilityTestMultiDevice}.
+ */
+@RunWith(JUnit4.class)
+public class CompatibilityTestMultiDeviceTest {
+    CompatibilityTestMultiDevice mTest = null;
+    private ITestDevice mMockDevice;
+    private ModuleDefMultiDevice moduleDef;
+    Map<ITestDevice, IBuildInfo> deviceInfos;
+    IInvocationContext invocationContext;
+
+    @Before
+    public void setUp() throws Exception {
+        mMockDevice = EasyMock.createMock(ITestDevice.class);
+        IAbi abi = EasyMock.createMock(IAbi.class);
+        EasyMock.expect(abi.getName()).andReturn("FAKE_ABI");
+        moduleDef = new ModuleDefMultiDevice("FAKE_MODULE", abi, new VtsMultiDeviceTest(),
+                new ArrayList<>(), new ArrayList<>(), null);
+        deviceInfos = new HashMap<>();
+        invocationContext = EasyMock.createMock(IInvocationContext.class);
+
+        mTest = new CompatibilityTestMultiDevice(1, new ModuleRepoMultiDevice() {
+            @Override
+            public boolean isInitialized() {
+                return true;
+            }
+            @Override
+            public LinkedList<IModuleDef> getModules(String serial, int shardIndex) {
+                LinkedList<IModuleDef> modules = new LinkedList<>();
+                modules.add(moduleDef);
+                return modules;
+            }
+        }, 0);
+        mTest.setDevice(mMockDevice);
+        mTest.setDeviceInfos(deviceInfos);
+        mTest.setInvocationContext(invocationContext);
+    }
+
+    /**
+     * Test the initializeModuleRepo method.
+     * @throws DeviceNotAvailableException
+     * @throws FileNotFoundException
+     */
+    @Test
+    public void testInitializeModuleRepo()
+            throws FileNotFoundException, DeviceNotAvailableException {
+        mTest.initializeModuleRepo();
+        assertEquals(deviceInfos, moduleDef.getDeviceInfos());
+        assertEquals(invocationContext, moduleDef.getInvocationContext());
+    }
+}
diff --git a/harnesses/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/ModuleDefMultiDeviceTest.java b/harnesses/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/ModuleDefMultiDeviceTest.java
new file mode 100644
index 0000000..2022f64
--- /dev/null
+++ b/harnesses/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/ModuleDefMultiDeviceTest.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compatibility.common.tradefed.testtype;
+
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.invoker.IInvocationContext;
+import com.android.tradefed.targetprep.BuildError;
+import com.android.tradefed.targetprep.TargetSetupError;
+import com.android.tradefed.targetprep.multi.IMultiTargetPreparer;
+import com.android.tradefed.testtype.IAbi;
+import com.android.tradefed.testtype.VtsMultiDeviceTest;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.easymock.EasyMock;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Unit tests for {@link ModuleDefMultiDeviceTest}.
+ */
+@RunWith(JUnit4.class)
+public class ModuleDefMultiDeviceTest {
+    private ITestDevice mMockDevice;
+    private IBuildInfo mBuildInfo;
+    private ModuleDefMultiDevice mTest;
+    private Map<ITestDevice, IBuildInfo> mDeviceInfos;
+    private IInvocationContext mInvocationContext;
+    private IMultiTargetPreparer mMultiTargetPreparer;
+    private VtsMultiDeviceTest mVtsMultiDeviceTest;
+
+    @Before
+    public void setUp() throws Exception {
+        mMockDevice = EasyMock.createMock(ITestDevice.class);
+        mBuildInfo = EasyMock.createMock(IBuildInfo.class);
+        IAbi mAbi = EasyMock.createMock(IAbi.class);
+        EasyMock.expect(mAbi.getName()).andReturn("FAKE_ABI");
+        mMultiTargetPreparer = EasyMock.createMock(IMultiTargetPreparer.class);
+
+        List<IMultiTargetPreparer> preparers = new ArrayList<>();
+        preparers.add(mMultiTargetPreparer);
+        mVtsMultiDeviceTest = new VtsMultiDeviceTest();
+        mTest = new ModuleDefMultiDevice(
+                "FAKE_MODULE", mAbi, mVtsMultiDeviceTest, new ArrayList<>(), preparers, null);
+        mDeviceInfos = new HashMap<>();
+        mInvocationContext = EasyMock.createMock(IInvocationContext.class);
+        ArrayList<ITestDevice> devices = new ArrayList<>();
+        devices.add(mMockDevice);
+        ArrayList<IBuildInfo> buildInfos = new ArrayList<>();
+        buildInfos.add(mBuildInfo);
+        EasyMock.expect(mInvocationContext.getDevices()).andReturn(devices);
+        EasyMock.expect(mInvocationContext.getBuildInfos()).andReturn(buildInfos);
+        EasyMock.replay(mInvocationContext);
+
+        mTest.setDeviceInfos(mDeviceInfos);
+        mTest.setInvocationContext(mInvocationContext);
+    }
+
+    /**
+     * Test the testRunPreparerSetUp method.
+     * @throws BuildError
+     * @throws TargetSetupError
+     * @throws DeviceNotAvailableException
+     */
+    @Test
+    public void testRunPreparerSetups()
+            throws TargetSetupError, BuildError, DeviceNotAvailableException {
+        mMultiTargetPreparer.setUp(mInvocationContext);
+        EasyMock.expectLastCall();
+        EasyMock.replay(mMultiTargetPreparer);
+        mTest.runPreparerSetups();
+        EasyMock.verify(mMultiTargetPreparer);
+    }
+
+    /**
+     * Test the prepareTestClass method.
+     */
+    @Test
+    public void testPrepareTestClass() {
+        mTest.prepareTestClass();
+        assertEquals(mVtsMultiDeviceTest.getInvocationContext(), mInvocationContext);
+    }
+}
diff --git a/harnesses/tradefed/tests/src/com/android/compatibility/common/tradefed/util/VtsRetryFilterHelperTest.java b/harnesses/tradefed/tests/src/com/android/compatibility/common/tradefed/util/VtsRetryFilterHelperTest.java
new file mode 100644
index 0000000..2fc5a11
--- /dev/null
+++ b/harnesses/tradefed/tests/src/com/android/compatibility/common/tradefed/util/VtsRetryFilterHelperTest.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compatibility.common.tradefed.util;
+
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.util.VtsFileUtil;
+
+import org.easymock.EasyMock;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.File;
+import java.util.HashSet;
+
+/**
+ * Unit tests for {@link VtsRetryFilterHelper}.
+ */
+@RunWith(JUnit4.class)
+public class VtsRetryFilterHelperTest {
+    private final String RESULTS_FILE = "/util/results/2017.09.01_17.30.00/test_result.xml";
+    private final String VENDOR_FINGERPRINT =
+            "Android/aosp_sailfish/sailfish:8.0.0/OC/4311111:userdebug/test-keys";
+    private final String WRONG_FINGERPRINT =
+            "Android/other_device/other_device:8.0.0/OC/4311112:userdebug/test-keys";
+    private final String VENDOR_FINGERPRINT_PROPERTY = "ro.vendor.build.fingerprint";
+    private CompatibilityBuildHelper mBuildHelper;
+    private RetryFilterHelper mHelper;
+
+    private File mTmpDir;
+
+    @Before
+    public void setUp() throws Exception {
+        mTmpDir = VtsFileUtil.createTempDir("vts-unit-tests");
+        File invDir = new File(mTmpDir, "2017.09.01_17.30.00");
+        invDir.mkdirs();
+        VtsFileUtil.saveResourceFile(
+                getClass().getResourceAsStream(RESULTS_FILE), invDir, "test_result.xml");
+        mBuildHelper = new CompatibilityBuildHelper(null) {
+            @Override
+            public File getResultsDir() {
+                return mTmpDir;
+            }
+        };
+        mHelper = new VtsRetryFilterHelper(mBuildHelper, 0, "SUB_PLAN", new HashSet<String>(),
+                new HashSet<String>(), "ABI_NAME", "MODULE_NAME", "TEST_NAME", RetryType.FAILED);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        VtsFileUtil.recursiveDelete(mTmpDir);
+    }
+
+    /**
+     * Create a mock {@link ITestDevice} with fingerprint properties.
+     * @param vendorFingerprint The vendor fingerprint of the device.
+     * @return The mock device.
+     * @throws DeviceNotAvailableException
+     */
+    private ITestDevice createMockDevice(String vendorFingerprint)
+            throws DeviceNotAvailableException {
+        ITestDevice mockDevice = EasyMock.createMock(ITestDevice.class);
+        EasyMock.expect(mockDevice.getProperty(VENDOR_FINGERPRINT_PROPERTY))
+                .andReturn(vendorFingerprint);
+        EasyMock.replay(mockDevice);
+        return mockDevice;
+    }
+
+    /**
+     * Test ValidateBuildFingerprint without error.
+     * @throws DeviceNotAvailableException
+     */
+    @Test
+    public void testValidateBuildFingerprint() throws DeviceNotAvailableException {
+        mHelper.validateBuildFingerprint(createMockDevice(VENDOR_FINGERPRINT));
+    }
+
+    /**
+     * Test ValidateBuildFingerprint with the incorrect fingerprint.
+     * @throws DeviceNotAvailableException
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testMismatchSystemFingerprint() throws DeviceNotAvailableException {
+        mHelper.validateBuildFingerprint(createMockDevice(WRONG_FINGERPRINT));
+    }
+}
diff --git a/harnesses/tradefed/tests/src/com/android/tradefed/VtsUnitTests.java b/harnesses/tradefed/tests/src/com/android/tradefed/VtsUnitTests.java
new file mode 100644
index 0000000..977f181
--- /dev/null
+++ b/harnesses/tradefed/tests/src/com/android/tradefed/VtsUnitTests.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tradefed;
+
+import com.android.tradefed.presubmit.VtsConfigLoadingTest;
+import com.android.tradefed.targetprep.VtsCoveragePreparerTest;
+import com.android.tradefed.targetprep.VtsHalAdapterPreparerTest;
+import com.android.tradefed.targetprep.VtsPythonVirtualenvPreparerTest;
+import com.android.tradefed.targetprep.VtsTraceCollectPreparerTest;
+import com.android.tradefed.testtype.VtsFuzzTestResultParserTest;
+import com.android.tradefed.testtype.VtsFuzzTestTest;
+import com.android.tradefed.testtype.VtsMultiDeviceTestResultParserTest;
+import com.android.tradefed.testtype.VtsMultiDeviceTestTest;
+import com.android.tradefed.util.CmdUtilTest;
+import com.android.tradefed.util.ProcessHelperTest;
+import com.android.tradefed.util.VtsPythonRunnerHelperTest;
+
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+import org.junit.runners.Suite.SuiteClasses;
+
+/**
+ * A test suite for all VTS Tradefed unit tests.
+ *
+ * <p>All tests listed here should be self-contained, and do not require any external dependencies.
+ */
+@RunWith(Suite.class)
+@SuiteClasses({
+    // NOTE: please keep classes sorted lexicographically in each group
+    // presubmit
+    VtsConfigLoadingTest.class,
+    // targetprep
+    VtsCoveragePreparerTest.class,
+    VtsHalAdapterPreparerTest.class,
+    VtsPythonVirtualenvPreparerTest.class,
+    VtsTraceCollectPreparerTest.class,
+
+    // testtype
+    VtsFuzzTestResultParserTest.class,
+    VtsFuzzTestTest.class,
+    VtsMultiDeviceTestResultParserTest.class,
+    VtsMultiDeviceTestTest.class,
+
+    // util
+    CmdUtilTest.class,
+    ProcessHelperTest.class,
+    VtsPythonRunnerHelperTest.class,
+})
+public class VtsUnitTests {
+    // empty on purpose
+}
diff --git a/harnesses/tradefed/tests/src/com/android/tradefed/presubmit/VtsConfigLoadingTest.java b/harnesses/tradefed/tests/src/com/android/tradefed/presubmit/VtsConfigLoadingTest.java
new file mode 100644
index 0000000..f5b2fb3
--- /dev/null
+++ b/harnesses/tradefed/tests/src/com/android/tradefed/presubmit/VtsConfigLoadingTest.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tradefed.presubmit;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.android.tradefed.config.ConfigurationDescriptor;
+import com.android.tradefed.config.ConfigurationException;
+import com.android.tradefed.config.ConfigurationFactory;
+import com.android.tradefed.config.IConfiguration;
+import com.android.tradefed.testtype.IRemoteTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.File;
+import java.io.FilenameFilter;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Test that configuration in VTS can load and have expected properties.
+ */
+@RunWith(JUnit4.class)
+public class VtsConfigLoadingTest {
+    /**
+     * List of the officially supported runners in VTS.
+     */
+    private static final Set<String> SUPPORTED_VTS_TEST_TYPE = new HashSet<>(Arrays.asList(
+            "com.android.compatibility.common.tradefed.testtype.JarHostTest",
+            "com.android.tradefed.testtype.AndroidJUnitTest",
+            "com.android.tradefed.testtype.GTest",
+            "com.android.tradefed.testtype.VtsMultiDeviceTest"));
+
+    /**
+     * Test that configuration shipped in Tradefed can be parsed.
+     */
+    @Test
+    public void testConfigurationLoad() throws Exception {
+        String vtsRoot = System.getProperty("VTS_ROOT");
+        File testcases = new File(vtsRoot, "/android-vts/testcases/");
+        if (!testcases.exists()) {
+            fail(String.format("%s does not exists", testcases));
+            return;
+        }
+        File[] listVtsConfig = testcases.listFiles(new FilenameFilter() {
+            @Override
+            public boolean accept(File dir, String name) {
+                // Only check the VTS test config
+                if (name.startsWith("Vts") && name.endsWith(".config")) {
+                    return true;
+                }
+                return false;
+            }
+        });
+        assertTrue(listVtsConfig.length > 0);
+        for (File config : listVtsConfig) {
+            IConfiguration c = ConfigurationFactory.getInstance().createConfigurationFromArgs(
+                    new String[] {config.getAbsolutePath()});
+            for (IRemoteTest test : c.getTests()) {
+                // Check that all the tests runners are well supported.
+                if (!SUPPORTED_VTS_TEST_TYPE.contains(test.getClass().getCanonicalName())) {
+                    throw new ConfigurationException(
+                            String.format("testtype %s is not officially supported by VTS.",
+                                    test.getClass().getCanonicalName()));
+                }
+            }
+        }
+    }
+}
diff --git a/harnesses/tradefed/tests/src/com/android/tradefed/targetprep/VtsCoveragePreparerTest.java b/harnesses/tradefed/tests/src/com/android/tradefed/targetprep/VtsCoveragePreparerTest.java
new file mode 100644
index 0000000..5765b80
--- /dev/null
+++ b/harnesses/tradefed/tests/src/com/android/tradefed/targetprep/VtsCoveragePreparerTest.java
@@ -0,0 +1,225 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tradefed.targetprep;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.config.OptionSetter;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.targetprep.TargetSetupError;
+import com.android.tradefed.util.CommandResult;
+import com.android.tradefed.util.CommandStatus;
+import com.android.tradefed.util.FileUtil;
+import com.android.tradefed.util.IRunUtil;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+/**
+ * Unit tests for {@link VtsCoveragePreparer}.
+ */
+@RunWith(JUnit4.class)
+public final class VtsCoveragePreparerTest {
+    private String BUILD_INFO_ARTIFACT = VtsCoveragePreparer.BUILD_INFO_ARTIFACT;
+    private String GCOV_PROPERTY = VtsCoveragePreparer.GCOV_PROPERTY;
+    private String GCOV_FILE_NAME = VtsCoveragePreparer.GCOV_FILE_NAME;
+    private String SYMBOLS_FILE_NAME = VtsCoveragePreparer.SYMBOLS_FILE_NAME;
+    private String COVERAGE_REPORT_PATH = VtsCoveragePreparer.COVERAGE_REPORT_PATH;
+
+    // Path to store coverage report files.
+    private static final String TEST_COVERAGE_REPORT_PATH = "/tmp/test-coverage";
+
+    private File mTestDir;
+    private CompatibilityBuildHelper mMockHelper;
+
+    private class TestCoveragePreparer extends VtsCoveragePreparer {
+        @Override
+        CompatibilityBuildHelper createBuildHelper(IBuildInfo buildInfo) {
+            return mMockHelper;
+        }
+
+        @Override
+        File createTempDir(ITestDevice device) {
+            return mTestDir;
+        }
+
+        @Override
+        String getArtifactFetcher(IBuildInfo buildInfo) {
+            return "fetcher --bid %s --target %s %s %s";
+        }
+    }
+
+    @Mock private IBuildInfo mBuildInfo;
+    @Mock private ITestDevice mDevice;
+    @Mock private IRunUtil mRunUtil;
+    @InjectMocks private TestCoveragePreparer mPreparer = new TestCoveragePreparer();
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mTestDir = FileUtil.createTempDir("vts-coverage-preparer-unit-tests");
+        mMockHelper = new CompatibilityBuildHelper(mBuildInfo) {
+            @Override
+            public File getTestsDir() throws FileNotFoundException {
+                return mTestDir;
+            }
+            public File getResultDir() throws FileNotFoundException {
+                return mTestDir;
+            }
+        };
+        doReturn("build_id").when(mDevice).getBuildId();
+        doReturn("1234").when(mDevice).getSerialNumber();
+        doReturn("enforcing").when(mDevice).executeShellCommand("getenforce");
+        doReturn("build_id").when(mBuildInfo).getBuildId();
+        CommandResult commandResult = new CommandResult();
+        commandResult.setStatus(CommandStatus.SUCCESS);
+        doReturn(commandResult).when(mRunUtil).runTimedCmd(anyLong(), any());
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        FileUtil.recursiveDelete(mTestDir);
+    }
+
+    @Test
+    public void testOnSetUpCoverageDisabled() throws Exception {
+        doReturn("UnknowFlaver").when(mDevice).getBuildFlavor();
+        doReturn("None").when(mDevice).getProperty(GCOV_PROPERTY);
+
+        mPreparer.setUp(mDevice, mBuildInfo);
+        verify(mBuildInfo, never()).setFile(any(), any(), any());
+    }
+
+    @Test
+    public void testOnSetUpSancovEnabled() throws Exception {
+        doReturn("walleye_asan_coverage-userdebug").when(mDevice).getBuildFlavor();
+        createTestFile(SYMBOLS_FILE_NAME);
+        createTestFile(BUILD_INFO_ARTIFACT);
+
+        mPreparer.setUp(mDevice, mBuildInfo);
+        verify(mBuildInfo, times(1))
+                .setFile(eq(VtsCoveragePreparer.getSancovResourceDirKey(mDevice)), eq(mTestDir),
+                        eq("build_id"));
+    }
+
+    @Test
+    public void testOnSetUpGcovEnabled() throws Exception {
+        doReturn("walleye_coverage-userdebug").when(mDevice).getBuildFlavor();
+        doReturn("1").when(mDevice).getProperty(GCOV_PROPERTY);
+        createTestFile(GCOV_FILE_NAME);
+        createTestFile(BUILD_INFO_ARTIFACT);
+        mPreparer.setUp(mDevice, mBuildInfo);
+        verify(mBuildInfo, times(1))
+                .setFile(eq(VtsCoveragePreparer.getGcovResourceDirKey(mDevice)), eq(mTestDir),
+                        eq("build_id"));
+    }
+
+    @Test
+    public void testOnSetUpLocalArtifectsNormal() throws Exception {
+        OptionSetter setter = new OptionSetter(mPreparer);
+        setter.setOptionValue("use-local-artifects", "true");
+        setter.setOptionValue("local-coverage-resource-path", mTestDir.getAbsolutePath());
+        doReturn("1").when(mDevice).getProperty(GCOV_PROPERTY);
+        createTestFile(GCOV_FILE_NAME);
+        createTestFile(BUILD_INFO_ARTIFACT);
+
+        mPreparer.setUp(mDevice, mBuildInfo);
+        verify(mBuildInfo, times(1))
+                .setFile(eq(VtsCoveragePreparer.getGcovResourceDirKey(mDevice)), eq(mTestDir),
+                        eq("build_id"));
+    }
+
+    @Test
+    public void testOnSetUpLocalArtifectsNotExists() throws Exception {
+        OptionSetter setter = new OptionSetter(mPreparer);
+        setter.setOptionValue("use-local-artifects", "true");
+        setter.setOptionValue("local-coverage-resource-path", mTestDir.getAbsolutePath());
+        doReturn("1").when(mDevice).getProperty(GCOV_PROPERTY);
+
+        try {
+            mPreparer.setUp(mDevice, mBuildInfo);
+        } catch (TargetSetupError e) {
+            // Expected.
+            assertEquals(String.format("Could not find %s under %s.", GCOV_FILE_NAME,
+                                 mTestDir.getAbsolutePath()),
+                    e.getMessage());
+            verify(mBuildInfo, never()).setFile(any(), any(), any());
+            return;
+        }
+        fail();
+    }
+
+    @Test
+    public void testOnSetUpOutputCoverageReport() throws Exception {
+        OptionSetter setter = new OptionSetter(mPreparer);
+        setter.setOptionValue("coverage-report-dir", TEST_COVERAGE_REPORT_PATH);
+        doReturn("walleye_coverage-userdebug").when(mDevice).getBuildFlavor();
+        doReturn("1").when(mDevice).getProperty(GCOV_PROPERTY);
+        createTestFile(GCOV_FILE_NAME);
+        createTestFile(BUILD_INFO_ARTIFACT);
+
+        mPreparer.setUp(mDevice, mBuildInfo);
+        verify(mBuildInfo, times(1))
+                .addBuildAttribute(eq(COVERAGE_REPORT_PATH),
+                        eq(mTestDir.getAbsolutePath() + TEST_COVERAGE_REPORT_PATH));
+    }
+
+    @Test
+    public void testOnTearDown() throws Exception {
+        doReturn("walleye_coverage-userdebug").when(mDevice).getBuildFlavor();
+        doReturn("1").when(mDevice).getProperty(GCOV_PROPERTY);
+        File artifectsFile = createTestFile(GCOV_FILE_NAME);
+        File buildInfoFile = createTestFile(BUILD_INFO_ARTIFACT);
+        mPreparer.setUp(mDevice, mBuildInfo);
+        mPreparer.tearDown(mDevice, mBuildInfo, null);
+        verify(mDevice, times(1)).executeShellCommand("setenforce enforcing");
+        assertFalse(artifectsFile.exists());
+        assertFalse(buildInfoFile.exists());
+    }
+
+    /**
+     * Helper method to create a test file under mTestDir.
+     *
+     * @param fileName test file name.
+     * @return created test file.
+     */
+    private File createTestFile(String fileName) throws IOException {
+        File testFile = new File(mTestDir, fileName);
+        testFile.createNewFile();
+        return testFile;
+    }
+}
diff --git a/harnesses/tradefed/tests/src/com/android/tradefed/targetprep/VtsHalAdapterPreparerTest.java b/harnesses/tradefed/tests/src/com/android/tradefed/targetprep/VtsHalAdapterPreparerTest.java
new file mode 100644
index 0000000..d1fc88a
--- /dev/null
+++ b/harnesses/tradefed/tests/src/com/android/tradefed/targetprep/VtsHalAdapterPreparerTest.java
@@ -0,0 +1,235 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tradefed.targetprep;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.*;
+
+import com.android.compatibility.common.tradefed.build.VtsCompatibilityInvocationHelper;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.config.OptionSetter;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.testtype.IAbi;
+import com.android.tradefed.util.CmdUtil;
+import com.android.tradefed.util.FileUtil;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.InjectMocks;
+import org.mockito.Mockito;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Vector;
+import java.util.function.Predicate;
+/**
+ * Unit tests for {@link VtsHalAdapterPreparer}.
+ */
+@RunWith(JUnit4.class)
+public final class VtsHalAdapterPreparerTest {
+    private int THREAD_COUNT_DEFAULT = VtsHalAdapterPreparer.THREAD_COUNT_DEFAULT;
+    private String SCRIPT_PATH = VtsHalAdapterPreparer.SCRIPT_PATH;
+    private String LIST_HAL_CMD = VtsHalAdapterPreparer.LIST_HAL_CMD;
+
+    private String VTS_NATIVE_TEST_DIR = "DATA/nativetest64/";
+    private String TARGET_NATIVE_TEST_DIR = "/data/nativetest64/";
+    private String TEST_HAL_ADAPTER_BINARY = "android.hardware.foo@1.0-adapter";
+    private String TEST_HAL_PACKAGE = "android.hardware.foo@1.1";
+
+    private class TestCmdUtil extends CmdUtil {
+        public boolean mCmdSuccess = true;
+        @Override
+        public boolean waitCmdResultWithDelay(ITestDevice device, String cmd,
+                Predicate<String> predicate) throws DeviceNotAvailableException {
+            return mCmdSuccess;
+        }
+
+        @Override
+        public boolean retry(ITestDevice device, String cmd, String validation_cmd,
+                Predicate<String> predicate, int retry_count) throws DeviceNotAvailableException {
+            device.executeShellCommand(cmd);
+            return mCmdSuccess;
+        }
+
+        @Override
+        public boolean retry(ITestDevice device, Vector<String> cmds, String validation_cmd,
+                Predicate<String> predicate) throws DeviceNotAvailableException {
+            for (String cmd : cmds) {
+                device.executeShellCommand(cmd);
+            }
+            return mCmdSuccess;
+        }
+
+        @Override
+        public void restartFramework(ITestDevice device) throws DeviceNotAvailableException {}
+
+        @Override
+        public void setSystemProperty(ITestDevice device, String name, String value)
+                throws DeviceNotAvailableException {}
+    }
+    private TestCmdUtil mCmdUtil = new TestCmdUtil();
+    VtsCompatibilityInvocationHelper mMockHelper = null;
+    private class TestPreparer extends VtsHalAdapterPreparer {
+        @Override
+        VtsCompatibilityInvocationHelper createVtsHelper() {
+            return mMockHelper;
+        }
+    }
+    File mTestDir = null;
+
+    @Mock private IBuildInfo mBuildInfo;
+    @Mock private ITestDevice mDevice;
+    @Mock private IAbi mAbi;
+    @InjectMocks private TestPreparer mPreparer = new TestPreparer();
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        // Create the base dirs
+        mTestDir = FileUtil.createTempDir("vts-hal-adapter-preparer-unit-tests");
+        new File(mTestDir, VTS_NATIVE_TEST_DIR).mkdirs();
+        mMockHelper = new VtsCompatibilityInvocationHelper() {
+            @Override
+            public File getTestsDir() throws FileNotFoundException {
+                return mTestDir;
+            }
+        };
+        mPreparer.setCmdUtil(mCmdUtil);
+        OptionSetter setter = new OptionSetter(mPreparer);
+        setter.setOptionValue("adapter-binary-name", TEST_HAL_ADAPTER_BINARY);
+        setter.setOptionValue("hal-package-name", TEST_HAL_PACKAGE);
+        doReturn("64").when(mAbi).getBitness();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        FileUtil.recursiveDelete(mTestDir);
+    }
+
+    @Test
+    public void testOnSetUpAdapterSingleInstance() throws Exception {
+        File testAdapter = createTestAdapter();
+        String output = "android.hardware.foo@1.1::IFoo/default";
+        doReturn(output).doReturn("").when(mDevice).executeShellCommand(
+                String.format(LIST_HAL_CMD, TEST_HAL_PACKAGE));
+
+        mPreparer.setUp(mDevice, mBuildInfo);
+        verify(mDevice, times(1))
+                .pushFile(eq(testAdapter), eq(TARGET_NATIVE_TEST_DIR + TEST_HAL_ADAPTER_BINARY));
+        String adapterCmd = String.format("%s /data/nativetest64/%s %s %s %d", SCRIPT_PATH,
+                TEST_HAL_ADAPTER_BINARY, "IFoo", "default", THREAD_COUNT_DEFAULT);
+        verify(mDevice, times(1)).executeShellCommand(eq(adapterCmd));
+    }
+
+    @Test
+    public void testOnSetUpAdapterMultipleInstance() throws Exception {
+        File testAdapter = createTestAdapter();
+        String output = "android.hardware.foo@1.1::IFoo/default\n"
+                + "android.hardware.foo@1.1::IFoo/test\n"
+                + "android.hardware.foo@1.1::IFooSecond/default\n"
+                + "android.hardware.foo@1.1::IFooSecond/slot/1\n";
+        doReturn(output).doReturn("").when(mDevice).executeShellCommand(
+                String.format(LIST_HAL_CMD, TEST_HAL_PACKAGE));
+
+        mPreparer.setUp(mDevice, mBuildInfo);
+
+        List<String> adapterCmds = new ArrayList<String>();
+        adapterCmds.add(String.format("%s /data/nativetest64/%s %s %s %d", SCRIPT_PATH,
+                TEST_HAL_ADAPTER_BINARY, "IFoo", "default", THREAD_COUNT_DEFAULT));
+        adapterCmds.add(String.format("%s /data/nativetest64/%s %s %s %d", SCRIPT_PATH,
+                TEST_HAL_ADAPTER_BINARY, "IFoo", "test", THREAD_COUNT_DEFAULT));
+        adapterCmds.add(String.format("%s /data/nativetest64/%s %s %s %d", SCRIPT_PATH,
+                TEST_HAL_ADAPTER_BINARY, "IFooSecond", "default", THREAD_COUNT_DEFAULT));
+        adapterCmds.add(String.format("%s /data/nativetest64/%s %s %s %d", SCRIPT_PATH,
+                TEST_HAL_ADAPTER_BINARY, "IFooSecond", "slot/1", THREAD_COUNT_DEFAULT));
+
+        for (String cmd : adapterCmds) {
+            verify(mDevice, times(1)).executeShellCommand(eq(cmd));
+        }
+    }
+
+    @Test
+    public void testOnSetupAdapterNotFound() throws Exception {
+        try {
+            mPreparer.setUp(mDevice, mBuildInfo);
+        } catch (TargetSetupError e) {
+            assertEquals("Could not push adapter.", e.getMessage());
+            return;
+        }
+        fail();
+    }
+
+    @Test
+    public void testOnSetupServiceNotAvailable() throws Exception {
+        File testAdapter = createTestAdapter();
+        doReturn("").when(mDevice).executeShellCommand(
+                String.format(LIST_HAL_CMD, TEST_HAL_PACKAGE));
+        mPreparer.setUp(mDevice, mBuildInfo);
+    }
+
+    @Test
+    public void testOnSetUpAdapterFailed() throws Exception {
+        File testAdapter = createTestAdapter();
+        String output = "android.hardware.foo@1.1::IFoo/default";
+        doReturn(output).when(mDevice).executeShellCommand(
+                String.format(LIST_HAL_CMD, TEST_HAL_PACKAGE));
+        mCmdUtil.mCmdSuccess = false;
+        try {
+            mPreparer.setUp(mDevice, mBuildInfo);
+        } catch (TargetSetupError e) {
+            assertEquals("HAL adapter setup failed.", e.getMessage());
+            return;
+        }
+        fail();
+    }
+
+    @Test
+    public void testOnTearDownRestoreFailed() throws Exception {
+        mCmdUtil.mCmdSuccess = false;
+        try {
+            mPreparer.setCmdUtil(mCmdUtil);
+            mPreparer.addCommand("one");
+            mPreparer.tearDown(mDevice, mBuildInfo, null);
+        } catch (AssertionError | DeviceNotAvailableException e) {
+            assertEquals("HAL restore failed.", e.getMessage());
+            return;
+        }
+        fail();
+    }
+
+    /**
+     * Helper method to create a test adapter under mTestDir.
+     *
+     * @return created test file.
+     */
+    private File createTestAdapter() throws IOException {
+        File testAdapter = new File(mTestDir, VTS_NATIVE_TEST_DIR + TEST_HAL_ADAPTER_BINARY);
+        testAdapter.createNewFile();
+        return testAdapter;
+    }
+}
diff --git a/harnesses/tradefed/tests/src/com/android/tradefed/targetprep/VtsPythonVirtualenvPreparerTest.java b/harnesses/tradefed/tests/src/com/android/tradefed/targetprep/VtsPythonVirtualenvPreparerTest.java
index 7d0f46a..319296a 100644
--- a/harnesses/tradefed/tests/src/com/android/tradefed/targetprep/VtsPythonVirtualenvPreparerTest.java
+++ b/harnesses/tradefed/tests/src/com/android/tradefed/targetprep/VtsPythonVirtualenvPreparerTest.java
@@ -17,87 +17,151 @@
 package com.android.tradefed.targetprep;
 
 import static org.easymock.EasyMock.anyLong;
-import static org.easymock.EasyMock.anyObject;
-import static org.easymock.EasyMock.createNiceMock;
 import static org.easymock.EasyMock.expect;
-import static org.easymock.EasyMock.replay;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 import com.android.tradefed.build.BuildInfo;
 import com.android.tradefed.build.IBuildInfo;
 import com.android.tradefed.util.CommandResult;
 import com.android.tradefed.util.CommandStatus;
+import com.android.tradefed.util.FileUtil;
 import com.android.tradefed.util.IRunUtil;
-
-import junit.framework.TestCase;
+import org.easymock.EasyMock;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
 
 import java.io.File;
 
-public class VtsPythonVirtualenvPreparerTest extends TestCase {
+/**
+ * Unit tests for {@link VtsPythonVirtualenvPreparer}.</p>
+ * TODO: add tests to cover a full end-to-end scenario.
+ */
+@RunWith(JUnit4.class)
+public class VtsPythonVirtualenvPreparerTest {
     private VtsPythonVirtualenvPreparer mPreparer;
     private IRunUtil mMockRunUtil;
 
-    @Override
+    @Before
     public void setUp() throws Exception {
-        mMockRunUtil = createNiceMock(IRunUtil.class);
-        mPreparer = new VtsPythonVirtualenvPreparer();
-        mPreparer.mRunUtil = mMockRunUtil;
+        mMockRunUtil = EasyMock.createMock(IRunUtil.class);
+        mPreparer = new VtsPythonVirtualenvPreparer() {
+            @Override
+            IRunUtil getRunUtil() {
+                return mMockRunUtil;
+            }
+        };
+        mPreparer.mVenvDir = new File("");
+        mPreparer.mDepModules.add("enum");
     }
 
+    /**
+     * Test that the installation of dependencies and requirements file is as expected.
+     */
+    @Test
     public void testInstallDeps_reqFile_success() throws Exception {
-        mPreparer.setRequirementsFile(new File("foobar"));
-        expect(mMockRunUtil.runTimedCmd(anyLong(),
-                (String)anyObject(), (String)anyObject(), (String)anyObject(), (String)anyObject()))
-            .andReturn(new CommandResult(CommandStatus.SUCCESS));
-        replay(mMockRunUtil);
-        IBuildInfo buildInfo = new BuildInfo();
-        mPreparer.installDeps(buildInfo);
-        assertTrue(buildInfo.getFile("PYTHONPATH") != null);
+        File requirementFile = FileUtil.createTempFile("reqfile", ".txt");
+        try {
+            mPreparer.setRequirementsFile(requirementFile);
+            CommandResult result = new CommandResult(CommandStatus.SUCCESS);
+            result.setStdout("output");
+            result.setStderr("std err");
+            // First check that the install requirements was attempted.
+            expect(mMockRunUtil.runTimedCmd(anyLong(), EasyMock.eq(mPreparer.getPipPath()),
+                           EasyMock.eq("install"), EasyMock.eq("-r"),
+                           EasyMock.eq(requirementFile.getAbsolutePath())))
+                    .andReturn(result);
+            // Check that all default modules are installed
+            addDefaultModuleExpectations(mMockRunUtil, result);
+            EasyMock.replay(mMockRunUtil);
+            mPreparer.installDeps();
+            EasyMock.verify(mMockRunUtil);
+        } finally {
+            FileUtil.deleteFile(requirementFile);
+        }
     }
 
+    /**
+     * Test that if an extra dependency module is required, we install it too.
+     */
+    @Test
     public void testInstallDeps_depModule_success() throws Exception {
         mPreparer.addDepModule("blahblah");
-        expect(mMockRunUtil.runTimedCmd(anyLong(),
-                (String)anyObject(), (String)anyObject(), (String)anyObject())).andReturn(
-                new CommandResult(CommandStatus.SUCCESS));
-        replay(mMockRunUtil);
-        IBuildInfo buildInfo = new BuildInfo();
-        mPreparer.installDeps(buildInfo);
-        assertTrue(buildInfo.getFile("PYTHONPATH") != null);
+        CommandResult result = new CommandResult(CommandStatus.SUCCESS);
+        result.setStdout("output");
+        result.setStderr("std err");
+        addDefaultModuleExpectations(mMockRunUtil, result);
+        // The non default module provided is also attempted to be installed.
+        expect(mMockRunUtil.runTimedCmd(anyLong(), EasyMock.eq(mPreparer.getPipPath()),
+                       EasyMock.eq("install"), EasyMock.eq("blahblah")))
+                .andReturn(result);
+
+        EasyMock.replay(mMockRunUtil);
+        mPreparer.installDeps();
+        EasyMock.verify(mMockRunUtil);
     }
 
+    /**
+     * Test that an installation failure of the requirements file throws a {@link TargetSetupError}.
+     */
+    @Test
     public void testInstallDeps_reqFile_failure() throws Exception {
-        mPreparer.setRequirementsFile(new File("foobar"));
-        expect(mMockRunUtil.runTimedCmd(anyLong(),
-                (String)anyObject(), (String)anyObject(), (String)anyObject(), (String)anyObject()))
-            .andReturn(new CommandResult(CommandStatus.TIMED_OUT));
-        replay(mMockRunUtil);
-        IBuildInfo buildInfo = new BuildInfo();
+        File requirementFile = FileUtil.createTempFile("reqfile", ".txt");
         try {
-            mPreparer.installDeps(buildInfo);
-            fail("installDeps succeeded despite a failed command");
-        } catch (TargetSetupError e) {
-            assertTrue(buildInfo.getFile("PYTHONPATH") == null);
+            mPreparer.setRequirementsFile(requirementFile);
+            CommandResult result = new CommandResult(CommandStatus.TIMED_OUT);
+            result.setStdout("output");
+            result.setStderr("std err");
+            expect(mMockRunUtil.runTimedCmd(anyLong(), EasyMock.eq(mPreparer.getPipPath()),
+                           EasyMock.eq("install"), EasyMock.eq("-r"),
+                           EasyMock.eq(requirementFile.getAbsolutePath())))
+                    .andReturn(result);
+            EasyMock.replay(mMockRunUtil);
+            IBuildInfo buildInfo = new BuildInfo();
+            try {
+                mPreparer.installDeps();
+                fail("installDeps succeeded despite a failed command");
+            } catch (TargetSetupError e) {
+                assertTrue(buildInfo.getFile("PYTHONPATH") == null);
+            }
+            EasyMock.verify(mMockRunUtil);
+        } finally {
+            FileUtil.deleteFile(requirementFile);
         }
     }
 
+    /**
+     * Test that an installation failure of the dep module throws a {@link TargetSetupError}.
+     */
+    @Test
     public void testInstallDeps_depModule_failure() throws Exception {
-        mPreparer.addDepModule("blahblah");
-        expect(mMockRunUtil.runTimedCmd(anyLong(),
-                (String)anyObject(), (String)anyObject(), (String)anyObject())).andReturn(
-                new CommandResult(CommandStatus.TIMED_OUT));
-        replay(mMockRunUtil);
+        CommandResult result = new CommandResult(CommandStatus.TIMED_OUT);
+        result.setStdout("output");
+        result.setStderr("std err");
+        expect(mMockRunUtil.runTimedCmd(anyLong(), EasyMock.eq(mPreparer.getPipPath()),
+                       EasyMock.eq("install"), EasyMock.eq("enum")))
+                .andReturn(result);
+        // If installing the dependency failed, an upgrade is attempted:
+        expect(mMockRunUtil.runTimedCmd(anyLong(), EasyMock.eq(mPreparer.getPipPath()),
+                       EasyMock.eq("install"), EasyMock.eq("--upgrade"), EasyMock.eq("enum")))
+                .andReturn(result);
+        EasyMock.replay(mMockRunUtil);
         IBuildInfo buildInfo = new BuildInfo();
         try {
-            mPreparer.installDeps(buildInfo);
+            mPreparer.installDeps();
+            mPreparer.addPathToBuild(buildInfo);
             fail("installDeps succeeded despite a failed command");
         } catch (TargetSetupError e) {
             assertTrue(buildInfo.getFile("PYTHONPATH") == null);
         }
+        EasyMock.verify(mMockRunUtil);
     }
 
-    public void testInstallDeps_noDeps() throws Exception {
-        BuildInfo buildInfo = new BuildInfo();
-        mPreparer.installDeps(buildInfo);
-        assertTrue(buildInfo.getFile("PYTHONPATH") == null);
+    private void addDefaultModuleExpectations(IRunUtil mockRunUtil, CommandResult result) {
+        expect(mockRunUtil.runTimedCmd(anyLong(), EasyMock.eq(mPreparer.getPipPath()),
+                       EasyMock.eq("install"), EasyMock.eq("enum")))
+                .andReturn(result);
     }
 }
\ No newline at end of file
diff --git a/harnesses/tradefed/tests/src/com/android/tradefed/targetprep/VtsTraceCollectPreparerTest.java b/harnesses/tradefed/tests/src/com/android/tradefed/targetprep/VtsTraceCollectPreparerTest.java
new file mode 100644
index 0000000..d745760
--- /dev/null
+++ b/harnesses/tradefed/tests/src/com/android/tradefed/targetprep/VtsTraceCollectPreparerTest.java
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tradefed.targetprep;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.config.OptionSetter;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.util.FileUtil;
+import com.android.tradefed.targetprep.TargetSetupError;
+
+import junit.framework.AssertionFailedError;
+
+import org.easymock.EasyMock;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.util.NoSuchElementException;
+
+/**
+ * Unit tests for {@link VtsTraceCollectPreparer}.
+ */
+@RunWith(JUnit4.class)
+public final class VtsTraceCollectPreparerTest {
+    private String SELINUX_PERMISSIVE = VtsTraceCollectPreparer.SELINUX_PERMISSIVE;
+    private String VTS_LIB_DIR_32 = VtsTraceCollectPreparer.VTS_LIB_DIR_32;
+    private String VTS_LIB_DIR_64 = VtsTraceCollectPreparer.VTS_LIB_DIR_64;
+    private String VTS_BINARY_DIR = VtsTraceCollectPreparer.VTS_BINARY_DIR;
+    private String VTS_TMP_LIB_DIR_32 = VtsTraceCollectPreparer.VTS_TMP_LIB_DIR_32;
+    private String VTS_TMP_LIB_DIR_64 = VtsTraceCollectPreparer.VTS_TMP_LIB_DIR_64;
+    private String VTS_TMP_DIR = VtsTraceCollectPreparer.VTS_TMP_DIR;
+    private String PROFILING_CONFIGURE_BINARY = VtsTraceCollectPreparer.PROFILING_CONFIGURE_BINARY;
+    private String TRACE_PATH = VtsTraceCollectPreparer.TRACE_PATH;
+    private String LOCAL_TRACE_DIR = VtsTraceCollectPreparer.LOCAL_TRACE_DIR;
+
+    private static final String TEST_VTS_PROFILER = "test-vts.profiler.so";
+    private static final String TEST_VTS_LIB = "libvts-for-test.so";
+    private static final String UNRELATED_LIB = "somelib.so";
+
+    private VtsTraceCollectPreparer mPreparer;
+    private IBuildInfo mMockBuildInfo;
+    private ITestDevice mMockDevice;
+    private CompatibilityBuildHelper mMockHelper;
+    private File mTestDir;
+
+    @Before
+    public void setUp() throws Exception {
+        mMockDevice = EasyMock.createNiceMock(ITestDevice.class);
+        mMockBuildInfo = EasyMock.createNiceMock(IBuildInfo.class);
+        mTestDir = FileUtil.createTempDir("vts-trace-collect-unit-tests");
+        mMockHelper = new CompatibilityBuildHelper(mMockBuildInfo) {
+            @Override
+            public File getTestsDir() throws FileNotFoundException {
+                return mTestDir;
+            }
+            public File getResultDir() throws FileNotFoundException {
+                return mTestDir;
+            }
+        };
+        mPreparer = new VtsTraceCollectPreparer() {
+            @Override
+            CompatibilityBuildHelper createBuildHelper(IBuildInfo buildInfo) {
+                return mMockHelper;
+            }
+        };
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        // Cleanup test files.
+        FileUtil.recursiveDelete(mTestDir);
+    }
+
+    @Test
+    public void testOnSetUp() throws Exception {
+        // Create the base dirs
+        new File(mTestDir, VTS_LIB_DIR_32).mkdirs();
+        new File(mTestDir, VTS_LIB_DIR_64).mkdirs();
+        // Files that should be pushed.
+        File testProfilerlib32 = createTestFile(TEST_VTS_PROFILER, "32");
+        File testProfilerlib64 = createTestFile(TEST_VTS_PROFILER, "64");
+        File testVtslib32 = createTestFile(TEST_VTS_LIB, "32");
+        File testVtslib64 = createTestFile(TEST_VTS_LIB, "64");
+        // Files that should not be pushed.
+        File testUnrelatedlib32 = createTestFile(UNRELATED_LIB, "32");
+        File testUnrelatedlib64 = createTestFile(UNRELATED_LIB, "64");
+
+        EasyMock.expect(mMockDevice.pushFile(EasyMock.eq(testProfilerlib32),
+                                EasyMock.eq(VTS_TMP_LIB_DIR_32 + TEST_VTS_PROFILER)))
+                .andReturn(true)
+                .times(1);
+        EasyMock.expect(mMockDevice.pushFile(EasyMock.eq(testProfilerlib64),
+                                EasyMock.eq(VTS_TMP_LIB_DIR_64 + TEST_VTS_PROFILER)))
+                .andReturn(true)
+                .times(1);
+        EasyMock.expect(mMockDevice.pushFile(EasyMock.eq(testVtslib32),
+                                EasyMock.eq(VTS_TMP_LIB_DIR_32 + TEST_VTS_LIB)))
+                .andReturn(true)
+                .times(1);
+        EasyMock.expect(mMockDevice.pushFile(EasyMock.eq(testVtslib64),
+                                EasyMock.eq(VTS_TMP_LIB_DIR_64 + TEST_VTS_LIB)))
+                .andReturn(true)
+                .times(1);
+        EasyMock.expect(mMockDevice.pushFile(EasyMock.eq(testUnrelatedlib32),
+                                EasyMock.eq(VTS_TMP_LIB_DIR_32 + UNRELATED_LIB)))
+                .andThrow(new AssertionFailedError())
+                .anyTimes();
+        EasyMock.expect(mMockDevice.pushFile(EasyMock.eq(testUnrelatedlib64),
+                                EasyMock.eq(VTS_TMP_LIB_DIR_64 + UNRELATED_LIB)))
+                .andThrow(new AssertionFailedError())
+                .anyTimes();
+        EasyMock.expect(mMockDevice.pushFile(EasyMock.eq(new File(mTestDir,
+                                                     VTS_BINARY_DIR + PROFILING_CONFIGURE_BINARY)),
+                                EasyMock.eq(VTS_TMP_DIR + PROFILING_CONFIGURE_BINARY)))
+                .andReturn(true)
+                .times(1);
+        EasyMock.expect(mMockDevice.executeShellCommand(EasyMock.eq("getenforce")))
+                .andReturn("")
+                .times(1);
+        EasyMock.expect(mMockDevice.executeShellCommand(
+                                EasyMock.eq("setenforce " + SELINUX_PERMISSIVE)))
+                .andReturn("")
+                .times(1);
+
+        // Configure the trace directory path.
+        File traceDir = new File(mTestDir, LOCAL_TRACE_DIR);
+        mMockBuildInfo.addBuildAttribute(TRACE_PATH, traceDir.getAbsolutePath());
+        EasyMock.expectLastCall().times(1);
+        EasyMock.replay(mMockDevice, mMockBuildInfo);
+
+        // Run setUp.
+        mPreparer.setUp(mMockDevice, mMockBuildInfo);
+        EasyMock.verify(mMockDevice, mMockBuildInfo);
+    }
+
+    @Test
+    public void testOnSetUpPushFileException() throws Exception {
+        EasyMock.expect(mMockDevice.pushFile(EasyMock.anyObject(), EasyMock.anyObject()))
+                .andThrow(new NoSuchElementException("file not found."));
+        EasyMock.replay(mMockDevice);
+        try {
+            mPreparer.setUp(mMockDevice, mMockBuildInfo);
+            EasyMock.verify(mMockDevice);
+        } catch (TargetSetupError e) {
+            // Expected.
+            assertEquals("Could not push profiler.", e.getMessage());
+            return;
+        }
+        fail();
+    }
+
+    @Test
+    public void testOnTearDown() throws Exception {
+        EasyMock.expect(mMockDevice.executeShellCommand(EasyMock.eq("getenforce")))
+                .andReturn(SELINUX_PERMISSIVE);
+        EasyMock.expect(mMockDevice.executeShellCommand(
+                                EasyMock.eq("setenforce " + SELINUX_PERMISSIVE)))
+                .andReturn("")
+                .times(1);
+        EasyMock.replay(mMockDevice, mMockBuildInfo);
+        mPreparer.setUp(mMockDevice, mMockBuildInfo);
+        mPreparer.tearDown(mMockDevice, mMockBuildInfo, null);
+        EasyMock.verify(mMockDevice, mMockBuildInfo);
+    }
+
+    /**
+     * Helper method to create a test file under mTestDir.
+     *
+     * @param fileName test file name.
+     * @param bitness bitness for the test file. Only supports "32" or "64".
+     * @return created test file, null for unsupported bitness.
+     */
+    private File createTestFile(String fileName, String bitness) throws Exception {
+        if (bitness == "32") {
+            File testFile32 = new File(mTestDir, VTS_LIB_DIR_32 + fileName);
+            testFile32.createNewFile();
+            return testFile32;
+        } else if (bitness == "64") {
+            File testFile64 = new File(mTestDir, VTS_LIB_DIR_64 + fileName);
+            testFile64.createNewFile();
+            return testFile64;
+        }
+        return null;
+    }
+}
diff --git a/harnesses/tradefed/tests/src/com/android/tradefed/testtype/VtsMultiDeviceTestResultParserTest.java b/harnesses/tradefed/tests/src/com/android/tradefed/testtype/VtsMultiDeviceTestResultParserTest.java
index 4973200..a7bb40c 100644
--- a/harnesses/tradefed/tests/src/com/android/tradefed/testtype/VtsMultiDeviceTestResultParserTest.java
+++ b/harnesses/tradefed/tests/src/com/android/tradefed/testtype/VtsMultiDeviceTestResultParserTest.java
@@ -17,9 +17,13 @@
 
 import com.android.ddmlib.testrunner.ITestRunListener;
 import com.android.ddmlib.testrunner.TestIdentifier;
+import com.android.tradefed.util.FileUtil;
+import com.android.tradefed.util.VtsFileUtil;
 
-import junit.framework.TestCase;
 import org.easymock.EasyMock;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
 
 import java.io.BufferedReader;
 import java.io.File;
@@ -33,203 +37,173 @@
 /**
  * Unit tests for {@link VtsMultiDeviceTestResultParser}.
  */
-public class VtsMultiDeviceTestResultParserTest extends TestCase {
-
-    // for file path
-    private static final String TEST_DIR = "tests";
-    private static final String OUTPUT_FILE_1 = "vts_multi_device_test_parser_output.txt";
-    private static final String OUTPUT_FILE_2 = "vts_multi_device_test_parser_output_timeout.txt";
-    private static final String OUTPUT_FILE_3 = "vts_multi_device_test_parser_output_error.txt";
-    private static final String USER_DIR = "user.dir";
-    private static final String RES = "res";
-    private static final String TEST_TYPE = "testtype";
-
-    //test results
-    static final String PASS = "PASS";
-    static final String FAIL = "FAIL";
-    static final String TIME_OUT = "TIMEOUT";
+@RunWith(JUnit4.class)
+public class VtsMultiDeviceTestResultParserTest {
+    // Test file paths in the resources
+    private static final String OUTPUT_FILE_1 = "/testtype/vts_multi_device_test_parser_output.txt";
+    private static final String OUTPUT_FILE_2 =
+            "/testtype/vts_multi_device_test_parser_output_timeout.txt";
+    private static final String OUTPUT_FILE_3 =
+            "/testtype/vts_multi_device_test_parser_output_error.txt";
 
     // test name
     private static final String RUN_NAME = "SampleLightFuzzTest";
     private static final String TEST_NAME_1 = "testTurnOnLightBlackBoxFuzzing";
     private static final String TEST_NAME_2 = "testTurnOnLightWhiteBoxFuzzing";
 
-    // enumeration to indicate the input file used for each run.
-    private TestCase mTestCase = TestCase.NORMAL;
-    private enum TestCase {
-        NORMAL, ERROR, TIMEOUT;
-    }
-
     /**
      * Test the run method with a normal input.
-     * @throws IOException
      */
-    public void testRunTimeoutInput() throws IOException{
-        mTestCase = TestCase.TIMEOUT;
-        String[] contents =  getOutput();
+    @Test
+    public void testRunTimeoutInput() throws IOException {
+        String[] contents = getOutput(OUTPUT_FILE_2);
         long totalTime = getTotalTime(contents);
 
         // prepare the mock object
         ITestRunListener mockRunListener = EasyMock.createMock(ITestRunListener.class);
-        mockRunListener.testRunStarted(TEST_NAME_1, 2);
         mockRunListener.testRunStarted(TEST_NAME_2, 2);
-        mockRunListener.testStarted(new TestIdentifier(RUN_NAME, TEST_NAME_1));
-        mockRunListener.testStarted(new TestIdentifier(RUN_NAME, TEST_NAME_2));
-        mockRunListener.testEnded(new TestIdentifier(RUN_NAME, TEST_NAME_1),
-                Collections.<String, String>emptyMap());
-        mockRunListener.testFailed(new TestIdentifier(RUN_NAME, TEST_NAME_2), TIME_OUT);
+        TestIdentifier test1 = new TestIdentifier(RUN_NAME, TEST_NAME_2);
+        mockRunListener.testStarted(test1);
+        mockRunListener.testFailed(test1, "TIMEOUT");
+        mockRunListener.testEnded(test1, Collections.emptyMap());
+
+        TestIdentifier test2 = new TestIdentifier(RUN_NAME, TEST_NAME_1);
+        mockRunListener.testStarted(test2);
+        mockRunListener.testEnded(test2, Collections.emptyMap());
+        mockRunListener.testRunEnded(totalTime, Collections.emptyMap());
+
+        EasyMock.replay(mockRunListener);
+        VtsMultiDeviceTestResultParser resultParser =
+                new VtsMultiDeviceTestResultParser(mockRunListener, RUN_NAME);
+        resultParser.processNewLines(contents);
+        resultParser.done();
+        EasyMock.verify(mockRunListener);
+    }
+
+    /**
+     * Test the run method with a normal input.
+     */
+    @Test
+    public void testRunNormalInput() throws IOException {
+        String[] contents = getOutput(OUTPUT_FILE_1);
+        long totalTime = getTotalTime(contents);
+
+        // prepare the mock object
+        ITestRunListener mockRunListener = EasyMock.createMock(ITestRunListener.class);
+        mockRunListener.testRunStarted(TEST_NAME_2, 2);
+        TestIdentifier test1 = new TestIdentifier(RUN_NAME, TEST_NAME_2);
+        mockRunListener.testStarted(test1);
+        mockRunListener.testEnded(test1, Collections.emptyMap());
+
+        TestIdentifier test2 = new TestIdentifier(RUN_NAME, TEST_NAME_1);
+        mockRunListener.testStarted(test2);
+        mockRunListener.testEnded(test2, Collections.emptyMap());
+        mockRunListener.testRunEnded(totalTime, Collections.emptyMap());
+
+        EasyMock.replay(mockRunListener);
+        VtsMultiDeviceTestResultParser resultParser =
+                new VtsMultiDeviceTestResultParser(mockRunListener, RUN_NAME);
+        resultParser.processNewLines(contents);
+        resultParser.done();
+        EasyMock.verify(mockRunListener);
+    }
+
+    /**
+     * Test the run method with a erroneous input.
+     */
+    @Test
+    public void testRunErrorInput() throws IOException {
+        String[] contents = getOutput(OUTPUT_FILE_3);
+        long totalTime = getTotalTime(contents);
+
+        // prepare the mock object
+        ITestRunListener mockRunListener = EasyMock.createMock(ITestRunListener.class);
+        mockRunListener.testRunStarted(null, 0);
         mockRunListener.testRunEnded(totalTime, Collections.<String, String>emptyMap());
 
         EasyMock.replay(mockRunListener);
-        VtsMultiDeviceTestResultParser resultParser = new VtsMultiDeviceTestResultParser(
-            mockRunListener, RUN_NAME);
+        VtsMultiDeviceTestResultParser resultParser =
+                new VtsMultiDeviceTestResultParser(mockRunListener, RUN_NAME);
         resultParser.processNewLines(contents);
         resultParser.done();
+        EasyMock.verify(mockRunListener);
     }
-
-     /**
-      * Test the run method with a normal input.
-      * @throws IOException
-      */
-     public void testRunNormalInput() throws IOException{
-         mTestCase = TestCase.NORMAL;
-         String[] contents =  getOutput();
-         long totalTime = getTotalTime(contents);
-
-         // prepare the mock object
-         ITestRunListener mockRunListener = EasyMock.createMock(ITestRunListener.class);
-         mockRunListener.testRunStarted(TEST_NAME_1, 2);
-         mockRunListener.testRunStarted(TEST_NAME_2, 2);
-         mockRunListener.testStarted(new TestIdentifier(RUN_NAME, TEST_NAME_1));
-         mockRunListener.testStarted(new TestIdentifier(RUN_NAME, TEST_NAME_2));
-         mockRunListener.testEnded(new TestIdentifier(RUN_NAME, TEST_NAME_1),
-                 Collections.<String, String>emptyMap());
-         mockRunListener.testEnded(new TestIdentifier(RUN_NAME, TEST_NAME_2),
-                 Collections.<String, String>emptyMap());
-         mockRunListener.testRunEnded(totalTime, Collections.<String, String>emptyMap());
-
-         EasyMock.replay(mockRunListener);
-         VtsMultiDeviceTestResultParser resultParser = new VtsMultiDeviceTestResultParser(
-                 mockRunListener, RUN_NAME);
-         resultParser.processNewLines(contents);
-         resultParser.done();
-     }
-
-     /**
-      * Test the run method with a erroneous input.
-      * @throws IOException
-      */
-     public void testRunErrorInput() throws IOException{
-       mTestCase = TestCase.ERROR;
-       String[] contents =  getOutput();
-       long totalTime = getTotalTime(contents);
-
-       // prepare the mock object
-       ITestRunListener mockRunListener = EasyMock.createMock(ITestRunListener.class);
-       mockRunListener.testRunStarted(null, 0);
-       mockRunListener.testRunEnded(totalTime, Collections.<String, String>emptyMap());
-
-       EasyMock.replay(mockRunListener);
-       VtsMultiDeviceTestResultParser resultParser = new VtsMultiDeviceTestResultParser(
-               mockRunListener, RUN_NAME);
-       resultParser.processNewLines(contents);
-       resultParser.done();
-     }
-     /**
+    /**
      * @param contents The logs that are used for a test case.
      * @return {long} total running time of the test.
      */
-     private long getTotalTime(String[] contents) {
-         Date startDate = getDate(contents, true);
-         Date endDate = getDate(contents, false);
+    private long getTotalTime(String[] contents) {
+        Date startDate = getDate(contents, true);
+        Date endDate = getDate(contents, false);
 
-         if (startDate == null || endDate == null) {
-             return 0;
-         }
-         return endDate.getTime() - startDate.getTime();
-     }
+        if (startDate == null || endDate == null) {
+            return 0;
+        }
+        return endDate.getTime() - startDate.getTime();
+    }
 
     /**
-      * Returns the sample shell output for a test command.
-      * @return {String} shell output
-      * @throws IOException
-      */
-     private String[] getOutput() throws IOException{
-         BufferedReader br = null;
-         String output = null;
-         try {
-             br = new BufferedReader(new FileReader(getFileName()));
-             StringBuilder sb = new StringBuilder();
-             String line = br.readLine();
+     * Returns the sample shell output for a test command.
+     *
+     * @return {String} shell output
+     * @throws IOException
+     */
+    private String[] getOutput(String filePath) throws IOException {
+        File tmpDir = FileUtil.createTempDir("tmp-dir");
+        BufferedReader br = null;
+        String output = null;
+        try {
+            File resFile = VtsFileUtil.saveResourceFile(
+                    this.getClass().getResourceAsStream(filePath), tmpDir, "test-file");
+            br = new BufferedReader(new FileReader(resFile));
+            StringBuilder sb = new StringBuilder();
+            String line = br.readLine();
 
-             while (line != null) {
-                 sb.append(line);
-                 sb.append(System.lineSeparator());
-                 line = br.readLine();
-             }
-             output = sb.toString();
-         } finally {
-             br.close();
-         }
-         return output.split("\n");
-     }
+            while (line != null) {
+                sb.append(line);
+                sb.append(System.lineSeparator());
+                line = br.readLine();
+            }
+            output = sb.toString();
+        } finally {
+            if (br != null) {
+                br.close();
+            }
+            FileUtil.recursiveDelete(tmpDir);
+        }
+        return output.split("\n");
+    }
 
-     /** Return the file path that contains sample shell output logs.
-      *
-      * @return {String} The file path.
-      */
-     private String getFileName(){
-         String fileName = null;
-         switch (mTestCase) {
-             case NORMAL:
-                 fileName = OUTPUT_FILE_1;
-                 break;
-             case TIMEOUT:
-                 fileName = OUTPUT_FILE_2;
-                 break;
-             case ERROR:
-                 fileName = OUTPUT_FILE_3;
-                 break;
-             default:
-                 break;
-         }
-         StringBuilder path = new StringBuilder();
-         path.append(System.getProperty(USER_DIR)).append(File.separator).append(TEST_DIR).
-                 append(File.separator).append(RES).append(File.separator).
-                 append(TEST_TYPE).append(File.separator).append(fileName);
-         return path.toString();
-      }
+    /**
+     * Return the time in milliseconds to calculate the time elapsed in a particular test.
+     *
+     * @param lines The logs that need to be parsed.
+     * @param calculateStartDate flag which is true if we need to calculate start date.
+     * @return {Date} the start and end time corresponding to a test.
+     */
+    private Date getDate(String[] lines, boolean calculateStartDate) {
+        Date date = null;
+        int begin = calculateStartDate ? 0 : lines.length - 1;
+        int diff = calculateStartDate ? 1 : -1;
 
-     /**
-      * Return the time in milliseconds to calculate the time elapsed in a particular test.
-      * 
-      * @param lines The logs that need to be parsed.
-      * @param calculateStartDate flag which is true if we need to calculate start date.
-      * @return {Date} the start and end time corresponding to a test.
-      */
-     private Date getDate(String[] lines, boolean calculateStartDate) {
-         Date date = null;
-         int begin = calculateStartDate ? 0 : lines.length - 1;
-         int diff = calculateStartDate ? 1 : -1;
+        for (int index = begin; index >= 0 && index < lines.length; index += diff) {
+            lines[index].trim();
+            String[] toks = lines[index].split(" ");
 
-         for (int index = begin; index >= 0 && index < lines.length; index += diff) {
-             lines[index].trim();
-             String[] toks = lines[index].split(" ");
-
-             // set the start time from the first line
-             // the loop should continue if exception occurs, else it can break
-             if (toks.length < 3) {
-                 continue;
-             }
-             String time = toks[2];
-             SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS");
-             try {
-                 date = sdf.parse(time);
-             } catch (ParseException e) {
-                 continue;
-             }
-             break;
-         }
-         return date;
-     }
+            // set the start time from the first line
+            // the loop should continue if exception occurs, else it can break
+            if (toks.length < 3) {
+                continue;
+            }
+            String time = toks[2];
+            SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS");
+            try {
+                date = sdf.parse(time);
+            } catch (ParseException e) {
+                continue;
+            }
+            break;
+        }
+        return date;
+    }
 }
diff --git a/harnesses/tradefed/tests/src/com/android/tradefed/testtype/VtsMultiDeviceTestTest.java b/harnesses/tradefed/tests/src/com/android/tradefed/testtype/VtsMultiDeviceTestTest.java
index 503cfab..14774e9 100644
--- a/harnesses/tradefed/tests/src/com/android/tradefed/testtype/VtsMultiDeviceTestTest.java
+++ b/harnesses/tradefed/tests/src/com/android/tradefed/testtype/VtsMultiDeviceTestTest.java
@@ -15,6 +15,10 @@
  */
 package com.android.tradefed.testtype;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
 import com.android.tradefed.build.IBuildInfo;
 import com.android.tradefed.build.IFolderBuildInfo;
 import com.android.tradefed.device.DeviceNotAvailableException;
@@ -22,21 +26,19 @@
 import com.android.tradefed.result.ITestInvocationListener;
 import com.android.tradefed.util.CommandResult;
 import com.android.tradefed.util.CommandStatus;
-import com.android.tradefed.util.IRunUtil;
-import com.android.tradefed.util.ProcessHelper;
-import com.android.tradefed.util.RunInterruptedException;
 import com.android.tradefed.util.StreamUtil;
+import com.android.tradefed.util.VtsPythonRunnerHelper;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileWriter;
-import java.io.IOException;
-import java.util.Arrays;
 import java.util.HashMap;
-import java.util.LinkedList;
 import java.util.Map;
-import junit.framework.TestCase;
-import org.easymock.EasyMock;
 import org.json.JSONObject;
+import org.easymock.EasyMock;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
 
 /**
  * Unit tests for {@link VtsMultiDeviceTest}.
@@ -46,35 +48,30 @@
  * which contains the same config as the build output
  * out/host/linux-x86/vts/android-vts/testcases/
  */
-public class VtsMultiDeviceTestTest extends TestCase {
-
-    private ITestInvocationListener mMockInvocationListener = null;
-    private VtsMultiDeviceTest mTest = null;
-    private ProcessHelper mProcessHelper = null;
-    private String mPython = null;
+@RunWith(JUnit4.class)
+public class VtsMultiDeviceTestTest {
+    private static final String PYTHON_BINARY = "python";
     private static final String PYTHON_DIR = "mock/";
     private static final String TEST_CASE_PATH =
         "vts/testcases/host/sample/SampleLightTest";
 
+    private ITestInvocationListener mMockInvocationListener = null;
+    private VtsMultiDeviceTest mTest = null;
     /**
      * Helper to initialize the various EasyMocks we'll need.
      */
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
+    @Before
+    public void setUp() throws Exception {
         mMockInvocationListener = EasyMock.createMock(ITestInvocationListener.class);
-        mProcessHelper = null;
-        mPython = "python";
         mTest = new VtsMultiDeviceTest() {
+            // TODO: Test this method.
             @Override
-            protected ProcessHelper createProcessHelper(String[] cmd) {
-                assertCommand(cmd);
-                try {
-                    createResult(cmd[3]);
-                } catch (Exception e) {
-                    throw new RuntimeException(e);
-                }
-                return mProcessHelper;
+            protected void updateVtsRunnerTestConfig(JSONObject jsonObject) {
+                return;
+            }
+            @Override
+            protected VtsPythonRunnerHelper createVtsPythonRunnerHelper() {
+                return createMockVtsPythonRunnerHelper(CommandStatus.SUCCESS);
             }
         };
         mTest.setBuild(createMockBuildInfo());
@@ -86,7 +83,7 @@
      * Check VTS Python command strings.
      */
     private void assertCommand(String[] cmd) {
-        assertEquals(cmd[0], PYTHON_DIR + mPython);
+        assertEquals(cmd[0], PYTHON_BINARY);
         assertEquals(cmd[1], "-m");
         assertEquals(cmd[2], TEST_CASE_PATH.replace("/", "."));
         assertTrue(cmd[3].endsWith(".json"));
@@ -110,33 +107,6 @@
         new File(logPath, VtsMultiDeviceTest.REPORT_MESSAGE_FILE_NAME).createNewFile();
     }
 
-    /**
-     * Create a process helper which mocks status of a running process.
-     */
-    private static ProcessHelper createMockProcessHelper(CommandStatus... status) {
-        Process process;
-        try {
-            process = new ProcessBuilder("true").start();
-        } catch (IOException e) {
-            throw new RuntimeException(e);
-        }
-        LinkedList<CommandStatus> nextStatus = new LinkedList<CommandStatus>(Arrays.asList(status));
-        return new ProcessHelper(process) {
-            @Override
-            public CommandStatus waitForProcess(long timeoutMsecs) throws RunInterruptedException {
-                CommandStatus currentStatus = nextStatus.removeFirst();
-                if (currentStatus == null) {
-                    throw new RunInterruptedException();
-                }
-                return currentStatus;
-            }
-
-            @Override
-            public boolean isRunning() {
-                return !nextStatus.isEmpty();
-            }
-        };
-    }
 
     /**
      * Create a mock IBuildInfo with necessary getter methods.
@@ -146,71 +116,36 @@
         buildAttributes.put("ROOT_DIR", "DIR_NOT_EXIST");
         buildAttributes.put("ROOT_DIR2", "DIR_NOT_EXIST");
         buildAttributes.put("SUITE_NAME", "JUNIT_TEST_SUITE");
-        IFolderBuildInfo buildInfo = EasyMock.createMock(IFolderBuildInfo.class);
-        EasyMock.expect(buildInfo.getBuildId()).
-                andReturn("BUILD_ID");
-        EasyMock.expect(buildInfo.getBuildTargetName()).
-                andReturn("BUILD_TARGET_NAME");
-        EasyMock.expect(buildInfo.getTestTag()).
-                andReturn("TEST_TAG").atLeastOnce();
-        EasyMock.expect(buildInfo.getDeviceSerial()).
-                andReturn("1234567890ABCXYZ").atLeastOnce();
-        EasyMock.expect(buildInfo.getRootDir()).
-                andReturn(new File("DIR_NOT_EXIST"));
-        EasyMock.expect(buildInfo.getBuildAttributes()).
-                andReturn(buildAttributes).atLeastOnce();
-        EasyMock.expect(buildInfo.getFile(EasyMock.eq("vts"))).
-                andReturn(null);
-        EasyMock.expect(buildInfo.getFile(EasyMock.eq("PYTHONPATH"))).
-                andReturn(new File("DIR_NOT_EXIST")).atLeastOnce();
-        EasyMock.expect(buildInfo.getFile(EasyMock.eq("VIRTUALENVPATH"))).
-                andReturn(new File("DIR_NOT_EXIST"));
-        EasyMock.expect(buildInfo.getFile(EasyMock.anyObject())).andReturn(null).anyTimes();
+        IFolderBuildInfo buildInfo = EasyMock.createNiceMock(IFolderBuildInfo.class);
+        EasyMock.expect(buildInfo.getBuildId()).andReturn("BUILD_ID").anyTimes();
+        EasyMock.expect(buildInfo.getBuildTargetName()).andReturn("BUILD_TARGET_NAME").anyTimes();
+        EasyMock.expect(buildInfo.getTestTag()).andReturn("TEST_TAG").anyTimes();
+        EasyMock.expect(buildInfo.getDeviceSerial()).andReturn("1234567890ABCXYZ").anyTimes();
+        EasyMock.expect(buildInfo.getRootDir()).andReturn(new File("DIR_NOT_EXIST")).anyTimes();
+        EasyMock.expect(buildInfo.getBuildAttributes()).andReturn(buildAttributes).anyTimes();
+        EasyMock.expect(buildInfo.getFile(EasyMock.eq("PYTHONPATH")))
+                .andReturn(new File("DIR_NOT_EXIST"))
+                .anyTimes();
+        EasyMock.expect(buildInfo.getFile(EasyMock.eq("VIRTUALENVPATH")))
+                .andReturn(new File("DIR_NOT_EXIST"))
+                .anyTimes();
         EasyMock.replay(buildInfo);
         return buildInfo;
     }
 
     /**
-     * Create a mock IRunUtil which sets environment variables and finds Python binary file.
-     */
-    private IRunUtil createMockRunUtil(String findFileCommand) {
-        IRunUtil runUtil = EasyMock.createMock(IRunUtil.class);
-        runUtil.setEnvVariable(EasyMock.eq("VTS"), EasyMock.eq("1"));
-        EasyMock.expectLastCall();
-        CommandResult findResult = new CommandResult();
-        findResult.setStatus(CommandStatus.SUCCESS);
-        findResult.setStdout(PYTHON_DIR + mPython);
-        EasyMock.expect(runUtil.runTimedCmd(EasyMock.anyLong(), EasyMock.eq(findFileCommand),
-                                EasyMock.eq(mPython)))
-                .andReturn(findResult);
-        EasyMock.replay(runUtil);
-        return runUtil;
-    }
-
-    /**
      * Create a mock ITestDevice with necessary getter methods.
      */
     private static ITestDevice createMockDevice() {
         // TestDevice
-        ITestDevice device = EasyMock.createMock(ITestDevice.class);
+        ITestDevice device = EasyMock.createNiceMock(ITestDevice.class);
         try {
-            EasyMock.expect(device.getSerialNumber()).
-                andReturn("1234567890ABCXYZ").atLeastOnce();
-            EasyMock.expect(device.getBuildAlias()).
-                andReturn("BUILD_ALIAS");
-            EasyMock.expect(device.getBuildFlavor()).
-                andReturn("BUILD_FLAVOR");
-            EasyMock.expect(device.getBuildId()).
-                andReturn("BUILD_ID");
-            EasyMock.expect(device.getProperty("ro.vts.coverage")).
-                andReturn(null);
-            EasyMock.expect(device.getProductType()).
-                andReturn("PRODUCT_TYPE");
-            EasyMock.expect(device.getProductVariant()).
-                andReturn("PRODUCT_VARIANT");
-            EasyMock.expect(device.executeShellCommand(EasyMock.startsWith("log")))
-                    .andReturn(null)
-                    .anyTimes();
+            EasyMock.expect(device.getSerialNumber()).andReturn("1234567890ABCXYZ").anyTimes();
+            EasyMock.expect(device.getBuildAlias()).andReturn("BUILD_ALIAS").anyTimes();
+            EasyMock.expect(device.getBuildFlavor()).andReturn("BUILD_FLAVOR").anyTimes();
+            EasyMock.expect(device.getBuildId()).andReturn("BUILD_ID").anyTimes();
+            EasyMock.expect(device.getProductType()).andReturn("PRODUCT_TYPE").anyTimes();
+            EasyMock.expect(device.getProductVariant()).andReturn("PRODUCT_VARIANT").anyTimes();
         } catch (DeviceNotAvailableException e) {
             fail();
         }
@@ -219,12 +154,31 @@
     }
 
     /**
+     * Create a process helper which mocks status of a running process.
+     */
+    private VtsPythonRunnerHelper createMockVtsPythonRunnerHelper(CommandStatus status) {
+        return new VtsPythonRunnerHelper(new File(PYTHON_DIR)) {
+            @Override
+            public String runPythonRunner(
+                    String[] cmd, CommandResult commandResult, long testTimeout) {
+                assertCommand(cmd);
+                try {
+                    createResult(cmd[3]);
+                } catch (Exception e) {
+                    throw new RuntimeException(e);
+                }
+                commandResult.setStatus(status);
+                return null;
+            }
+        };
+    }
+
+    /**
      * Test the run method with a normal input.
      */
+    @Test
     public void testRunNormalInput() {
-        mProcessHelper = createMockProcessHelper(CommandStatus.SUCCESS);
         mTest.setDevice(createMockDevice());
-        mTest.setRunUtil(createMockRunUtil("which"));
         try {
             mTest.run(mMockInvocationListener);
         } catch (IllegalArgumentException e) {
@@ -239,26 +193,9 @@
     }
 
     /**
-     * Test the run method with a normal input and Windows environment variable.
-     */
-    public void testRunNormalInputOnWindows()
-            throws IllegalArgumentException, DeviceNotAvailableException {
-        mProcessHelper = createMockProcessHelper(CommandStatus.SUCCESS);
-        mPython = "python.exe";
-        String originalName = System.getProperty(VtsMultiDeviceTest.OS_NAME);
-        System.setProperty(VtsMultiDeviceTest.OS_NAME, VtsMultiDeviceTest.WINDOWS);
-        mTest.setDevice(createMockDevice());
-        mTest.setRunUtil(createMockRunUtil("where"));
-        try {
-            mTest.run(mMockInvocationListener);
-        } finally {
-            System.setProperty(VtsMultiDeviceTest.OS_NAME, originalName);
-        }
-    }
-
-    /**
      * Test the run method when the device is set null.
      */
+    @Test
     public void testRunDeviceNotAvailable() {
         mTest.setDevice(null);
         try {
@@ -271,61 +208,4 @@
             // expected
        }
     }
-
-    /**
-     * Test the run method with abnormal input data.
-     */
-    public void testRunNormalInputCommandFailed() {
-        mProcessHelper = createMockProcessHelper(CommandStatus.FAILED);
-        mTest.setDevice(createMockDevice());
-        mTest.setRunUtil(createMockRunUtil("which"));
-        try {
-            mTest.run(mMockInvocationListener);
-            fail();
-        } catch (RuntimeException e) {
-            if (!"Failed to run VTS test".equals(e.getMessage())) {
-                fail();
-            }
-            // expected
-        } catch (DeviceNotAvailableException e) {
-            // not expected
-            fail();
-            e.printStackTrace();
-        }
-    }
-
-    /**
-     * Test the run method in which the command times out.
-     */
-    public void testRunNormalInputTimeout() {
-        mProcessHelper = createMockProcessHelper(CommandStatus.TIMED_OUT, CommandStatus.TIMED_OUT);
-        mTest.setDevice(createMockDevice());
-        mTest.setRunUtil(createMockRunUtil("which"));
-        try {
-            mTest.run(mMockInvocationListener);
-        } catch (IllegalArgumentException e) {
-            fail();
-        } catch (DeviceNotAvailableException e) {
-            fail();
-        }
-    }
-
-    /**
-     * Test the run method in which the command is interrupted.
-     */
-    public void testRunNormalInputInterrupted() {
-        mProcessHelper = createMockProcessHelper(null, CommandStatus.SUCCESS);
-        mTest.setDevice(createMockDevice());
-        mTest.setRunUtil(createMockRunUtil("which"));
-        try {
-            mTest.run(mMockInvocationListener);
-            fail();
-        } catch (IllegalArgumentException e) {
-            fail();
-        } catch (RunInterruptedException e) {
-            // expected
-        } catch (DeviceNotAvailableException e) {
-            fail();
-        }
-    }
 }
diff --git a/harnesses/tradefed/tests/src/com/android/tradefed/util/CmdUtilTest.java b/harnesses/tradefed/tests/src/com/android/tradefed/util/CmdUtilTest.java
new file mode 100644
index 0000000..e115bd0
--- /dev/null
+++ b/harnesses/tradefed/tests/src/com/android/tradefed/util/CmdUtilTest.java
@@ -0,0 +1,102 @@
+package com.android.tradefed.util;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.*;
+
+import com.android.tradefed.device.ITestDevice;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.function.Predicate;
+import java.util.Vector;
+
+/**
+ * Unit tests for {@link CmdUtil}.
+ */
+@RunWith(JUnit4.class)
+public class CmdUtilTest {
+    static final String RUN_CMD = "run cmd";
+    static final String TEST_CMD = "test cmd";
+
+    class MockSleeper implements CmdUtil.ISleeper {
+        @Override
+        public void sleep(int seconds) throws InterruptedException {
+            return;
+        }
+    };
+
+    CmdUtil mCmdUtil = null;
+
+    // Predicates to stop retrying cmd.
+    private Predicate<String> mCheckEmpty = (String str) -> {
+        return str.isEmpty();
+    };
+
+    @Mock private ITestDevice mDevice;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        CmdUtil.ISleeper msleeper = new MockSleeper();
+        mCmdUtil = new CmdUtil();
+        mCmdUtil.setSleeper(msleeper);
+    }
+
+    @Test
+    public void testWaitCmdSuccess() throws Exception {
+        doReturn("").when(mDevice).executeShellCommand(TEST_CMD);
+        assertTrue(mCmdUtil.waitCmdResultWithDelay(mDevice, TEST_CMD, mCheckEmpty));
+    }
+
+    @Test
+    public void testWaitCmdSuccessWithRetry() throws Exception {
+        when(mDevice.executeShellCommand(TEST_CMD)).thenReturn("something").thenReturn("");
+        assertTrue(mCmdUtil.waitCmdResultWithDelay(mDevice, TEST_CMD, mCheckEmpty));
+    }
+
+    @Test
+    public void testWaitCmdSuccessFail() throws Exception {
+        doReturn("something").when(mDevice).executeShellCommand(TEST_CMD);
+        assertFalse(mCmdUtil.waitCmdResultWithDelay(mDevice, TEST_CMD, mCheckEmpty));
+    }
+
+    @Test
+    public void testRetrySuccess() throws Exception {
+        doReturn("").when(mDevice).executeShellCommand(TEST_CMD);
+        assertTrue(mCmdUtil.retry(mDevice, RUN_CMD, TEST_CMD, mCheckEmpty));
+        verify(mDevice, times(1)).executeShellCommand(eq(RUN_CMD));
+    }
+
+    @Test
+    public void testRetrySuccessWithRetry() throws Exception {
+        when(mDevice.executeShellCommand(TEST_CMD)).thenReturn("something").thenReturn("");
+        assertTrue(mCmdUtil.retry(mDevice, RUN_CMD, TEST_CMD, mCheckEmpty));
+        verify(mDevice, times(2)).executeShellCommand(eq(RUN_CMD));
+    }
+
+    @Test
+    public void testRetryFail() throws Exception {
+        doReturn("something").when(mDevice).executeShellCommand(TEST_CMD);
+        assertFalse(mCmdUtil.retry(mDevice, RUN_CMD, TEST_CMD, mCheckEmpty));
+        verify(mDevice, times(mCmdUtil.MAX_RETRY_COUNT)).executeShellCommand(eq(RUN_CMD));
+    }
+
+    @Test
+    public void testRetryMultipleCommandsSuccess() throws Exception {
+        doReturn("").when(mDevice).executeShellCommand(TEST_CMD);
+        Vector<String> cmds = new Vector<String>();
+        int command_count = 5;
+        for (int i = 0; i < command_count; i++) {
+            cmds.add(RUN_CMD);
+        }
+        assertTrue(mCmdUtil.retry(mDevice, cmds, TEST_CMD, mCheckEmpty));
+        verify(mDevice, times(command_count)).executeShellCommand(eq(RUN_CMD));
+    }
+}
diff --git a/harnesses/tradefed/tests/src/com/android/tradefed/util/ProcessHelperTest.java b/harnesses/tradefed/tests/src/com/android/tradefed/util/ProcessHelperTest.java
index 2859f27..70ff407 100644
--- a/harnesses/tradefed/tests/src/com/android/tradefed/util/ProcessHelperTest.java
+++ b/harnesses/tradefed/tests/src/com/android/tradefed/util/ProcessHelperTest.java
@@ -16,79 +16,149 @@
 
 package com.android.tradefed.util;
 
-import com.android.tradefed.util.CommandStatus;
-import com.android.tradefed.util.IRunUtil;
-import com.android.tradefed.util.ProcessHelper;
-import com.android.tradefed.util.RunInterruptedException;
-import com.android.tradefed.util.RunUtil;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
-import junit.framework.TestCase;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
 
+import java.io.ByteArrayInputStream;
 import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
 
 /**
  * Test cases for {@link ProcessHelper}.
  */
-public class ProcessHelperTest extends TestCase {
-    private ProcessHelper mProcess;
+@RunWith(JUnit4.class)
+public class ProcessHelperTest {
+    private ProcessHelper mProcessHelper;
 
     /**
-     * Reset the ProcessHelper
+     * Return a mock process.
      */
-    @Override
+    private Process createMockProcess(
+            String stdout, String stderr, int exitValue, long executionTimeMsecs) {
+        // No need to close OutputStream and ByteArrayInputStream because doing so has no effect.
+        OutputStream stdinStream = new OutputStream() {
+            @Override
+            public void write(int b) throws IOException {}
+        };
+        InputStream stdoutStream = new ByteArrayInputStream(stdout.getBytes());
+        InputStream stderrStream = new ByteArrayInputStream(stderr.getBytes());
+        long endTime = System.currentTimeMillis() + executionTimeMsecs;
+
+        return new Process() {
+            private boolean destroyed = false;
+
+            private boolean isRunning() {
+                return System.currentTimeMillis() <= endTime && !destroyed;
+            }
+
+            @Override
+            public void destroy() {
+                destroyed = true;
+            }
+
+            @Override
+            public int exitValue() {
+                if (isRunning()) {
+                    throw new IllegalThreadStateException();
+                }
+                return exitValue;
+            }
+
+            @Override
+            public InputStream getInputStream() {
+                return stdoutStream;
+            }
+
+            @Override
+            public OutputStream getOutputStream() {
+                return stdinStream;
+            }
+
+            @Override
+            public InputStream getErrorStream() {
+                return stderrStream;
+            }
+
+            @Override
+            public int waitFor() throws InterruptedException {
+                while (isRunning()) {
+                    Thread.sleep(50);
+                }
+                return exitValue;
+            }
+        };
+    }
+
+    /**
+     * Reset the ProcessHelper.
+     */
+    @Before
     public void setUp() {
-        mProcess = null;
+        mProcessHelper = null;
     }
 
     /**
      * Terminate the process, join threads and close IO streams.
      */
-    @Override
+    @After
     public void tearDown() {
-        if (mProcess != null) {
-            mProcess.cleanUp();
+        if (mProcessHelper != null) {
+            mProcessHelper.cleanUp();
         }
     }
 
     /**
      * Test running a process that returns zero.
      */
-    public void testSuccess() throws IOException {
-        mProcess = new ProcessHelper(new ProcessBuilder("echo", "123").start());
-        CommandStatus status = mProcess.waitForProcess(1000);
+    @Test
+    public void testSuccess() {
+        mProcessHelper = new ProcessHelper(createMockProcess("123\n", "456\n", 0, 10));
+        CommandStatus status = mProcessHelper.waitForProcess(10000);
         assertEquals(CommandStatus.SUCCESS, status);
-        assertFalse(mProcess.isRunning());
-        assertTrue(mProcess.getStdout().equals("123\n"));
-        assertTrue(mProcess.getStderr().isEmpty());
+        assertFalse(mProcessHelper.isRunning());
+        assertTrue(mProcessHelper.getStdout().equals("123\n"));
+        assertTrue(mProcessHelper.getStderr().equals("456\n"));
     }
 
     /**
      * Test running a process that returns non-zero.
      */
-    public void testFailure() throws IOException {
-        mProcess = new ProcessHelper(new ProcessBuilder("ls", "--WRONG-OPTION").start());
-        CommandStatus status = mProcess.waitForProcess(1000);
+    @Test
+    public void testFailure() {
+        mProcessHelper = new ProcessHelper(createMockProcess("123\n", "456\n", 1, 10));
+        CommandStatus status = mProcessHelper.waitForProcess(10000);
         assertEquals(CommandStatus.FAILED, status);
-        assertFalse(mProcess.isRunning());
-        assertTrue(mProcess.getStdout().isEmpty());
-        assertTrue(mProcess.getStderr().contains("unrecognized option"));
+        assertFalse(mProcessHelper.isRunning());
+        assertTrue(mProcessHelper.getStdout().equals("123\n"));
+        assertTrue(mProcessHelper.getStderr().equals("456\n"));
     }
 
     /**
      * Test running a process that times out.
      */
-    public void testTimeout() throws IOException {
-        mProcess = new ProcessHelper(new ProcessBuilder("cat").start());
-        CommandStatus status = mProcess.waitForProcess(30);
+    @Test
+    public void testTimeout() {
+        mProcessHelper = new ProcessHelper(createMockProcess("", "", 1, 10000));
+        CommandStatus status = mProcessHelper.waitForProcess(100);
         assertEquals(CommandStatus.TIMED_OUT, status);
-        assertTrue(mProcess.isRunning());
+        assertTrue(mProcessHelper.isRunning());
     }
 
     /**
      * Test running a process and being interrupted.
      */
-    public void testInterrupt() throws IOException, InterruptedException {
-        mProcess = new ProcessHelper(new ProcessBuilder("cat").start());
+    @Test
+    public void testInterrupt() throws InterruptedException {
+        mProcessHelper = new ProcessHelper(createMockProcess("", "", 1, 10000));
         IRunUtil runUtil = RunUtil.getDefault();
         Thread testThread = Thread.currentThread();
 
@@ -107,10 +177,10 @@
         runUtil.allowInterrupt(true);
         timer.start();
         try {
-            mProcess.waitForProcess(100);
+            mProcessHelper.waitForProcess(100);
             fail();
         } catch (RunInterruptedException e) {
-            assertTrue(mProcess.isRunning());
+            assertTrue(mProcessHelper.isRunning());
         } finally {
             timer.join(1000);
         }
diff --git a/harnesses/tradefed/tests/src/com/android/tradefed/util/VtsPythonRunnerHelperTest.java b/harnesses/tradefed/tests/src/com/android/tradefed/util/VtsPythonRunnerHelperTest.java
new file mode 100644
index 0000000..875ca79
--- /dev/null
+++ b/harnesses/tradefed/tests/src/com/android/tradefed/util/VtsPythonRunnerHelperTest.java
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tradefed.util;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+import com.android.tradefed.build.IFolderBuildInfo;
+import com.android.tradefed.util.CommandResult;
+import com.android.tradefed.util.CommandStatus;
+import com.android.tradefed.util.EnvUtil;
+import com.android.tradefed.util.IRunUtil;
+import com.android.tradefed.util.ProcessHelper;
+import com.android.tradefed.util.RunInterruptedException;
+import com.android.tradefed.util.VtsPythonRunnerHelper;
+import java.io.File;
+import java.io.IOException;
+import org.easymock.EasyMock;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Unit tests for {@link VtsPythonRunnerHelper}.
+ */
+@RunWith(JUnit4.class)
+public class VtsPythonRunnerHelperTest {
+    private static final String[] mPythonCmd = {"python"};
+    private static final long mTestTimeout = 1000 * 5;
+
+    private ProcessHelper mProcessHelper = null;
+    private VtsPythonRunnerHelper mVtsPythonRunnerHelper = null;
+    private String mVirtualenvPath = "virtualenv_path_" + System.currentTimeMillis();
+
+    @Before
+    public void setUp() throws Exception {
+        IFolderBuildInfo buildInfo = EasyMock.createNiceMock(IFolderBuildInfo.class);
+        EasyMock.replay(buildInfo);
+        mVtsPythonRunnerHelper = new VtsPythonRunnerHelper(new File(mVirtualenvPath)) {
+            @Override
+            protected ProcessHelper createProcessHelper(String[] cmd) {
+                return mProcessHelper;
+            }
+        };
+    }
+
+    /**
+     * Create a process helper which mocks status of a running process.
+     */
+    private static ProcessHelper createMockProcessHelper(
+            CommandStatus status, boolean interrupted, boolean keepRunning) {
+        Process process;
+        try {
+            process = new ProcessBuilder("true").start();
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+        return new ProcessHelper(process) {
+            @Override
+            public CommandStatus waitForProcess(long timeoutMsecs) throws RunInterruptedException {
+                if (interrupted) {
+                    throw new RunInterruptedException();
+                }
+                return status;
+            }
+
+            @Override
+            public boolean isRunning() {
+                return keepRunning;
+            }
+        };
+    }
+
+    private static ProcessHelper createMockProcessHelper(
+            CommandStatus status, boolean interrupted) {
+        return createMockProcessHelper(status, interrupted, /*keepRunning=*/false);
+    }
+
+    private static ProcessHelper createMockProcessHelper(CommandStatus status) {
+        return createMockProcessHelper(status, /*interrupted=*/false, /*keepRunning=*/false);
+    }
+
+    /**
+     * Create a mock runUtil with returns the expected results.
+     */
+    private IRunUtil createMockRunUtil() {
+        IRunUtil runUtil = new RunUtil() {
+            private String path = null;
+
+            @Override
+            public void setEnvVariable(String key, String value) {
+                super.setEnvVariable(key, value);
+                if (key.equals("PATH")) {
+                    path = value;
+                }
+            }
+
+            @Override
+            public CommandResult runTimedCmd(final long timeout, final String... command) {
+                CommandResult cmdRes = new CommandResult(CommandStatus.SUCCESS);
+                String out = "";
+                if (command.length == 2 && command[0].equals("which")
+                        && command[1].equals("python")) {
+                    if (path != null) {
+                        out = path.split(":")[0] + "/python";
+                    } else {
+                        out = "/usr/bin/python";
+                    }
+                }
+                cmdRes.setStdout(out);
+                return cmdRes;
+            }
+        };
+        return runUtil;
+    }
+
+    @Test
+    public void testProcessRunSuccess() {
+        CommandResult commandResult = new CommandResult();
+        mProcessHelper = createMockProcessHelper(CommandStatus.SUCCESS);
+        String interruptMessage =
+                mVtsPythonRunnerHelper.runPythonRunner(mPythonCmd, commandResult, mTestTimeout);
+        assertEquals(interruptMessage, null);
+        assertEquals(commandResult.getStatus(), CommandStatus.SUCCESS);
+    }
+
+    @Test
+    public void testProcessRunFailed() {
+        CommandResult commandResult = new CommandResult();
+        mProcessHelper = createMockProcessHelper(CommandStatus.FAILED);
+        String interruptMessage =
+                mVtsPythonRunnerHelper.runPythonRunner(mPythonCmd, commandResult, mTestTimeout);
+        assertEquals(interruptMessage, null);
+        assertEquals(commandResult.getStatus(), CommandStatus.FAILED);
+    }
+
+    @Test
+    public void testProcessRunTimeout() {
+        CommandResult commandResult = new CommandResult();
+        mProcessHelper = createMockProcessHelper(CommandStatus.TIMED_OUT);
+        String interruptMessage =
+                mVtsPythonRunnerHelper.runPythonRunner(mPythonCmd, commandResult, mTestTimeout);
+        assertEquals(interruptMessage, null);
+        assertEquals(commandResult.getStatus(), CommandStatus.TIMED_OUT);
+    }
+
+    @Test
+    public void testProcessRunInterrupted() {
+        CommandResult commandResult = new CommandResult();
+        mProcessHelper = createMockProcessHelper(null, /*interrupted=*/true);
+        String interruptMessage =
+                mVtsPythonRunnerHelper.runPythonRunner(mPythonCmd, commandResult, mTestTimeout);
+        assertNotEquals(interruptMessage, null);
+        assertEquals(commandResult.getStatus(), CommandStatus.TIMED_OUT);
+    }
+
+    @Test
+    public void testActivateVirtualEnvNotExist() {
+        IRunUtil runUtil = createMockRunUtil();
+        assertEquals(null, VtsPythonRunnerHelper.getPythonBinDir(mVirtualenvPath));
+        VtsPythonRunnerHelper.activateVirtualenv(runUtil, mVirtualenvPath);
+        String pythonBinary = runUtil.runTimedCmd(1000, "which", "python").getStdout();
+        assertEquals(pythonBinary, "/usr/bin/python");
+    }
+
+    @Test
+    public void testActivateVirtualEnvExist() {
+        IRunUtil runUtil = createMockRunUtil();
+        String binDirName = EnvUtil.isOnWindows() ? "Scripts" : "bin";
+        File envDir = new File(mVirtualenvPath);
+        File binDir = new File(mVirtualenvPath, binDirName);
+        try {
+            System.out.println(envDir.mkdir());
+            System.out.println(binDir.mkdir());
+            System.out.println(binDir.exists());
+            assertEquals(binDir.getAbsolutePath(),
+                    VtsPythonRunnerHelper.getPythonBinDir(mVirtualenvPath));
+            VtsPythonRunnerHelper.activateVirtualenv(runUtil, mVirtualenvPath);
+            String pythonBinary = runUtil.runTimedCmd(1000, "which", "python").getStdout();
+            assertEquals(pythonBinary, new File(binDir, "python").getAbsolutePath());
+        } finally {
+            binDir.delete();
+            envDir.delete();
+        }
+    }
+
+}
diff --git a/proto/ComponentSpecificationMessage.proto b/proto/ComponentSpecificationMessage.proto
index 4375405..5ccc88b 100644
--- a/proto/ComponentSpecificationMessage.proto
+++ b/proto/ComponentSpecificationMessage.proto
@@ -188,11 +188,14 @@
   // Component type, e.g., BLUETOOTH, used for Conventional HAL only.
   optional ComponentType component_type = 22;
   // Component version (e.g., 1.0).
-  optional bytes component_type_version = 23;
+  optional bytes component_type_version = 23 [deprecated = true];
   // Component name (e.g., INfc), used for HIDL HALs only.
   optional bytes component_name = 24;
   // Component package name (e.g., android.hardware.nfc).
   optional bytes package_name = 25;
+  // Component major and minor versions stored separately.
+  optional uint32 component_type_version_major = 26;
+  optional uint32 component_type_version_minor = 27;
 
   // Specifies API function and inputs.
   optional FunctionSpecificationMessage api = 100;
diff --git a/proto/ComponentSpecificationMessage_pb2.py b/proto/ComponentSpecificationMessage_pb2.py
index edff528..28520b4 100644
--- a/proto/ComponentSpecificationMessage_pb2.py
+++ b/proto/ComponentSpecificationMessage_pb2.py
@@ -14,7 +14,7 @@
 DESCRIPTOR = _descriptor.FileDescriptor(
   name='ComponentSpecificationMessage.proto',
   package='android.vts',
-  serialized_pb='\n#ComponentSpecificationMessage.proto\x12\x0b\x61ndroid.vts\"e\n\x1c\x43\x61llFlowSpecificationMessage\x12\x14\n\x05\x65ntry\x18\x01 \x01(\x08:\x05\x66\x61lse\x12\x13\n\x04\x65xit\x18\x02 \x01(\x08:\x05\x66\x61lse\x12\x0c\n\x04next\x18\x0b \x03(\x0c\x12\x0c\n\x04prev\x18\x0c \x03(\x0c\"C\n NativeCodeCoverageRawDataMessage\x12\x11\n\tfile_path\x18\x01 \x01(\x0c\x12\x0c\n\x04gcda\x18\x0b \x01(\x0c\"\xbd\x02\n\x13\x46unctionCallMessage\x12\x1b\n\x13hidl_interface_name\x18\x01 \x01(\x0c\x12\x19\n\rhal_driver_id\x18\x0b \x01(\x05:\x02-1\x12\x34\n\x0f\x63omponent_class\x18\x15 \x01(\x0e\x32\x1b.android.vts.ComponentClass\x12\x32\n\x0e\x63omponent_type\x18\x16 \x01(\x0e\x32\x1a.android.vts.ComponentType\x12\x1e\n\x16\x63omponent_type_version\x18\x17 \x01(\x0c\x12\x16\n\x0e\x63omponent_name\x18\x18 \x01(\x0c\x12\x14\n\x0cpackage_name\x18\x19 \x01(\x0c\x12\x36\n\x03\x61pi\x18\x64 \x01(\x0b\x32).android.vts.FunctionSpecificationMessage\"\xde\x05\n\x1c\x46unctionSpecificationMessage\x12\x0c\n\x04name\x18\x01 \x01(\x0c\x12\x16\n\x0esubmodule_name\x18\x02 \x01(\x0c\x12\x19\n\x11hidl_interface_id\x18\x03 \x01(\x05\x12>\n\x0breturn_type\x18\x0b \x01(\x0b\x32).android.vts.VariableSpecificationMessage\x12\x43\n\x10return_type_hidl\x18\x0c \x03(\x0b\x32).android.vts.VariableSpecificationMessage\x12N\n\x1areturn_type_submodule_spec\x18\r \x01(\x0b\x32*.android.vts.ComponentSpecificationMessage\x12\x36\n\x03\x61rg\x18\x15 \x03(\x0b\x32).android.vts.VariableSpecificationMessage\x12;\n\x08\x63\x61llflow\x18\x1f \x03(\x0b\x32).android.vts.CallFlowSpecificationMessage\x12\x1a\n\x0b\x64o_not_fuzz\x18  \x01(\x08:\x05\x66\x61lse\x12\x17\n\x0bis_callback\x18) \x01(\x08\x42\x02\x18\x01\x12J\n\x10\x66unction_pointer\x18* \x01(\x0b\x32\x30.android.vts.FunctionPointerSpecificationMessage\x12\x16\n\x0eprofiling_data\x18\x65 \x03(\x02\x12 \n\x17processed_coverage_data\x18\xc9\x01 \x03(\r\x12I\n\x11raw_coverage_data\x18\xca\x01 \x03(\x0b\x32-.android.vts.NativeCodeCoverageRawDataMessage\x12\x14\n\x0bparent_path\x18\xad\x02 \x01(\x0c\x12\x17\n\x0esyscall_number\x18\x91\x03 \x01(\r\"\xf5\x02\n\x16ScalarDataValueMessage\x12\x0e\n\x06\x62ool_t\x18\x01 \x01(\x08\x12\x0e\n\x06int8_t\x18\x0b \x01(\x05\x12\x0f\n\x07uint8_t\x18\x0c \x01(\r\x12\x0c\n\x04\x63har\x18\r \x01(\x05\x12\r\n\x05uchar\x18\x0e \x01(\r\x12\x0f\n\x07int16_t\x18\x15 \x01(\x05\x12\x10\n\x08uint16_t\x18\x16 \x01(\r\x12\x0f\n\x07int32_t\x18\x1f \x01(\x05\x12\x10\n\x08uint32_t\x18  \x01(\r\x12\x0f\n\x07int64_t\x18) \x01(\x03\x12\x10\n\x08uint64_t\x18* \x01(\x04\x12\x0f\n\x07\x66loat_t\x18\x65 \x01(\x02\x12\x10\n\x08\x64ouble_t\x18\x66 \x01(\x01\x12\x10\n\x07pointer\x18\xc9\x01 \x01(\r\x12\x0f\n\x06opaque\x18\xca\x01 \x01(\r\x12\x15\n\x0cvoid_pointer\x18\xd3\x01 \x01(\r\x12\x15\n\x0c\x63har_pointer\x18\xd4\x01 \x01(\r\x12\x16\n\ruchar_pointer\x18\xd5\x01 \x01(\r\x12\x18\n\x0fpointer_pointer\x18\xfb\x01 \x01(\r\"\xd1\x01\n#FunctionPointerSpecificationMessage\x12\x15\n\rfunction_name\x18\x01 \x01(\x0c\x12\x0f\n\x07\x61\x64\x64ress\x18\x0b \x01(\r\x12\n\n\x02id\x18\x15 \x01(\x0c\x12\x36\n\x03\x61rg\x18\x65 \x03(\x0b\x32).android.vts.VariableSpecificationMessage\x12>\n\x0breturn_type\x18o \x01(\x0b\x32).android.vts.VariableSpecificationMessage\"9\n\x16StringDataValueMessage\x12\x0f\n\x07message\x18\x01 \x01(\x0c\x12\x0e\n\x06length\x18\x0b \x01(\r\"z\n\x14\x45numDataValueMessage\x12\x12\n\nenumerator\x18\x01 \x03(\x0c\x12\x39\n\x0cscalar_value\x18\x02 \x03(\x0b\x32#.android.vts.ScalarDataValueMessage\x12\x13\n\x0bscalar_type\x18\x03 \x01(\x0c\"8\n\x16MemoryDataValueMessage\x12\x0c\n\x04size\x18\x01 \x01(\x03\x12\x10\n\x08\x63ontents\x18\x02 \x01(\x0c\"\x93\x01\n\tFdMessage\x12!\n\x04type\x18\x01 \x01(\x0e\x32\x13.android.vts.FdType\x12\x0c\n\x04mode\x18\x02 \x01(\r\x12\r\n\x05\x66lags\x18\x03 \x01(\x05\x12\x11\n\tfile_name\x18\x04 \x01(\x0c\x12\x33\n\x06memory\x18\x06 \x01(\x0b\x32#.android.vts.MemoryDataValueMessage\"\x85\x01\n\x16HandleDataValueMessage\x12\x0f\n\x07version\x18\x01 \x01(\x05\x12\x0f\n\x07num_fds\x18\x02 \x01(\x05\x12\x10\n\x08num_ints\x18\x03 \x01(\x05\x12&\n\x06\x66\x64_val\x18\x04 \x03(\x0b\x32\x16.android.vts.FdMessage\x12\x0f\n\x07int_val\x18\x05 \x03(\x05\"\xc3\t\n\x1cVariableSpecificationMessage\x12\x0c\n\x04name\x18\x01 \x01(\x0c\x12\'\n\x04type\x18\x02 \x01(\x0e\x32\x19.android.vts.VariableType\x12\x39\n\x0cscalar_value\x18\x65 \x01(\x0b\x32#.android.vts.ScalarDataValueMessage\x12\x13\n\x0bscalar_type\x18\x66 \x01(\x0c\x12\x39\n\x0cstring_value\x18o \x01(\x0b\x32#.android.vts.StringDataValueMessage\x12\x35\n\nenum_value\x18y \x01(\x0b\x32!.android.vts.EnumDataValueMessage\x12@\n\x0cvector_value\x18\x83\x01 \x03(\x0b\x32).android.vts.VariableSpecificationMessage\x12\x14\n\x0bvector_size\x18\x84\x01 \x01(\x05\x12@\n\x0cstruct_value\x18\x8d\x01 \x03(\x0b\x32).android.vts.VariableSpecificationMessage\x12\x14\n\x0bstruct_type\x18\x8e\x01 \x01(\x0c\x12>\n\nsub_struct\x18\x8f\x01 \x03(\x0b\x32).android.vts.VariableSpecificationMessage\x12?\n\x0bunion_value\x18\x97\x01 \x03(\x0b\x32).android.vts.VariableSpecificationMessage\x12\x13\n\nunion_type\x18\x98\x01 \x01(\x0c\x12=\n\tsub_union\x18\x99\x01 \x03(\x0b\x32).android.vts.VariableSpecificationMessage\x12=\n\tfmq_value\x18\xa1\x01 \x03(\x0b\x32).android.vts.VariableSpecificationMessage\x12=\n\tref_value\x18\xab\x01 \x01(\x0b\x32).android.vts.VariableSpecificationMessage\x12?\n\x11hidl_memory_value\x18\xac\x01 \x01(\x0b\x32#.android.vts.MemoryDataValueMessage\x12:\n\x0chandle_value\x18\xb5\x01 \x01(\x0b\x32#.android.vts.HandleDataValueMessage\x12\x18\n\x0fpredefined_type\x18\xc9\x01 \x01(\x0c\x12K\n\x10\x66unction_pointer\x18\xdd\x01 \x03(\x0b\x32\x30.android.vts.FunctionPointerSpecificationMessage\x12\x1b\n\x12hidl_callback_type\x18\xe7\x01 \x01(\x0c\x12\x1a\n\x11hidl_interface_id\x18\xf1\x01 \x01(\x05\x12\x1f\n\x16hidl_interface_pointer\x18\xf2\x01 \x01(\x04\x12\x17\n\x08is_input\x18\xad\x02 \x01(\x08:\x04true\x12\x19\n\tis_output\x18\xae\x02 \x01(\x08:\x05\x66\x61lse\x12\x18\n\x08is_const\x18\xaf\x02 \x01(\x08:\x05\x66\x61lse\x12\x1b\n\x0bis_callback\x18\xb0\x02 \x01(\x08:\x05\x66\x61lse\"\xfb\x01\n\x1aStructSpecificationMessage\x12\x0c\n\x04name\x18\x01 \x01(\x0c\x12\x19\n\nis_pointer\x18\x02 \x01(\x08:\x05\x66\x61lse\x12\x37\n\x03\x61pi\x18\xe9\x07 \x03(\x0b\x32).android.vts.FunctionSpecificationMessage\x12<\n\nsub_struct\x18\xd1\x0f \x03(\x0b\x32\'.android.vts.StructSpecificationMessage\x12=\n\tattribute\x18\xb9\x17 \x03(\x0b\x32).android.vts.VariableSpecificationMessage\"\xf6\x01\n\x1dInterfaceSpecificationMessage\x12\x1f\n\x10is_hidl_callback\x18\x65 \x01(\x08:\x05\x66\x61lse\x12\x37\n\x03\x61pi\x18\xd1\x0f \x03(\x0b\x32).android.vts.FunctionSpecificationMessage\x12=\n\tattribute\x18\xb9\x17 \x03(\x0b\x32).android.vts.VariableSpecificationMessage\x12<\n\nsub_struct\x18\xa1\x1f \x03(\x0b\x32\'.android.vts.StructSpecificationMessage\"\xca\x03\n\x1d\x43omponentSpecificationMessage\x12\x34\n\x0f\x63omponent_class\x18\x01 \x01(\x0e\x32\x1b.android.vts.ComponentClass\x12\x32\n\x0e\x63omponent_type\x18\x02 \x01(\x0e\x32\x1a.android.vts.ComponentType\x12!\n\x16\x63omponent_type_version\x18\x03 \x01(\x02:\x01\x31\x12\x16\n\x0e\x63omponent_name\x18\x04 \x01(\x0c\x12,\n\x0btarget_arch\x18\x05 \x01(\x0e\x32\x17.android.vts.TargetArch\x12\x0f\n\x07package\x18\x0b \x01(\x0c\x12\x0e\n\x06import\x18\x0c \x03(\x0c\x12%\n\x1coriginal_data_structure_name\x18\xe9\x07 \x01(\x0c\x12\x0f\n\x06header\x18\xea\x07 \x03(\x0c\x12>\n\tinterface\x18\xd1\x0f \x01(\x0b\x32*.android.vts.InterfaceSpecificationMessage\x12=\n\tattribute\x18\xb5\x10 \x03(\x0b\x32).android.vts.VariableSpecificationMessage*\xc9\x01\n\x0e\x43omponentClass\x12\x11\n\rUNKNOWN_CLASS\x10\x00\x12\x14\n\x10HAL_CONVENTIONAL\x10\x01\x12\x1e\n\x1aHAL_CONVENTIONAL_SUBMODULE\x10\x02\x12\x0e\n\nHAL_LEGACY\x10\x03\x12\x0c\n\x08HAL_HIDL\x10\x04\x12!\n\x1dHAL_HIDL_WRAPPED_CONVENTIONAL\x10\x05\x12\x0e\n\nLIB_SHARED\x10\x0b\x12\n\n\x06KERNEL\x10\x15\x12\x11\n\rKERNEL_MODULE\x10\x16*\xa8\x03\n\rComponentType\x12\x10\n\x0cUNKNOWN_TYPE\x10\x00\x12\t\n\x05\x41UDIO\x10\x01\x12\n\n\x06\x43\x41MERA\x10\x02\x12\x07\n\x03GPS\x10\x03\x12\t\n\x05LIGHT\x10\x04\x12\x08\n\x04WIFI\x10\x05\x12\n\n\x06MOBILE\x10\x06\x12\r\n\tBLUETOOTH\x10\x07\x12\x07\n\x03NFC\x10\x08\x12\t\n\x05POWER\x10\t\x12\x0c\n\x08MEMTRACK\x10\n\x12\x07\n\x03\x42\x46P\x10\x0b\x12\x0c\n\x08VIBRATOR\x10\x0c\x12\x0b\n\x07THERMAL\x10\r\x12\x0c\n\x08TV_INPUT\x10\x0e\x12\n\n\x06TV_CEC\x10\x0f\x12\x0b\n\x07SENSORS\x10\x10\x12\x0b\n\x07VEHICLE\x10\x11\x12\x06\n\x02VR\x10\x12\x12\x16\n\x12GRAPHICS_ALLOCATOR\x10\x13\x12\x13\n\x0fGRAPHICS_MAPPER\x10\x14\x12\t\n\x05RADIO\x10\x15\x12\x0e\n\nCONTEXTHUB\x10\x16\x12\x15\n\x11GRAPHICS_COMPOSER\x10\x17\x12\r\n\tMEDIA_OMX\x10\x18\x12\x10\n\x0b\x42IONIC_LIBM\x10\xe9\x07\x12\x10\n\x0b\x42IONIC_LIBC\x10\xea\x07\x12\x13\n\x0eVNDK_LIBCUTILS\x10\xcd\x08\x12\x0c\n\x07SYSCALL\x10\xd1\x0f*\x9e\x03\n\x0cVariableType\x12\x19\n\x15UNKNOWN_VARIABLE_TYPE\x10\x00\x12\x13\n\x0fTYPE_PREDEFINED\x10\x01\x12\x0f\n\x0bTYPE_SCALAR\x10\x02\x12\x0f\n\x0bTYPE_STRING\x10\x03\x12\r\n\tTYPE_ENUM\x10\x04\x12\x0e\n\nTYPE_ARRAY\x10\x05\x12\x0f\n\x0bTYPE_VECTOR\x10\x06\x12\x0f\n\x0bTYPE_STRUCT\x10\x07\x12\x19\n\x15TYPE_FUNCTION_POINTER\x10\x08\x12\r\n\tTYPE_VOID\x10\t\x12\x16\n\x12TYPE_HIDL_CALLBACK\x10\n\x12\x12\n\x0eTYPE_SUBMODULE\x10\x0b\x12\x0e\n\nTYPE_UNION\x10\x0c\x12\x17\n\x13TYPE_HIDL_INTERFACE\x10\r\x12\x0f\n\x0bTYPE_HANDLE\x10\x0e\x12\r\n\tTYPE_MASK\x10\x0f\x12\x14\n\x10TYPE_HIDL_MEMORY\x10\x10\x12\x10\n\x0cTYPE_POINTER\x10\x11\x12\x11\n\rTYPE_FMQ_SYNC\x10\x12\x12\x13\n\x0fTYPE_FMQ_UNSYNC\x10\x13\x12\x0c\n\x08TYPE_REF\x10\x14*Q\n\nTargetArch\x12\x17\n\x13UNKNOWN_TARGET_ARCH\x10\x00\x12\x13\n\x0fTARGET_ARCH_ARM\x10\x01\x12\x15\n\x11TARGET_ARCH_ARM64\x10\x02*b\n\x06\x46\x64Type\x12\r\n\tFILE_TYPE\x10\x01\x12\x0c\n\x08\x44IR_TYPE\x10\x02\x12\x0c\n\x08\x44\x45V_TYPE\x10\x03\x12\r\n\tPIPE_TYPE\x10\x04\x12\x0f\n\x0bSOCKET_TYPE\x10\x05\x12\r\n\tLINK_TYPE\x10\x06\x42\x39\n\x15\x63om.android.vts.protoB VtsComponentSpecificationMessage')
+  serialized_pb='\n#ComponentSpecificationMessage.proto\x12\x0b\x61ndroid.vts\"e\n\x1c\x43\x61llFlowSpecificationMessage\x12\x14\n\x05\x65ntry\x18\x01 \x01(\x08:\x05\x66\x61lse\x12\x13\n\x04\x65xit\x18\x02 \x01(\x08:\x05\x66\x61lse\x12\x0c\n\x04next\x18\x0b \x03(\x0c\x12\x0c\n\x04prev\x18\x0c \x03(\x0c\"C\n NativeCodeCoverageRawDataMessage\x12\x11\n\tfile_path\x18\x01 \x01(\x0c\x12\x0c\n\x04gcda\x18\x0b \x01(\x0c\"\x8d\x03\n\x13\x46unctionCallMessage\x12\x1b\n\x13hidl_interface_name\x18\x01 \x01(\x0c\x12\x19\n\rhal_driver_id\x18\x0b \x01(\x05:\x02-1\x12\x34\n\x0f\x63omponent_class\x18\x15 \x01(\x0e\x32\x1b.android.vts.ComponentClass\x12\x32\n\x0e\x63omponent_type\x18\x16 \x01(\x0e\x32\x1a.android.vts.ComponentType\x12\"\n\x16\x63omponent_type_version\x18\x17 \x01(\x0c\x42\x02\x18\x01\x12\x16\n\x0e\x63omponent_name\x18\x18 \x01(\x0c\x12\x14\n\x0cpackage_name\x18\x19 \x01(\x0c\x12$\n\x1c\x63omponent_type_version_major\x18\x1a \x01(\r\x12$\n\x1c\x63omponent_type_version_minor\x18\x1b \x01(\r\x12\x36\n\x03\x61pi\x18\x64 \x01(\x0b\x32).android.vts.FunctionSpecificationMessage\"\xde\x05\n\x1c\x46unctionSpecificationMessage\x12\x0c\n\x04name\x18\x01 \x01(\x0c\x12\x16\n\x0esubmodule_name\x18\x02 \x01(\x0c\x12\x19\n\x11hidl_interface_id\x18\x03 \x01(\x05\x12>\n\x0breturn_type\x18\x0b \x01(\x0b\x32).android.vts.VariableSpecificationMessage\x12\x43\n\x10return_type_hidl\x18\x0c \x03(\x0b\x32).android.vts.VariableSpecificationMessage\x12N\n\x1areturn_type_submodule_spec\x18\r \x01(\x0b\x32*.android.vts.ComponentSpecificationMessage\x12\x36\n\x03\x61rg\x18\x15 \x03(\x0b\x32).android.vts.VariableSpecificationMessage\x12;\n\x08\x63\x61llflow\x18\x1f \x03(\x0b\x32).android.vts.CallFlowSpecificationMessage\x12\x1a\n\x0b\x64o_not_fuzz\x18  \x01(\x08:\x05\x66\x61lse\x12\x17\n\x0bis_callback\x18) \x01(\x08\x42\x02\x18\x01\x12J\n\x10\x66unction_pointer\x18* \x01(\x0b\x32\x30.android.vts.FunctionPointerSpecificationMessage\x12\x16\n\x0eprofiling_data\x18\x65 \x03(\x02\x12 \n\x17processed_coverage_data\x18\xc9\x01 \x03(\r\x12I\n\x11raw_coverage_data\x18\xca\x01 \x03(\x0b\x32-.android.vts.NativeCodeCoverageRawDataMessage\x12\x14\n\x0bparent_path\x18\xad\x02 \x01(\x0c\x12\x17\n\x0esyscall_number\x18\x91\x03 \x01(\r\"\xf5\x02\n\x16ScalarDataValueMessage\x12\x0e\n\x06\x62ool_t\x18\x01 \x01(\x08\x12\x0e\n\x06int8_t\x18\x0b \x01(\x05\x12\x0f\n\x07uint8_t\x18\x0c \x01(\r\x12\x0c\n\x04\x63har\x18\r \x01(\x05\x12\r\n\x05uchar\x18\x0e \x01(\r\x12\x0f\n\x07int16_t\x18\x15 \x01(\x05\x12\x10\n\x08uint16_t\x18\x16 \x01(\r\x12\x0f\n\x07int32_t\x18\x1f \x01(\x05\x12\x10\n\x08uint32_t\x18  \x01(\r\x12\x0f\n\x07int64_t\x18) \x01(\x03\x12\x10\n\x08uint64_t\x18* \x01(\x04\x12\x0f\n\x07\x66loat_t\x18\x65 \x01(\x02\x12\x10\n\x08\x64ouble_t\x18\x66 \x01(\x01\x12\x10\n\x07pointer\x18\xc9\x01 \x01(\r\x12\x0f\n\x06opaque\x18\xca\x01 \x01(\r\x12\x15\n\x0cvoid_pointer\x18\xd3\x01 \x01(\r\x12\x15\n\x0c\x63har_pointer\x18\xd4\x01 \x01(\r\x12\x16\n\ruchar_pointer\x18\xd5\x01 \x01(\r\x12\x18\n\x0fpointer_pointer\x18\xfb\x01 \x01(\r\"\xd1\x01\n#FunctionPointerSpecificationMessage\x12\x15\n\rfunction_name\x18\x01 \x01(\x0c\x12\x0f\n\x07\x61\x64\x64ress\x18\x0b \x01(\r\x12\n\n\x02id\x18\x15 \x01(\x0c\x12\x36\n\x03\x61rg\x18\x65 \x03(\x0b\x32).android.vts.VariableSpecificationMessage\x12>\n\x0breturn_type\x18o \x01(\x0b\x32).android.vts.VariableSpecificationMessage\"9\n\x16StringDataValueMessage\x12\x0f\n\x07message\x18\x01 \x01(\x0c\x12\x0e\n\x06length\x18\x0b \x01(\r\"z\n\x14\x45numDataValueMessage\x12\x12\n\nenumerator\x18\x01 \x03(\x0c\x12\x39\n\x0cscalar_value\x18\x02 \x03(\x0b\x32#.android.vts.ScalarDataValueMessage\x12\x13\n\x0bscalar_type\x18\x03 \x01(\x0c\"8\n\x16MemoryDataValueMessage\x12\x0c\n\x04size\x18\x01 \x01(\x03\x12\x10\n\x08\x63ontents\x18\x02 \x01(\x0c\"\x93\x01\n\tFdMessage\x12!\n\x04type\x18\x01 \x01(\x0e\x32\x13.android.vts.FdType\x12\x0c\n\x04mode\x18\x02 \x01(\r\x12\r\n\x05\x66lags\x18\x03 \x01(\x05\x12\x11\n\tfile_name\x18\x04 \x01(\x0c\x12\x33\n\x06memory\x18\x06 \x01(\x0b\x32#.android.vts.MemoryDataValueMessage\"\x85\x01\n\x16HandleDataValueMessage\x12\x0f\n\x07version\x18\x01 \x01(\x05\x12\x0f\n\x07num_fds\x18\x02 \x01(\x05\x12\x10\n\x08num_ints\x18\x03 \x01(\x05\x12&\n\x06\x66\x64_val\x18\x04 \x03(\x0b\x32\x16.android.vts.FdMessage\x12\x0f\n\x07int_val\x18\x05 \x03(\x05\"\xc3\t\n\x1cVariableSpecificationMessage\x12\x0c\n\x04name\x18\x01 \x01(\x0c\x12\'\n\x04type\x18\x02 \x01(\x0e\x32\x19.android.vts.VariableType\x12\x39\n\x0cscalar_value\x18\x65 \x01(\x0b\x32#.android.vts.ScalarDataValueMessage\x12\x13\n\x0bscalar_type\x18\x66 \x01(\x0c\x12\x39\n\x0cstring_value\x18o \x01(\x0b\x32#.android.vts.StringDataValueMessage\x12\x35\n\nenum_value\x18y \x01(\x0b\x32!.android.vts.EnumDataValueMessage\x12@\n\x0cvector_value\x18\x83\x01 \x03(\x0b\x32).android.vts.VariableSpecificationMessage\x12\x14\n\x0bvector_size\x18\x84\x01 \x01(\x05\x12@\n\x0cstruct_value\x18\x8d\x01 \x03(\x0b\x32).android.vts.VariableSpecificationMessage\x12\x14\n\x0bstruct_type\x18\x8e\x01 \x01(\x0c\x12>\n\nsub_struct\x18\x8f\x01 \x03(\x0b\x32).android.vts.VariableSpecificationMessage\x12?\n\x0bunion_value\x18\x97\x01 \x03(\x0b\x32).android.vts.VariableSpecificationMessage\x12\x13\n\nunion_type\x18\x98\x01 \x01(\x0c\x12=\n\tsub_union\x18\x99\x01 \x03(\x0b\x32).android.vts.VariableSpecificationMessage\x12=\n\tfmq_value\x18\xa1\x01 \x03(\x0b\x32).android.vts.VariableSpecificationMessage\x12=\n\tref_value\x18\xab\x01 \x01(\x0b\x32).android.vts.VariableSpecificationMessage\x12?\n\x11hidl_memory_value\x18\xac\x01 \x01(\x0b\x32#.android.vts.MemoryDataValueMessage\x12:\n\x0chandle_value\x18\xb5\x01 \x01(\x0b\x32#.android.vts.HandleDataValueMessage\x12\x18\n\x0fpredefined_type\x18\xc9\x01 \x01(\x0c\x12K\n\x10\x66unction_pointer\x18\xdd\x01 \x03(\x0b\x32\x30.android.vts.FunctionPointerSpecificationMessage\x12\x1b\n\x12hidl_callback_type\x18\xe7\x01 \x01(\x0c\x12\x1a\n\x11hidl_interface_id\x18\xf1\x01 \x01(\x05\x12\x1f\n\x16hidl_interface_pointer\x18\xf2\x01 \x01(\x04\x12\x17\n\x08is_input\x18\xad\x02 \x01(\x08:\x04true\x12\x19\n\tis_output\x18\xae\x02 \x01(\x08:\x05\x66\x61lse\x12\x18\n\x08is_const\x18\xaf\x02 \x01(\x08:\x05\x66\x61lse\x12\x1b\n\x0bis_callback\x18\xb0\x02 \x01(\x08:\x05\x66\x61lse\"\xfb\x01\n\x1aStructSpecificationMessage\x12\x0c\n\x04name\x18\x01 \x01(\x0c\x12\x19\n\nis_pointer\x18\x02 \x01(\x08:\x05\x66\x61lse\x12\x37\n\x03\x61pi\x18\xe9\x07 \x03(\x0b\x32).android.vts.FunctionSpecificationMessage\x12<\n\nsub_struct\x18\xd1\x0f \x03(\x0b\x32\'.android.vts.StructSpecificationMessage\x12=\n\tattribute\x18\xb9\x17 \x03(\x0b\x32).android.vts.VariableSpecificationMessage\"\xf6\x01\n\x1dInterfaceSpecificationMessage\x12\x1f\n\x10is_hidl_callback\x18\x65 \x01(\x08:\x05\x66\x61lse\x12\x37\n\x03\x61pi\x18\xd1\x0f \x03(\x0b\x32).android.vts.FunctionSpecificationMessage\x12=\n\tattribute\x18\xb9\x17 \x03(\x0b\x32).android.vts.VariableSpecificationMessage\x12<\n\nsub_struct\x18\xa1\x1f \x03(\x0b\x32\'.android.vts.StructSpecificationMessage\"\xca\x03\n\x1d\x43omponentSpecificationMessage\x12\x34\n\x0f\x63omponent_class\x18\x01 \x01(\x0e\x32\x1b.android.vts.ComponentClass\x12\x32\n\x0e\x63omponent_type\x18\x02 \x01(\x0e\x32\x1a.android.vts.ComponentType\x12!\n\x16\x63omponent_type_version\x18\x03 \x01(\x02:\x01\x31\x12\x16\n\x0e\x63omponent_name\x18\x04 \x01(\x0c\x12,\n\x0btarget_arch\x18\x05 \x01(\x0e\x32\x17.android.vts.TargetArch\x12\x0f\n\x07package\x18\x0b \x01(\x0c\x12\x0e\n\x06import\x18\x0c \x03(\x0c\x12%\n\x1coriginal_data_structure_name\x18\xe9\x07 \x01(\x0c\x12\x0f\n\x06header\x18\xea\x07 \x03(\x0c\x12>\n\tinterface\x18\xd1\x0f \x01(\x0b\x32*.android.vts.InterfaceSpecificationMessage\x12=\n\tattribute\x18\xb5\x10 \x03(\x0b\x32).android.vts.VariableSpecificationMessage*\xc9\x01\n\x0e\x43omponentClass\x12\x11\n\rUNKNOWN_CLASS\x10\x00\x12\x14\n\x10HAL_CONVENTIONAL\x10\x01\x12\x1e\n\x1aHAL_CONVENTIONAL_SUBMODULE\x10\x02\x12\x0e\n\nHAL_LEGACY\x10\x03\x12\x0c\n\x08HAL_HIDL\x10\x04\x12!\n\x1dHAL_HIDL_WRAPPED_CONVENTIONAL\x10\x05\x12\x0e\n\nLIB_SHARED\x10\x0b\x12\n\n\x06KERNEL\x10\x15\x12\x11\n\rKERNEL_MODULE\x10\x16*\xa8\x03\n\rComponentType\x12\x10\n\x0cUNKNOWN_TYPE\x10\x00\x12\t\n\x05\x41UDIO\x10\x01\x12\n\n\x06\x43\x41MERA\x10\x02\x12\x07\n\x03GPS\x10\x03\x12\t\n\x05LIGHT\x10\x04\x12\x08\n\x04WIFI\x10\x05\x12\n\n\x06MOBILE\x10\x06\x12\r\n\tBLUETOOTH\x10\x07\x12\x07\n\x03NFC\x10\x08\x12\t\n\x05POWER\x10\t\x12\x0c\n\x08MEMTRACK\x10\n\x12\x07\n\x03\x42\x46P\x10\x0b\x12\x0c\n\x08VIBRATOR\x10\x0c\x12\x0b\n\x07THERMAL\x10\r\x12\x0c\n\x08TV_INPUT\x10\x0e\x12\n\n\x06TV_CEC\x10\x0f\x12\x0b\n\x07SENSORS\x10\x10\x12\x0b\n\x07VEHICLE\x10\x11\x12\x06\n\x02VR\x10\x12\x12\x16\n\x12GRAPHICS_ALLOCATOR\x10\x13\x12\x13\n\x0fGRAPHICS_MAPPER\x10\x14\x12\t\n\x05RADIO\x10\x15\x12\x0e\n\nCONTEXTHUB\x10\x16\x12\x15\n\x11GRAPHICS_COMPOSER\x10\x17\x12\r\n\tMEDIA_OMX\x10\x18\x12\x10\n\x0b\x42IONIC_LIBM\x10\xe9\x07\x12\x10\n\x0b\x42IONIC_LIBC\x10\xea\x07\x12\x13\n\x0eVNDK_LIBCUTILS\x10\xcd\x08\x12\x0c\n\x07SYSCALL\x10\xd1\x0f*\x9e\x03\n\x0cVariableType\x12\x19\n\x15UNKNOWN_VARIABLE_TYPE\x10\x00\x12\x13\n\x0fTYPE_PREDEFINED\x10\x01\x12\x0f\n\x0bTYPE_SCALAR\x10\x02\x12\x0f\n\x0bTYPE_STRING\x10\x03\x12\r\n\tTYPE_ENUM\x10\x04\x12\x0e\n\nTYPE_ARRAY\x10\x05\x12\x0f\n\x0bTYPE_VECTOR\x10\x06\x12\x0f\n\x0bTYPE_STRUCT\x10\x07\x12\x19\n\x15TYPE_FUNCTION_POINTER\x10\x08\x12\r\n\tTYPE_VOID\x10\t\x12\x16\n\x12TYPE_HIDL_CALLBACK\x10\n\x12\x12\n\x0eTYPE_SUBMODULE\x10\x0b\x12\x0e\n\nTYPE_UNION\x10\x0c\x12\x17\n\x13TYPE_HIDL_INTERFACE\x10\r\x12\x0f\n\x0bTYPE_HANDLE\x10\x0e\x12\r\n\tTYPE_MASK\x10\x0f\x12\x14\n\x10TYPE_HIDL_MEMORY\x10\x10\x12\x10\n\x0cTYPE_POINTER\x10\x11\x12\x11\n\rTYPE_FMQ_SYNC\x10\x12\x12\x13\n\x0fTYPE_FMQ_UNSYNC\x10\x13\x12\x0c\n\x08TYPE_REF\x10\x14*Q\n\nTargetArch\x12\x17\n\x13UNKNOWN_TARGET_ARCH\x10\x00\x12\x13\n\x0fTARGET_ARCH_ARM\x10\x01\x12\x15\n\x11TARGET_ARCH_ARM64\x10\x02*b\n\x06\x46\x64Type\x12\r\n\tFILE_TYPE\x10\x01\x12\x0c\n\x08\x44IR_TYPE\x10\x02\x12\x0c\n\x08\x44\x45V_TYPE\x10\x03\x12\r\n\tPIPE_TYPE\x10\x04\x12\x0f\n\x0bSOCKET_TYPE\x10\x05\x12\r\n\tLINK_TYPE\x10\x06\x42\x39\n\x15\x63om.android.vts.protoB VtsComponentSpecificationMessage')
 
 _COMPONENTCLASS = _descriptor.EnumDescriptor(
   name='ComponentClass',
@@ -61,8 +61,8 @@
   ],
   containing_type=None,
   options=None,
-  serialized_start=4583,
-  serialized_end=4784,
+  serialized_start=4663,
+  serialized_end=4864,
 )
 
 ComponentClass = enum_type_wrapper.EnumTypeWrapper(_COMPONENTCLASS)
@@ -191,8 +191,8 @@
   ],
   containing_type=None,
   options=None,
-  serialized_start=4787,
-  serialized_end=5211,
+  serialized_start=4867,
+  serialized_end=5291,
 )
 
 ComponentType = enum_type_wrapper.EnumTypeWrapper(_COMPONENTTYPE)
@@ -289,8 +289,8 @@
   ],
   containing_type=None,
   options=None,
-  serialized_start=5214,
-  serialized_end=5628,
+  serialized_start=5294,
+  serialized_end=5708,
 )
 
 VariableType = enum_type_wrapper.EnumTypeWrapper(_VARIABLETYPE)
@@ -315,8 +315,8 @@
   ],
   containing_type=None,
   options=None,
-  serialized_start=5630,
-  serialized_end=5711,
+  serialized_start=5710,
+  serialized_end=5791,
 )
 
 TargetArch = enum_type_wrapper.EnumTypeWrapper(_TARGETARCH)
@@ -353,8 +353,8 @@
   ],
   containing_type=None,
   options=None,
-  serialized_start=5713,
-  serialized_end=5811,
+  serialized_start=5793,
+  serialized_end=5891,
 )
 
 FdType = enum_type_wrapper.EnumTypeWrapper(_FDTYPE)
@@ -554,7 +554,7 @@
       has_default_value=False, default_value="",
       message_type=None, enum_type=None, containing_type=None,
       is_extension=False, extension_scope=None,
-      options=None),
+      options=_descriptor._ParseOptions(descriptor_pb2.FieldOptions(), '\030\001')),
     _descriptor.FieldDescriptor(
       name='component_name', full_name='android.vts.FunctionCallMessage.component_name', index=5,
       number=24, type=12, cpp_type=9, label=1,
@@ -570,7 +570,21 @@
       is_extension=False, extension_scope=None,
       options=None),
     _descriptor.FieldDescriptor(
-      name='api', full_name='android.vts.FunctionCallMessage.api', index=7,
+      name='component_type_version_major', full_name='android.vts.FunctionCallMessage.component_type_version_major', index=7,
+      number=26, type=13, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+    _descriptor.FieldDescriptor(
+      name='component_type_version_minor', full_name='android.vts.FunctionCallMessage.component_type_version_minor', index=8,
+      number=27, type=13, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+    _descriptor.FieldDescriptor(
+      name='api', full_name='android.vts.FunctionCallMessage.api', index=9,
       number=100, type=11, cpp_type=10, label=1,
       has_default_value=False, default_value=None,
       message_type=None, enum_type=None, containing_type=None,
@@ -586,7 +600,7 @@
   is_extendable=False,
   extension_ranges=[],
   serialized_start=225,
-  serialized_end=542,
+  serialized_end=622,
 )
 
 
@@ -718,8 +732,8 @@
   options=None,
   is_extendable=False,
   extension_ranges=[],
-  serialized_start=545,
-  serialized_end=1279,
+  serialized_start=625,
+  serialized_end=1359,
 )
 
 
@@ -872,8 +886,8 @@
   options=None,
   is_extendable=False,
   extension_ranges=[],
-  serialized_start=1282,
-  serialized_end=1655,
+  serialized_start=1362,
+  serialized_end=1735,
 )
 
 
@@ -928,8 +942,8 @@
   options=None,
   is_extendable=False,
   extension_ranges=[],
-  serialized_start=1658,
-  serialized_end=1867,
+  serialized_start=1738,
+  serialized_end=1947,
 )
 
 
@@ -963,8 +977,8 @@
   options=None,
   is_extendable=False,
   extension_ranges=[],
-  serialized_start=1869,
-  serialized_end=1926,
+  serialized_start=1949,
+  serialized_end=2006,
 )
 
 
@@ -1005,8 +1019,8 @@
   options=None,
   is_extendable=False,
   extension_ranges=[],
-  serialized_start=1928,
-  serialized_end=2050,
+  serialized_start=2008,
+  serialized_end=2130,
 )
 
 
@@ -1040,8 +1054,8 @@
   options=None,
   is_extendable=False,
   extension_ranges=[],
-  serialized_start=2052,
-  serialized_end=2108,
+  serialized_start=2132,
+  serialized_end=2188,
 )
 
 
@@ -1096,8 +1110,8 @@
   options=None,
   is_extendable=False,
   extension_ranges=[],
-  serialized_start=2111,
-  serialized_end=2258,
+  serialized_start=2191,
+  serialized_end=2338,
 )
 
 
@@ -1152,8 +1166,8 @@
   options=None,
   is_extendable=False,
   extension_ranges=[],
-  serialized_start=2261,
-  serialized_end=2394,
+  serialized_start=2341,
+  serialized_end=2474,
 )
 
 
@@ -1362,8 +1376,8 @@
   options=None,
   is_extendable=False,
   extension_ranges=[],
-  serialized_start=2397,
-  serialized_end=3616,
+  serialized_start=2477,
+  serialized_end=3696,
 )
 
 
@@ -1418,8 +1432,8 @@
   options=None,
   is_extendable=False,
   extension_ranges=[],
-  serialized_start=3619,
-  serialized_end=3870,
+  serialized_start=3699,
+  serialized_end=3950,
 )
 
 
@@ -1467,8 +1481,8 @@
   options=None,
   is_extendable=False,
   extension_ranges=[],
-  serialized_start=3873,
-  serialized_end=4119,
+  serialized_start=3953,
+  serialized_end=4199,
 )
 
 
@@ -1565,8 +1579,8 @@
   options=None,
   is_extendable=False,
   extension_ranges=[],
-  serialized_start=4122,
-  serialized_end=4580,
+  serialized_start=4202,
+  serialized_end=4660,
 )
 
 _FUNCTIONCALLMESSAGE.fields_by_name['component_class'].enum_type = _COMPONENTCLASS
@@ -1719,6 +1733,8 @@
 
 DESCRIPTOR.has_options = True
 DESCRIPTOR._options = _descriptor._ParseOptions(descriptor_pb2.FileOptions(), '\n\025com.android.vts.protoB VtsComponentSpecificationMessage')
+_FUNCTIONCALLMESSAGE.fields_by_name['component_type_version'].has_options = True
+_FUNCTIONCALLMESSAGE.fields_by_name['component_type_version']._options = _descriptor._ParseOptions(descriptor_pb2.FieldOptions(), '\030\001')
 _FUNCTIONSPECIFICATIONMESSAGE.fields_by_name['is_callback'].has_options = True
 _FUNCTIONSPECIFICATIONMESSAGE.fields_by_name['is_callback']._options = _descriptor._ParseOptions(descriptor_pb2.FieldOptions(), '\030\001')
 # @@protoc_insertion_point(module_scope)
diff --git a/proto/VtsReportMessage.proto b/proto/VtsReportMessage.proto
index 3e83150..1137729 100644
--- a/proto/VtsReportMessage.proto
+++ b/proto/VtsReportMessage.proto
@@ -195,7 +195,7 @@
   optional bytes revision = 13;
 
   // i-th element gives the number of times i-th line is executed.
-  repeated int32 line_coverage_vector = 23;
+  repeated int64 line_coverage_vector = 23;
 
   // the number of source code lines that are instrumented for code coverage
   // measurement.
diff --git a/proto/VtsReportMessage_pb2.py b/proto/VtsReportMessage_pb2.py
index cbfdb98..408adb1 100644
--- a/proto/VtsReportMessage_pb2.py
+++ b/proto/VtsReportMessage_pb2.py
@@ -14,7 +14,7 @@
 DESCRIPTOR = _descriptor.FileDescriptor(
   name='VtsReportMessage.proto',
   package='android.vts',
-  serialized_pb='\n\x16VtsReportMessage.proto\x12\x0b\x61ndroid.vts\"\xe0\x01\n\x18\x41ndroidDeviceInfoMessage\x12\x14\n\x0cproduct_type\x18\x01 \x01(\x0c\x12\x17\n\x0fproduct_variant\x18\x02 \x01(\x0c\x12\x14\n\x0c\x62uild_flavor\x18\x0b \x01(\x0c\x12\x10\n\x08\x62uild_id\x18\x0c \x01(\x0c\x12\x0e\n\x06\x62ranch\x18\x15 \x01(\x0c\x12\x13\n\x0b\x62uild_alias\x18\x16 \x01(\x0c\x12\x11\n\tapi_level\x18\x1f \x01(\x0c\x12\x10\n\x08\x61\x62i_name\x18\x33 \x01(\x0c\x12\x13\n\x0b\x61\x62i_bitness\x18\x34 \x01(\x0c\x12\x0e\n\x06serial\x18\x65 \x01(\x0c\"g\n\x10\x41ndroidBuildInfo\x12\n\n\x02id\x18\x01 \x01(\x0c\x12\x0c\n\x04name\x18\x0b \x01(\x0c\x12\x12\n\nbuild_type\x18\x0c \x01(\x0c\x12\x0e\n\x06\x62ranch\x18\r \x01(\x0c\x12\x15\n\rbuild_summary\x18\x15 \x01(\x0c\"\x1f\n\x0bVtsHostInfo\x12\x10\n\x08hostname\x18\x01 \x01(\x0c\"\xd5\x02\n\x15TestCaseReportMessage\x12\x0c\n\x04name\x18\x01 \x01(\x0c\x12\x30\n\x0btest_result\x18\x0b \x01(\x0e\x32\x1b.android.vts.TestCaseResult\x12\x17\n\x0fstart_timestamp\x18\x15 \x01(\x03\x12\x15\n\rend_timestamp\x18\x16 \x01(\x03\x12\x34\n\x08\x63overage\x18\x1f \x03(\x0b\x32\".android.vts.CoverageReportMessage\x12\x36\n\tprofiling\x18) \x03(\x0b\x32#.android.vts.ProfilingReportMessage\x12\x38\n\x08systrace\x18* \x03(\x0b\x32\".android.vts.SystraceReportMessageB\x02\x18\x01\x12$\n\x03log\x18\x65 \x03(\x0b\x32\x17.android.vts.LogMessage\"\xa0\x02\n\x16ProfilingReportMessage\x12\x0c\n\x04name\x18\x01 \x01(\x0c\x12+\n\x04type\x18\x02 \x01(\x0e\x32\x1d.android.vts.VtsProfilingType\x12@\n\x0fregression_mode\x18\x03 \x01(\x0e\x32\'.android.vts.VtsProfilingRegressionMode\x12\x17\n\x0fstart_timestamp\x18\x0b \x01(\x03\x12\x15\n\rend_timestamp\x18\x0c \x01(\x03\x12\r\n\x05label\x18\x15 \x03(\x0c\x12\r\n\x05value\x18\x16 \x03(\x03\x12\x14\n\x0cx_axis_label\x18\x1f \x01(\x0c\x12\x14\n\x0cy_axis_label\x18  \x01(\x0c\x12\x0f\n\x07options\x18) \x03(\x0c\"H\n\x15SystraceReportMessage\x12\x14\n\x0cprocess_name\x18\x01 \x01(\x0c\x12\x0c\n\x04html\x18\x0b \x03(\x0c\x12\x0b\n\x03url\x18\x15 \x03(\x0c\"\xe5\x01\n\x15\x43overageReportMessage\x12\x11\n\tfile_path\x18\x0b \x01(\x0c\x12\x14\n\x0cproject_name\x18\x0c \x01(\x0c\x12\x10\n\x08revision\x18\r \x01(\x0c\x12\x1c\n\x14line_coverage_vector\x18\x17 \x03(\x05\x12\x18\n\x10total_line_count\x18\x65 \x01(\x05\x12\x1a\n\x12\x63overed_line_count\x18\x66 \x01(\x05\x12\x14\n\x08\x64ir_path\x18\x01 \x01(\x0c\x42\x02\x18\x01\x12\x15\n\tfile_name\x18\x02 \x01(\x0c\x42\x02\x18\x01\x12\x10\n\x04html\x18\x03 \x01(\x0c\x42\x02\x18\x01\"8\n\nLogMessage\x12\x0b\n\x03url\x18\x01 \x01(\x0c\x12\x0c\n\x04name\x18\x02 \x01(\x0c\x12\x0f\n\x07\x63ontent\x18\x03 \x01(\x0c\"@\n\x12UrlResourceMessage\x12\x0b\n\x03url\x18\x01 \x01(\x0c\x12\x0c\n\x04name\x18\x02 \x01(\x0c\x12\x0f\n\x07\x63ontent\x18\x03 \x01(\x0c\"\x8b\x05\n\x11TestReportMessage\x12\x16\n\ntest_suite\x18\x01 \x01(\x0c\x42\x02\x18\x01\x12\x0c\n\x04test\x18\x02 \x01(\x0c\x12+\n\ttest_type\x18\x03 \x01(\x0e\x32\x18.android.vts.VtsTestType\x12:\n\x0b\x64\x65vice_info\x18\x04 \x03(\x0b\x32%.android.vts.AndroidDeviceInfoMessage\x12\x31\n\nbuild_info\x18\x05 \x01(\x0b\x32\x1d.android.vts.AndroidBuildInfo\x12\x18\n\x10subscriber_email\x18\x06 \x03(\x0c\x12+\n\thost_info\x18\x07 \x01(\x0b\x32\x18.android.vts.VtsHostInfo\x12\x35\n\ttest_case\x18\x0b \x03(\x0b\x32\".android.vts.TestCaseReportMessage\x12\x36\n\tprofiling\x18\x15 \x03(\x0b\x32#.android.vts.ProfilingReportMessage\x12\x38\n\x08systrace\x18\x16 \x03(\x0b\x32\".android.vts.SystraceReportMessageB\x02\x18\x01\x12\x17\n\x0fstart_timestamp\x18\x65 \x01(\x03\x12\x15\n\rend_timestamp\x18\x66 \x01(\x03\x12\x34\n\x08\x63overage\x18g \x03(\x0b\x32\".android.vts.CoverageReportMessage\x12%\n\x03log\x18\xe9\x07 \x03(\x0b\x32\x17.android.vts.LogMessage\x12\x37\n\rlink_resource\x18\xf3\x07 \x03(\x0b\x32\x1f.android.vts.UrlResourceMessage\"\xa7\x01\n\x15TestPlanReportMessage\x12\x18\n\x10test_module_name\x18\x0b \x03(\t\x12#\n\x1btest_module_start_timestamp\x18\x0c \x03(\x03\x12\x16\n\x0etest_plan_name\x18\x15 \x01(\t\x12\x37\n\x0epartner_report\x18\x1f \x03(\x0b\x32\x1f.android.vts.UrlResourceMessage\"\x9f\x01\n\x14\x44\x61shboardPostMessage\x12\x14\n\x0c\x61\x63\x63\x65ss_token\x18\x01 \x01(\t\x12\x33\n\x0btest_report\x18\x02 \x03(\x0b\x32\x1e.android.vts.TestReportMessage\x12<\n\x10test_plan_report\x18\x03 \x03(\x0b\x32\".android.vts.TestPlanReportMessage*\xb3\x01\n\x0eTestCaseResult\x12\x12\n\x0eUNKNOWN_RESULT\x10\x00\x12\x19\n\x15TEST_CASE_RESULT_PASS\x10\x01\x12\x19\n\x15TEST_CASE_RESULT_FAIL\x10\x02\x12\x19\n\x15TEST_CASE_RESULT_SKIP\x10\x03\x12\x1e\n\x1aTEST_CASE_RESULT_EXCEPTION\x10\x04\x12\x1c\n\x18TEST_CASE_RESULT_TIMEOUT\x10\x05*\x9c\x01\n\x0bVtsTestType\x12\x18\n\x14UNKNOWN_VTS_TESTTYPE\x10\x00\x12\x1e\n\x1aVTS_HOST_DRIVEN_STRUCTURAL\x10\x01\x12\x1b\n\x17VTS_HOST_DRIVEN_FUZZING\x10\x02\x12\x19\n\x15VTS_TARGET_SIDE_GTEST\x10\x03\x12\x1b\n\x17VTS_TARGET_SIDE_FUZZING\x10\x04*\xa3\x01\n\x1aVtsProfilingRegressionMode\x12\x1b\n\x17UNKNOWN_REGRESSION_MODE\x10\x00\x12 \n\x1cVTS_REGRESSION_MODE_DISABLED\x10\x01\x12\"\n\x1eVTS_REGRESSION_MODE_INCREASING\x10\x02\x12\"\n\x1eVTS_REGRESSION_MODE_DECREASING\x10\x03*\xa4\x01\n\x10VtsProfilingType\x12\x1e\n\x1aUNKNOWN_VTS_PROFILING_TYPE\x10\x00\x12 \n\x1cVTS_PROFILING_TYPE_TIMESTAMP\x10\x01\x12%\n!VTS_PROFILING_TYPE_LABELED_VECTOR\x10\x02\x12\'\n#VTS_PROFILING_TYPE_UNLABELED_VECTOR\x10\x03\x42+\n\x15\x63om.android.vts.protoB\x10VtsReportMessageP\x00')
+  serialized_pb='\n\x16VtsReportMessage.proto\x12\x0b\x61ndroid.vts\"\xe0\x01\n\x18\x41ndroidDeviceInfoMessage\x12\x14\n\x0cproduct_type\x18\x01 \x01(\x0c\x12\x17\n\x0fproduct_variant\x18\x02 \x01(\x0c\x12\x14\n\x0c\x62uild_flavor\x18\x0b \x01(\x0c\x12\x10\n\x08\x62uild_id\x18\x0c \x01(\x0c\x12\x0e\n\x06\x62ranch\x18\x15 \x01(\x0c\x12\x13\n\x0b\x62uild_alias\x18\x16 \x01(\x0c\x12\x11\n\tapi_level\x18\x1f \x01(\x0c\x12\x10\n\x08\x61\x62i_name\x18\x33 \x01(\x0c\x12\x13\n\x0b\x61\x62i_bitness\x18\x34 \x01(\x0c\x12\x0e\n\x06serial\x18\x65 \x01(\x0c\"g\n\x10\x41ndroidBuildInfo\x12\n\n\x02id\x18\x01 \x01(\x0c\x12\x0c\n\x04name\x18\x0b \x01(\x0c\x12\x12\n\nbuild_type\x18\x0c \x01(\x0c\x12\x0e\n\x06\x62ranch\x18\r \x01(\x0c\x12\x15\n\rbuild_summary\x18\x15 \x01(\x0c\"\x1f\n\x0bVtsHostInfo\x12\x10\n\x08hostname\x18\x01 \x01(\x0c\"\xd5\x02\n\x15TestCaseReportMessage\x12\x0c\n\x04name\x18\x01 \x01(\x0c\x12\x30\n\x0btest_result\x18\x0b \x01(\x0e\x32\x1b.android.vts.TestCaseResult\x12\x17\n\x0fstart_timestamp\x18\x15 \x01(\x03\x12\x15\n\rend_timestamp\x18\x16 \x01(\x03\x12\x34\n\x08\x63overage\x18\x1f \x03(\x0b\x32\".android.vts.CoverageReportMessage\x12\x36\n\tprofiling\x18) \x03(\x0b\x32#.android.vts.ProfilingReportMessage\x12\x38\n\x08systrace\x18* \x03(\x0b\x32\".android.vts.SystraceReportMessageB\x02\x18\x01\x12$\n\x03log\x18\x65 \x03(\x0b\x32\x17.android.vts.LogMessage\"\xa0\x02\n\x16ProfilingReportMessage\x12\x0c\n\x04name\x18\x01 \x01(\x0c\x12+\n\x04type\x18\x02 \x01(\x0e\x32\x1d.android.vts.VtsProfilingType\x12@\n\x0fregression_mode\x18\x03 \x01(\x0e\x32\'.android.vts.VtsProfilingRegressionMode\x12\x17\n\x0fstart_timestamp\x18\x0b \x01(\x03\x12\x15\n\rend_timestamp\x18\x0c \x01(\x03\x12\r\n\x05label\x18\x15 \x03(\x0c\x12\r\n\x05value\x18\x16 \x03(\x03\x12\x14\n\x0cx_axis_label\x18\x1f \x01(\x0c\x12\x14\n\x0cy_axis_label\x18  \x01(\x0c\x12\x0f\n\x07options\x18) \x03(\x0c\"H\n\x15SystraceReportMessage\x12\x14\n\x0cprocess_name\x18\x01 \x01(\x0c\x12\x0c\n\x04html\x18\x0b \x03(\x0c\x12\x0b\n\x03url\x18\x15 \x03(\x0c\"\xe5\x01\n\x15\x43overageReportMessage\x12\x11\n\tfile_path\x18\x0b \x01(\x0c\x12\x14\n\x0cproject_name\x18\x0c \x01(\x0c\x12\x10\n\x08revision\x18\r \x01(\x0c\x12\x1c\n\x14line_coverage_vector\x18\x17 \x03(\x03\x12\x18\n\x10total_line_count\x18\x65 \x01(\x05\x12\x1a\n\x12\x63overed_line_count\x18\x66 \x01(\x05\x12\x14\n\x08\x64ir_path\x18\x01 \x01(\x0c\x42\x02\x18\x01\x12\x15\n\tfile_name\x18\x02 \x01(\x0c\x42\x02\x18\x01\x12\x10\n\x04html\x18\x03 \x01(\x0c\x42\x02\x18\x01\"8\n\nLogMessage\x12\x0b\n\x03url\x18\x01 \x01(\x0c\x12\x0c\n\x04name\x18\x02 \x01(\x0c\x12\x0f\n\x07\x63ontent\x18\x03 \x01(\x0c\"@\n\x12UrlResourceMessage\x12\x0b\n\x03url\x18\x01 \x01(\x0c\x12\x0c\n\x04name\x18\x02 \x01(\x0c\x12\x0f\n\x07\x63ontent\x18\x03 \x01(\x0c\"\x8b\x05\n\x11TestReportMessage\x12\x16\n\ntest_suite\x18\x01 \x01(\x0c\x42\x02\x18\x01\x12\x0c\n\x04test\x18\x02 \x01(\x0c\x12+\n\ttest_type\x18\x03 \x01(\x0e\x32\x18.android.vts.VtsTestType\x12:\n\x0b\x64\x65vice_info\x18\x04 \x03(\x0b\x32%.android.vts.AndroidDeviceInfoMessage\x12\x31\n\nbuild_info\x18\x05 \x01(\x0b\x32\x1d.android.vts.AndroidBuildInfo\x12\x18\n\x10subscriber_email\x18\x06 \x03(\x0c\x12+\n\thost_info\x18\x07 \x01(\x0b\x32\x18.android.vts.VtsHostInfo\x12\x35\n\ttest_case\x18\x0b \x03(\x0b\x32\".android.vts.TestCaseReportMessage\x12\x36\n\tprofiling\x18\x15 \x03(\x0b\x32#.android.vts.ProfilingReportMessage\x12\x38\n\x08systrace\x18\x16 \x03(\x0b\x32\".android.vts.SystraceReportMessageB\x02\x18\x01\x12\x17\n\x0fstart_timestamp\x18\x65 \x01(\x03\x12\x15\n\rend_timestamp\x18\x66 \x01(\x03\x12\x34\n\x08\x63overage\x18g \x03(\x0b\x32\".android.vts.CoverageReportMessage\x12%\n\x03log\x18\xe9\x07 \x03(\x0b\x32\x17.android.vts.LogMessage\x12\x37\n\rlink_resource\x18\xf3\x07 \x03(\x0b\x32\x1f.android.vts.UrlResourceMessage\"\xa7\x01\n\x15TestPlanReportMessage\x12\x18\n\x10test_module_name\x18\x0b \x03(\t\x12#\n\x1btest_module_start_timestamp\x18\x0c \x03(\x03\x12\x16\n\x0etest_plan_name\x18\x15 \x01(\t\x12\x37\n\x0epartner_report\x18\x1f \x03(\x0b\x32\x1f.android.vts.UrlResourceMessage\"\x9f\x01\n\x14\x44\x61shboardPostMessage\x12\x14\n\x0c\x61\x63\x63\x65ss_token\x18\x01 \x01(\t\x12\x33\n\x0btest_report\x18\x02 \x03(\x0b\x32\x1e.android.vts.TestReportMessage\x12<\n\x10test_plan_report\x18\x03 \x03(\x0b\x32\".android.vts.TestPlanReportMessage*\xb3\x01\n\x0eTestCaseResult\x12\x12\n\x0eUNKNOWN_RESULT\x10\x00\x12\x19\n\x15TEST_CASE_RESULT_PASS\x10\x01\x12\x19\n\x15TEST_CASE_RESULT_FAIL\x10\x02\x12\x19\n\x15TEST_CASE_RESULT_SKIP\x10\x03\x12\x1e\n\x1aTEST_CASE_RESULT_EXCEPTION\x10\x04\x12\x1c\n\x18TEST_CASE_RESULT_TIMEOUT\x10\x05*\x9c\x01\n\x0bVtsTestType\x12\x18\n\x14UNKNOWN_VTS_TESTTYPE\x10\x00\x12\x1e\n\x1aVTS_HOST_DRIVEN_STRUCTURAL\x10\x01\x12\x1b\n\x17VTS_HOST_DRIVEN_FUZZING\x10\x02\x12\x19\n\x15VTS_TARGET_SIDE_GTEST\x10\x03\x12\x1b\n\x17VTS_TARGET_SIDE_FUZZING\x10\x04*\xa3\x01\n\x1aVtsProfilingRegressionMode\x12\x1b\n\x17UNKNOWN_REGRESSION_MODE\x10\x00\x12 \n\x1cVTS_REGRESSION_MODE_DISABLED\x10\x01\x12\"\n\x1eVTS_REGRESSION_MODE_INCREASING\x10\x02\x12\"\n\x1eVTS_REGRESSION_MODE_DECREASING\x10\x03*\xa4\x01\n\x10VtsProfilingType\x12\x1e\n\x1aUNKNOWN_VTS_PROFILING_TYPE\x10\x00\x12 \n\x1cVTS_PROFILING_TYPE_TIMESTAMP\x10\x01\x12%\n!VTS_PROFILING_TYPE_LABELED_VECTOR\x10\x02\x12\'\n#VTS_PROFILING_TYPE_UNLABELED_VECTOR\x10\x03\x42+\n\x15\x63om.android.vts.protoB\x10VtsReportMessageP\x00')
 
 _TESTCASERESULT = _descriptor.EnumDescriptor(
   name='TestCaseResult',
@@ -585,7 +585,7 @@
       options=None),
     _descriptor.FieldDescriptor(
       name='line_coverage_vector', full_name='android.vts.CoverageReportMessage.line_coverage_vector', index=3,
-      number=23, type=5, cpp_type=1, label=3,
+      number=23, type=3, cpp_type=2, label=3,
       has_default_value=False, default_value=[],
       message_type=None, enum_type=None, containing_type=None,
       is_extension=False, extension_scope=None,
diff --git a/utils/python/retry/__init__.py b/runners/adapters/__init__.py
similarity index 100%
copy from utils/python/retry/__init__.py
copy to runners/adapters/__init__.py
diff --git a/utils/python/retry/__init__.py b/runners/adapters/acts/__init__.py
similarity index 100%
copy from utils/python/retry/__init__.py
copy to runners/adapters/acts/__init__.py
diff --git a/runners/adapters/acts/acts_adapter.py b/runners/adapters/acts/acts_adapter.py
new file mode 100644
index 0000000..53f247a
--- /dev/null
+++ b/runners/adapters/acts/acts_adapter.py
@@ -0,0 +1,205 @@
+#
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+import importlib
+import json
+import logging
+import os
+import subprocess
+import sys
+import time
+
+from vts.runners.host import asserts
+from vts.runners.host import base_test
+from vts.runners.host import config_parser
+from vts.runners.host import records
+from vts.runners.host import test_runner
+from vts.utils.python.io import capture_printout
+from vts.utils.python.io import file_util
+
+ACTS_TEST_MODULE = 'ACTS_TEST_MODULE'
+LIST_TEST_OUTPUT_START = '==========> '
+LIST_TEST_OUTPUT_END = ' <=========='
+# Temp directory inside python log path. The name is required to be
+# 'temp' for the Java framework to skip reading contents as regular test logs.
+TEMP_DIR_NAME = 'temp'
+CONFIG_FILE_NAME = 'acts_config.txt'
+RESULT_FILE_NAME = 'test_run_summary.json'
+
+CONFIG_TEXT = '''{{
+    "_description": "VTS acts tests",
+    "testbed":
+    [
+        {{
+            "_description": "ACTS test bed",
+            "name": "{module_name}",
+            "AndroidDevice":
+            [
+              {serials}
+            ]
+        }}
+    ],
+    "logpath": "{log_path}",
+    "testpaths":
+    [
+        "{src_path}"
+    ]
+}}
+'''
+
+
+class ActsAdapter(base_test.BaseTestClass):
+    '''Template class for running acts test cases.
+
+    Attributes:
+        test_type: string, name of test type this adapter is for
+        result_path: string, test result directory for the adaptor
+        config_path: string, test config file path
+        module_name: string, ACTS module name
+        test_path: string, ACTS module source directory
+    '''
+    test_type = 'ACTS'
+
+    def setUpClass(self):
+        '''Set up result directory, generate configuration file, and list tests.'''
+        self.result_path = os.path.join(logging.log_path, TEMP_DIR_NAME,
+                                        self.test_type, str(time.time()))
+        file_util.Makedirs(self.result_path)
+        logging.debug('Result path for %s: %s' % (self.test_type,
+                                                 self.result_path))
+        self.test_path, self.module_name = self.getUserParam(
+            ACTS_TEST_MODULE).rsplit('/', 1)
+
+        self.config_path = os.path.join(self.result_path, CONFIG_FILE_NAME)
+        self.GenerateConfigFile()
+
+        testcases = self.ListTestCases()
+        logging.debug('ACTS Test cases: %s', testcases)
+
+    def tearDownClass(self):
+        '''Clear the result path.'''
+        file_util.Rmdirs(self.result_path, ignore_errors=True)
+
+    def GenerateConfigFile(self):
+        '''Generate test configuration file.'''
+        serials = []
+        for ad in self.android_devices:
+            serials.append('{"serial":"%s"}' % ad.serial)
+
+        config_text = CONFIG_TEXT.format(
+            module_name=self.module_name,
+            serials=','.join(serials),
+            log_path=self.result_path,
+            src_path=self.test_path)
+
+        with open(self.config_path, 'w') as f:
+            f.write(config_text)
+
+    def ListTestCases(self):
+        '''List test cases.
+
+        Returns:
+            List of string, test names.
+        '''
+        # TODO use ACTS runner to list test cases and add requested record.
+        # This step is optional but desired. To be implemented later
+
+    def Run(self):
+        '''Execute test cases.'''
+        # acts.py is installed to user bin by ACTS setup script.
+        # In the future, it is preferred to use the source code
+        # from repo directory.
+        bin = 'acts/bin/act.py'
+
+        cmd = '{bin} -c {config} -tb {module_name} -tc {module_name}'.format(
+            bin=bin, config=self.config_path, module_name=self.module_name)
+        logging.debug('cmd is: %s', cmd)
+
+        # Calling through subprocess is required because ACTS requires python3
+        # while VTS is currently using python2. In the future, ACTS runner
+        # can be invoked through importing when VTS upgrades to python3.
+
+        # A "hack" to call python3 outside of python2 virtualenv created by
+        # VTS framework
+        environ = {
+            key: val
+            for key, val in os.environ.iteritems() if 'virtualenv' not in val
+        }
+
+        # TODO(yuexima): disable buffer
+        p = subprocess.Popen(
+            cmd,
+            shell=True,
+            stdout=subprocess.PIPE,
+            stderr=subprocess.STDOUT,
+            env=environ)
+
+        for line in iter(p.stdout.readline, b''):
+            print line.rstrip()
+
+        p.communicate()
+        if p.returncode:
+            asserts.fail('Subprocess of ACTS command failed. Return code: %s' %
+                         p.returncode)
+
+    def ParseResults(self):
+        '''Get module run results and put in vts results.'''
+        file_path = file_util.FindFile(self.result_path, RESULT_FILE_NAME)
+
+        if file_path:
+            logging.debug('ACTS test result path: %s', file_path)
+            self.ParseJsonResults(file_path)
+        else:
+            logging.error('Cannot find result file name %s in %s',
+                          RESULT_FILE_NAME, self.result_path)
+
+    def generateAllTests(self):
+        '''Run the test module and parse results.'''
+        self.Run()
+        self.ParseResults()
+
+    def ParseJsonResults(self, result_path):
+        '''Parse test json result.
+
+        Args:
+            result_path: string, result json file path.
+        '''
+        with open(result_path, 'r') as f:
+            summary = json.load(f)
+
+        results = summary['Results']
+        for result in results:
+            logging.debug('Adding result for %s' %
+                         result[records.TestResultEnums.RECORD_NAME])
+            record = records.TestResultRecord(
+                result[records.TestResultEnums.RECORD_NAME])
+            record.test_class = result[records.TestResultEnums.RECORD_CLASS]
+            record.begin_time = result[
+                records.TestResultEnums.RECORD_BEGIN_TIME]
+            record.end_time = result[records.TestResultEnums.RECORD_END_TIME]
+            record.result = result[records.TestResultEnums.RECORD_RESULT]
+            record.uid = result[records.TestResultEnums.RECORD_UID]
+            record.extras = result[records.TestResultEnums.RECORD_EXTRAS]
+            record.details = result[records.TestResultEnums.RECORD_DETAILS]
+            record.extra_errors = result[
+                records.TestResultEnums.RECORD_EXTRA_ERRORS]
+
+            self.results.addRecord(record)
+
+        # TODO(yuexima): parse new result types
+
+if __name__ == "__main__":
+    test_runner.main()
diff --git a/runners/host/base_test.py b/runners/host/base_test.py
index 3c1e158..fd8c38e 100644
--- a/runners/host/base_test.py
+++ b/runners/host/base_test.py
@@ -28,11 +28,13 @@
 from vts.runners.host import records
 from vts.runners.host import signals
 from vts.runners.host import utils
+from vts.utils.python.controllers import adb
 from vts.utils.python.controllers import android_device
 from vts.utils.python.common import filter_utils
 from vts.utils.python.common import list_utils
 from vts.utils.python.coverage import coverage_utils
 from vts.utils.python.coverage import sancov_utils
+from vts.utils.python.precondition import precondition_utils
 from vts.utils.python.profiling import profiling_utils
 from vts.utils.python.reporting import log_uploading_utils
 from vts.utils.python.systrace import systrace_utils
@@ -49,7 +51,22 @@
 _REPORT_MESSAGE_FILE_NAME = "report_proto.msg"
 _BUG_REPORT_FILE_PREFIX = "bugreport"
 _BUG_REPORT_FILE_EXTENSION = ".zip"
+_LOGCAT_FILE_PREFIX = "logcat"
+_LOGCAT_FILE_EXTENSION = ".txt"
 _ANDROID_DEVICES = '_android_devices'
+_REASON_TO_SKIP_ALL_TESTS = '_reason_to_skip_all_tests'
+# the name of a system property which tells whether to stop properly configured
+# native servers where properly configured means a server's init.rc is
+# configured to stop when that property's value is 1.
+SYSPROP_VTS_NATIVE_SERVER = "vts.native_server.on"
+
+LOGCAT_BUFFERS = [
+    'radio',
+    'events',
+    'main',
+    'system',
+    'crash'
+]
 
 
 class BaseTestClass(object):
@@ -64,9 +81,9 @@
     Attributes:
         android_devices: A list of AndroidDevice object, representing android
                          devices.
+        test_module_name: A string representing the test module name.
         tests: A list of strings, each representing a test case name.
-        TAG: A string used to refer to a test class. Default is the test class
-             name.
+        log: A logger object used for logging.
         results: A records.TestResult object for aggregating test results from
                  the execution of test cases.
         _current_record: A records.TestResultRecord object for the test case
@@ -81,23 +98,24 @@
         web: WebFeature, object storing web feature util for test run
         coverage: CoverageFeature, object storing coverage feature util for test run
         sancov: SancovFeature, object storing sancov feature util for test run
+        start_vts_agents: whether to start vts agents when registering new
+                          android devices.
         profiling: ProfilingFeature, object storing profiling feature util for test run
-        _skip_all_testcases: A boolean, can be set by a subclass in
-                             setUpClass() to skip all test cases.
         _bug_report_on_failure: bool, whether to catch bug report at the end
-                                of failed test cases.
+                                of failed test cases. Default is False
+        _logcat_on_failure: bool, whether to dump logcat at the end
+                                of failed test cases. Default is True
         test_filter: Filter object to filter test names.
     """
-    TAG = None
+    start_vts_agents = True
 
     def __init__(self, configs):
         self.tests = []
-        if not self.TAG:
-            self.TAG = self.__class__.__name__
         # Set all the controller objects and params.
         for name, value in configs.items():
             setattr(self, name, value)
         self.results = records.TestResult()
+        self.log = logger.LoggerProxy()
         self._current_record = None
 
         # Setup test filters
@@ -122,8 +140,10 @@
             list_utils.ItemsToStr(self.exclude_filter), ',')
         exclude_over_include = self.getUserParam(
             keys.ConfigKeys.KEY_EXCLUDE_OVER_INCLUDE, default_value=None)
-        self.test_module_name = self.getUserParam(keys.ConfigKeys.KEY_TESTBED_NAME,
-                                             default_value=None)
+        self.test_module_name = self.getUserParam(
+            keys.ConfigKeys.KEY_TESTBED_NAME,
+            warn_if_not_found=True,
+            default_value=self.__class__.__name__)
         self.test_filter = filter_utils.Filter(
             self.include_filter,
             self.exclude_filter,
@@ -131,9 +151,9 @@
             exclude_over_include=exclude_over_include,
             enable_negative_pattern=True,
             enable_module_name_prefix_matching=True,
-            module_name=self.test_module_name)
-        self.test_filter.ExpandBitness()
-        logging.info('Test filter: %s' % self.test_filter)
+            module_name=self.test_module_name,
+            expand_bitness=True)
+        logging.debug('Test filter: %s' % self.test_filter)
 
         # TODO: get abi information differently for multi-device support.
         # Set other optional parameters
@@ -158,20 +178,24 @@
             self.user_params, web=self.web)
         self.log_uploading = log_uploading_utils.LogUploadingFeature(
             self.user_params, web=self.web)
+        self.collect_tests_only = self.getUserParam(
+            keys.ConfigKeys.IKEY_COLLECT_TESTS_ONLY, default_value=False)
         self.run_as_vts_self_test = self.getUserParam(
             keys.ConfigKeys.RUN_AS_VTS_SELFTEST, default_value=False)
         self.run_as_compliance_test = self.getUserParam(
             keys.ConfigKeys.RUN_AS_COMPLIANCE_TEST, default_value=False)
-        self._skip_all_testcases = False
         self._bug_report_on_failure = self.getUserParam(
             keys.ConfigKeys.IKEY_BUG_REPORT_ON_FAILURE, default_value=False)
+        self._logcat_on_failure = self.getUserParam(
+            keys.ConfigKeys.IKEY_LOGCAT_ON_FAILURE, default_value=True)
 
     @property
     def android_devices(self):
         """Returns a list of AndroidDevice objects"""
         if not hasattr(self, _ANDROID_DEVICES):
             setattr(self, _ANDROID_DEVICES,
-                    self.registerController(android_device))
+                    self.registerController(android_device,
+                                            start_services=self.start_vts_agents))
         return getattr(self, _ANDROID_DEVICES)
 
     @android_devices.setter
@@ -216,7 +240,7 @@
             setattr(self, name, self.user_params[name])
         for name in opt_param_names:
             if name not in self.user_params:
-                logging.info(("Missing optional user param '%s' in "
+                logging.debug(("Missing optional user param '%s' in "
                               "configuration, continue."), name)
             else:
                 setattr(self, name, self.user_params[name])
@@ -224,13 +248,15 @@
     def getUserParam(self,
                      param_name,
                      error_if_not_found=False,
-                     log_warning_and_continue_if_not_found=False,
+                     warn_if_not_found=False,
                      default_value=None,
                      to_str=False):
         """Get the value of a single user parameter.
 
         This method returns the value of specified user parameter.
-        Note: this method will not automatically set attribute using the parameter name and value.
+
+        Note: unlike getUserParams(), this method will not automatically set
+              attribute using the parameter name and value.
 
         Args:
             param_name: string or list of string, denoting user parameter names. If provided
@@ -238,18 +264,21 @@
                         If provided multiple strings,
                         self.user_params["<param_name1>"]["<param_name2>"]["<param_name3>"]...
                         will be accessed.
-            error_if_not_found: bool, whether to raise error if parameter not exists. Default:
-                                False
-            log_warning_and_continue_if_not_found: bool, log a warning message if parameter value
-                                                   not found.
-            default_value: object, default value to return if not found. If error_if_not_found is
-                           True, this parameter has no effect. Default: None
-            to_str: boolean, whether to convert the result object to string if not None.
-                    Note, strings passing in from java json config are usually unicode.
+            error_if_not_found: bool, whether to raise error if parameter not
+                                exists. Default: False
+            warn_if_not_found: bool, log a warning message if parameter value
+                               not found. Default: False
+            default_value: object, default value to return if not found.
+                           If error_if_not_found is true, this parameter has no
+                           effect. Default: None
+            to_str: boolean, whether to convert the result object to string if
+                    not None.
+                    Note, strings passing in from java json config are often
+                    unicode.
 
         Returns:
             object, value of the specified parameter name chain if exists;
-            <default_value> if not exists.
+            <default_value> otherwise.
         """
 
         def ToStr(return_value):
@@ -270,20 +299,143 @@
         curr_obj = self.user_params
         for param in param_name:
             if param not in curr_obj:
-                msg = "Missing user param '%s' in test configuration." % param_name
+                msg = ("Missing user param '%s' in test configuration.\n"
+                       "User params: %s") % (param_name, self.user_params)
                 if error_if_not_found:
                     raise errors.BaseTestError(msg)
-                elif log_warning_and_continue_if_not_found:
+                elif warn_if_not_found:
                     logging.warn(msg)
                 return ToStr(default_value)
             curr_obj = curr_obj[param]
 
         return ToStr(curr_obj)
 
+    def _getUserConfig(self,
+                       config_type,
+                       key,
+                       default_value=None,
+                       error_if_not_found=False,
+                       warn_if_not_found=False,
+                       to_str=False):
+        """Get the value of a user config given the key.
+
+        This method returns the value of specified user config type.
+
+        Args:
+            config_type: string, type of user config
+            key: string, key of the value string in string config map.
+            default_value: object, default value to return if not found.
+                           If error_if_not_found is true, this parameter has no
+                           effect. Default: None
+            error_if_not_found: bool, whether to raise error if parameter not
+                                exists. Default: False
+            warn_if_not_found: bool, log a warning message if parameter value
+                               not found. Default: False
+            to_str: boolean, whether to apply str() method to result value
+                    if result is not None.
+                    Note, strings passing in from java json config are ofen
+                    unicode.
+
+        Returns:
+            Value in config matching the given key and type if exists;
+            <default_value> otherwise.
+        """
+        dic = self.getUserParam(config_type,
+                                error_if_not_found=False,
+                                warn_if_not_found=False,
+                                default_value=None,
+                                to_str=False)
+
+        if dic is None or key not in dic:
+            msg = ("Config key %s not found in user config type %s.\n"
+                   "User params: %s") % (key, config_type, self.user_params)
+            if error_if_not_found:
+                raise errors.BaseTestError(msg)
+            elif warn_if_not_found:
+                logging.warn(msg)
+
+            return default_value
+
+        return dic[key] if not to_str else str(dic[key])
+
+    def getUserConfigStr(self, key, **kwargs):
+        """Get the value of a user config string given the key.
+
+        See _getUserConfig method for more details.
+        """
+        kwargs["to_str"] = True
+        return self._getUserConfig(keys.ConfigKeys.IKEY_USER_CONFIG_STR,
+                                   key,
+                                   **kwargs)
+
+    def getUserConfigInt(self, key, **kwargs):
+        """Get the value of a user config int given the key.
+
+        See _getUserConfig method for more details.
+        """
+        return self._getUserConfig(keys.ConfigKeys.IKEY_USER_CONFIG_INT,
+                                   key,
+                                   **kwargs)
+
+    def getUserConfigBool(self, key, **kwargs):
+        """Get the value of a user config bool given the key.
+
+        See _getUserConfig method for more details.
+        """
+        return self._getUserConfig(keys.ConfigKeys.IKEY_USER_CONFIG_BOOL,
+                                   key,
+                                   **kwargs)
+
     def _setUpClass(self):
         """Proxy function to guarantee the base implementation of setUpClass
         is called.
         """
+        if not precondition_utils.MeetFirstApiLevelPrecondition(self):
+            self.skipAllTests("The device's first API level doesn't meet the "
+                              "precondition.")
+
+        if (self.getUserParam(keys.ConfigKeys.IKEY_DISABLE_FRAMEWORK,
+                              default_value=False) or
+            # @Deprecated Legacy configuration option name.
+            self.getUserParam(keys.ConfigKeys.IKEY_BINARY_TEST_DISABLE_FRAMEWORK,
+                              default_value=False)):
+            # Disable the framework if requested.
+            for device in self.android_devices:
+                device.stop()
+        else:
+            # Enable the framework if requested.
+            for device in self.android_devices:
+                device.start()
+
+        if (self.getUserParam(keys.ConfigKeys.IKEY_STOP_NATIVE_SERVERS,
+                              default_value=False) or
+            # @Deprecated Legacy configuration option name.
+            self.getUserParam(keys.ConfigKeys.IKEY_BINARY_TEST_STOP_NATIVE_SERVERS,
+                              default_value=False)):
+            for device in self.android_devices:
+                logging.debug("Stops all properly configured native servers "
+                              "on device %s", device.serial)
+                results = device.setProp(SYSPROP_VTS_NATIVE_SERVER, "1")
+                native_server_process_names = self.getUserParam(
+                    keys.ConfigKeys.IKEY_NATIVE_SERVER_PROCESS_NAME,
+                    default_value=[])
+                if native_server_process_names:
+                    for native_server_process_name in native_server_process_names:
+                        while True:
+                            logging.info("Checking process %s",
+                                         native_server_process_name)
+                            cmd_result = device.shell.Execute("ps -A")
+                            if cmd_result[const.EXIT_CODE][0] != 0:
+                                logging.error("ps command failed (exit code: %s",
+                                              cmd_result[const.EXIT_CODE][0])
+                                break
+                            if (native_server_process_name not in cmd_result[
+                                    const.STDOUT][0]):
+                                logging.debug("Process %s not running",
+                                             native_server_process_name)
+                                break
+                            time.sleep(1)
+
         return self.setUpClass()
 
     def setUpClass(self):
@@ -315,11 +467,21 @@
                                          _REPORT_MESSAGE_FILE_NAME)
 
         if message_b:
-            logging.info('Result proto message path: %s', report_proto_path)
+            logging.debug('Result proto message path: %s', report_proto_path)
 
         with open(report_proto_path, "wb") as f:
             f.write(message_b)
 
+        if getattr(self, keys.ConfigKeys.IKEY_BINARY_TEST_STOP_NATIVE_SERVERS,
+                   False):
+            logging.debug("Restarts all properly configured native servers.")
+            for device in self.android_devices:
+                try:
+                    self.device.setProp(SYSPROP_VTS_NATIVE_SERVER, "0")
+                except adb.AdbError:
+                    logging.error("failed to restore native servers for device "
+                                  + device.serial)
+
         return ret
 
     def tearDownClass(self):
@@ -391,7 +553,9 @@
             self.web.SetTestResult(ReportMsg.TEST_CASE_RESULT_FAIL)
         self.onFail(record.test_name, begin_time)
         if self._bug_report_on_failure:
-            self.CatchBugReport('%s-%s' % (self.TAG, record.test_name))
+            self.DumpBugReport(ecord.test_name)
+        if self._logcat_on_failure:
+            self.DumpLogcat(record.test_name)
 
     def onFail(self, test_name, begin_time):
         """A function that is executed upon a test case failure.
@@ -412,7 +576,7 @@
         begin_time = logger.epochToLogLineTimestamp(record.begin_time)
         msg = record.details
         if msg:
-            logging.info(msg)
+            logging.debug(msg)
         logging.info(RESULT_LINE_TEMPLATE, test_name, record.result)
         if self.web.enabled:
             self.web.SetTestResult(ReportMsg.TEST_CASE_RESULT_PASS)
@@ -436,7 +600,7 @@
         test_name = record.test_name
         begin_time = logger.epochToLogLineTimestamp(record.begin_time)
         logging.info(RESULT_LINE_TEMPLATE, test_name, record.result)
-        logging.info("Reason to skip: %s", record.details)
+        logging.debug("Reason to skip: %s", record.details)
         if self.web.enabled:
             self.web.SetTestResult(ReportMsg.TEST_CASE_RESULT_SKIP)
         self.onSkip(test_name, begin_time)
@@ -484,7 +648,9 @@
             self.web.SetTestResult(ReportMsg.TEST_CASE_RESULT_EXCEPTION)
         self.onException(test_name, begin_time)
         if self._bug_report_on_failure:
-            self.CatchBugReport('%s-%s' % (self.TAG, record.test_name))
+            self.DumpBugReport(ecord.test_name)
+        if self._logcat_on_failure:
+            self.DumpLogcat(record.test_name)
 
     def onException(self, test_name, begin_time):
         """A function that is executed upon an unhandled exception from a test
@@ -544,9 +710,8 @@
         if include filter is empty, only tests not in exclude filter will be
         executed.
 
-        The second layer of filter is checking _skip_all_testcases flag:
-        the subclass may set _skip_all_testcases to True in its implementation
-        of setUpClass. If the flag is set, this method raises signals.TestSkip.
+        The second layer of filter is checking whether skipAllTests method is
+        called. If the flag is set, this method raises signals.TestSkip.
 
         The third layer of filter is checking abi bitness:
         if a test has a suffix indicating the intended architecture bitness,
@@ -583,8 +748,8 @@
         if not test_filter.Filter(test_name):
             raise signals.TestSilent("Test case '%s' did not pass filters.")
 
-        if self._skip_all_testcases:
-            raise signals.TestSkip("All test cases skipped.")
+        if self.isSkipAllTests():
+            raise signals.TestSkip(self.getSkipAllTestsReason())
 
     def _filterOneTestThroughAbiBitness(self, test_name):
         """Check test filter for the given test name.
@@ -620,7 +785,7 @@
             kwargs: Extra kwargs.
         """
         is_silenced = False
-        tr_record = records.TestResultRecord(test_name, self.TAG)
+        tr_record = records.TestResultRecord(test_name, self.test_module_name)
         tr_record.testBegin()
         logging.info("%s %s", TEST_CASE_TOKEN, test_name)
         verdict = None
@@ -630,6 +795,9 @@
             asserts.assertTrue(ret is not False,
                                "Setup test entry for %s failed." % test_name)
             self.filterOneTest(test_name)
+            if self.collect_tests_only:
+                asserts.explicitPass("Collect tests only.")
+
             try:
                 ret = self._setUp(test_name)
                 asserts.assertTrue(ret is not False,
@@ -743,24 +911,36 @@
         args = args or ()
         kwargs = kwargs or {}
         failed_settings = []
-        for s in settings:
-            test_name = "{} {}".format(tag, s)
+
+        def GenerateTestName(setting):
+            test_name = "{} {}".format(tag, setting)
             if name_func:
                 try:
-                    test_name = name_func(s, *args, **kwargs)
+                    test_name = name_func(setting, *args, **kwargs)
                 except:
                     logging.exception(("Failed to get test name from "
                                        "test_func. Fall back to default %s"),
                                       test_name)
 
-            tr_record = records.TestResultRecord(test_name, self.TAG)
-            self.results.requested.append(tr_record)
             if len(test_name) > utils.MAX_FILENAME_LEN:
                 test_name = test_name[:utils.MAX_FILENAME_LEN]
+
+            return test_name
+
+        for setting in settings:
+            test_name = GenerateTestName(setting)
+
+            tr_record = records.TestResultRecord(test_name, self.test_module_name)
+            self.results.requested.append(tr_record)
+
+        for setting in settings:
+            test_name = GenerateTestName(setting)
             previous_success_cnt = len(self.results.passed)
-            self.execOneTest(test_name, test_func, (s, ) + args, **kwargs)
+
+            self.execOneTest(test_name, test_func, (setting, ) + args, **kwargs)
             if len(self.results.passed) - previous_success_cnt != 1:
-                failed_settings.append(s)
+                failed_settings.append(setting)
+
         return failed_settings
 
     def _exec_func(self, func, *args):
@@ -783,7 +963,7 @@
             raise signals.TestAbortAll, e, sys.exc_info()[2]
         except:
             logging.exception("Exception happened when executing %s in %s.",
-                              func.__name__, self.TAG)
+                              func.__name__, self.test_module_name)
             return False
 
     def _get_all_test_names(self):
@@ -819,8 +999,8 @@
         test_funcs = []
         for test_name in test_names:
             if not hasattr(self, test_name):
-                logging.warning("%s does not have test case %s.", self.TAG,
-                                test_name)
+                logging.warning("%s does not have test case %s.",
+                                self.test_module_name, test_name)
             elif (test_name.startswith(STR_TEST) or
                   test_name.startswith(STR_GENERATE)):
                 test_funcs.append((test_name, getattr(self, test_name)))
@@ -831,6 +1011,91 @@
 
         return test_funcs
 
+    def getTests(self, test_names=None):
+        """Get the test cases within a test class.
+
+        Args:
+            test_names: A list of string that are test case names requested in
+                        cmd line.
+
+        Returns:
+            A list of tuples of (string, function). String is the test case
+            name, function is the actual test case function.
+        """
+        if not test_names:
+            if self.tests:
+                # Specified by run list in class.
+                test_names = list(self.tests)
+            else:
+                # No test case specified by user, execute all in the test class
+                test_names = self._get_all_test_names()
+
+        tests = self._get_test_funcs(test_names)
+        return tests
+
+    def runTests(self, tests):
+        """Run tests and collect test results.
+
+        Args:
+            tests: A list of tests to be run.
+
+        Returns:
+            The test results object of this class.
+        """
+        # Setup for the class.
+        try:
+            if self._setUpClass() is False:
+                raise signals.TestFailure(
+                    "Failed to setup %s." % self.test_module_name)
+        except Exception as e:
+            logging.exception("Failed to setup %s.", self.test_module_name)
+            self.results.failClass(self.test_module_name, e)
+            self._exec_func(self._tearDownClass)
+            return self.results
+
+        # Run tests in order.
+        try:
+            # Check if module is running in self test mode.
+            if self.run_as_vts_self_test:
+                logging.debug('setUpClass function was executed successfully.')
+                self.results.passClass(self.test_module_name)
+                return self.results
+
+            for test_name, test_func in tests:
+                if test_name.startswith(STR_GENERATE):
+                    logging.debug(
+                        "Executing generated test trigger function '%s'",
+                        test_name)
+                    test_func()
+                    logging.debug("Finished '%s'", test_name)
+                else:
+                    self.execOneTest(test_name, test_func, None)
+            if self.isSkipAllTests() and not self.results.executed:
+                self.results.skipClass(
+                    self.test_module_name,
+                    "All test cases skipped; unable to find any test case.")
+            return self.results
+        except (signals.TestAbortClass, acts_signals.TestAbortClass):
+            logging.error("Received TestAbortClass signal")
+            return self.results
+        except (signals.TestAbortAll, acts_signals.TestAbortAll) as e:
+            logging.error("Received TestAbortAll signal")
+            # Piggy-back test results on this exception object so we don't lose
+            # results from this test class.
+            setattr(e, "results", self.results)
+            raise signals.TestAbortAll, e, sys.exc_info()[2]
+        except Exception as e:
+            # Exception happened during test.
+            logging.exception(e)
+            raise e
+        finally:
+            self._exec_func(self._tearDownClass)
+            if self.web.enabled:
+                name, timestamp = self.web.GetTestModuleKeys()
+                self.results.setTestModuleKeys(name, timestamp)
+            logging.info("Summary for test class %s: %s",
+                         self.test_module_name, self.results.summary())
+
     def run(self, test_names=None):
         """Runs test cases within a test class by the order they appear in the
         execution list.
@@ -851,75 +1116,16 @@
         Returns:
             The test results object of this class.
         """
-        logging.info("==========> %s <==========", self.TAG)
+        logging.info("==========> %s <==========", self.test_module_name)
         # Devise the actual test cases to run in the test class.
-        if not test_names:
-            if self.tests:
-                # Specified by run list in class.
-                test_names = list(self.tests)
-            else:
-                # No test case specified by user, execute all in the test class
-                test_names = self._get_all_test_names()
+        tests = self.getTests(test_names)
 
         if not self.run_as_vts_self_test:
             self.results.requested = [
-                records.TestResultRecord(test_name, self.TAG)
-                for test_name in test_names if test_name.startswith(STR_TEST)
+                records.TestResultRecord(test_name, self.test_module_name)
+                for test_name,_ in tests if test_name.startswith(STR_TEST)
             ]
-        tests = self._get_test_funcs(test_names)
-
-        # Setup for the class.
-        try:
-            if self._setUpClass() is False:
-                raise signals.TestFailure("Failed to setup %s." % self.TAG)
-        except Exception as e:
-            logging.exception("Failed to setup %s.", self.TAG)
-            self.results.failClass(self.TAG, e)
-            self._exec_func(self._tearDownClass)
-            return self.results
-
-        # Run tests in order.
-        try:
-            # Check if module is running in self test mode.
-            if self.run_as_vts_self_test:
-                logging.info('setUpClass function was executed successfully.')
-                self.results.passClass(self.TAG)
-                return self.results
-
-            for test_name, test_func in tests:
-                if test_name.startswith(STR_GENERATE):
-                    logging.info(
-                        "Executing generated test trigger function '%s'",
-                        test_name)
-                    test_func()
-                    logging.info("Finished '%s'", test_name)
-                else:
-                    self.execOneTest(test_name, test_func, None)
-            if self._skip_all_testcases and not self.results.executed:
-                self.results.skipClass(
-                    self.TAG,
-                    "All test cases skipped; unable to find any test case.")
-            return self.results
-        except (signals.TestAbortClass, acts_signals.TestAbortClass):
-            logging.info("Received TestAbortClass signal")
-            return self.results
-        except (signals.TestAbortAll, acts_signals.TestAbortAll) as e:
-            logging.info("Received TestAbortAll signal")
-            # Piggy-back test results on this exception object so we don't lose
-            # results from this test class.
-            setattr(e, "results", self.results)
-            raise signals.TestAbortAll, e, sys.exc_info()[2]
-        except Exception as e:
-            # Exception happened during test.
-            logging.exception(e)
-            raise e
-        finally:
-            self._exec_func(self._tearDownClass)
-            if self.web.enabled:
-                name, timestamp = self.web.GetTestModuleKeys()
-                self.results.setTestModuleKeys(name, timestamp)
-            logging.info("Summary for test class %s: %s", self.TAG,
-                         self.results.summary())
+        return self.runTests(tests)
 
     def cleanUp(self):
         """A function that is executed upon completion of all tests cases
@@ -929,7 +1135,7 @@
         user.
         """
 
-    def CatchBugReport(self, prefix=''):
+    def DumpBugReport(self, prefix=''):
         """Get device bugreport through adb command.
 
         Args:
@@ -939,12 +1145,78 @@
         if prefix:
             prefix = re.sub('[^\w\-_\. ]', '_', prefix) + '_'
 
-        for i in range(len(self.android_devices)):
-            device = self.android_devices[i]
-            bug_report_file_name = prefix + _BUG_REPORT_FILE_PREFIX + str(
-                i) + _BUG_REPORT_FILE_EXTENSION
-            bug_report_file_path = os.path.join(logging.log_path,
-                                                bug_report_file_name)
+        for device in self.android_devices:
+            file_name = (_BUG_REPORT_FILE_PREFIX
+                         + prefix
+                         + '_%s' % device.serial
+                         + _BUG_REPORT_FILE_EXTENSION)
 
-            logging.info('Catching bugreport %s' % bug_report_file_path)
-            device.adb.bugreport(bug_report_file_path)
+            file_path = os.path.join(logging.log_path,
+                                     file_name)
+
+            logging.info('Dumping bugreport %s...' % file_path)
+            device.adb.bugreport(file_path)
+
+    def skipAllTests(self, msg):
+        """Skip all test cases.
+
+        This method is usually called in setup functions when a precondition
+        to the test module is not met.
+
+        Args:
+            msg: string, reason why tests are skipped. If set to None or empty
+            string, a default message will be used (not recommended)
+        """
+        if not msg:
+            msg = "No reason provided."
+
+        setattr(self, _REASON_TO_SKIP_ALL_TESTS, msg)
+
+    def isSkipAllTests(self):
+        """Returns whether all tests are set to be skipped.
+
+        Note: If all tests are being skipped not due to skipAllTests
+              being called, or there is no tests defined, this method will
+              still return False (since skipAllTests is not called.)
+
+        Returns:
+            bool, True if skipAllTests has been called; False otherwise.
+        """
+        return self.getSkipAllTestsReason() is not None
+
+    def getSkipAllTestsReason(self):
+        """Returns the reason why all tests are skipped.
+
+        Note: If all tests are being skipped not due to skipAllTests
+              being called, or there is no tests defined, this method will
+              still return None (since skipAllTests is not called.)
+
+        Returns:
+            String, reason why tests are skipped. None if skipAllTests
+            is not called.
+        """
+        return getattr(self, _REASON_TO_SKIP_ALL_TESTS, None)
+
+    def DumpLogcat(self, prefix=''):
+        """Dumps device logcat outputs to log directory.
+
+        Args:
+            prefix: string, file name prefix. Usually in format of
+                    <test_module>-<test_case>
+        """
+        if prefix:
+            prefix = re.sub('[^\w\-_\. ]', '_', prefix) + '_'
+
+        for device in self.android_devices:
+            for buffer in LOGCAT_BUFFERS:
+                file_name = (_LOGCAT_FILE_PREFIX
+                             + prefix
+                             + '_%s_' % buffer
+                             + device.serial
+                             + _LOGCAT_FILE_EXTENSION)
+
+                file_path = os.path.join(logging.log_path,
+                                         file_name)
+
+                logging.info('Dumping logcat %s...' % file_path)
+                device.adb.logcat('-b', buffer, '-d', '>', file_path)
diff --git a/runners/host/keys.py b/runners/host/keys.py
index b9e75fb..37f4e90 100644
--- a/runners/host/keys.py
+++ b/runners/host/keys.py
@@ -30,7 +30,6 @@
     KEY_TESTBED_NAME = "name"
     KEY_TEST_PATHS = "test_paths"
     KEY_TEST_SUITE = "test_suite"
-    KEY_TEST_MAX_TIMEOUT = "test_max_timeout"
 
     # Keys in test suite
     KEY_INCLUDE_FILTER = "include_filter"
@@ -43,11 +42,16 @@
     IKEY_BINARY_TEST_ENVP = "binary_test_envp"
     IKEY_BINARY_TEST_ARGS = "binary_test_args"
     IKEY_BINARY_TEST_LD_LIBRARY_PATH = "binary_test_ld_library_path"
-    IKEY_BINARY_TEST_DISABLE_FRAMEWORK = "binary_test_disable_framework"
-    IKEY_BINARY_TEST_STOP_NATIVE_SERVERS = "binary_test_stop_native_servers"
     IKEY_NATIVE_SERVER_PROCESS_NAME = "native_server_process_name"
     IKEY_GTEST_BATCH_MODE = "gtest_batch_mode"
 
+    # @Deprecated use IKEY_DISABLE_FRAMEWORK
+    IKEY_BINARY_TEST_DISABLE_FRAMEWORK = "binary_test_disable_framework"
+    IKEY_DISABLE_FRAMEWORK = "DISABLE_FRAMEWORK"
+    # @Deprecated use IKEY_STOP_NATIVE_SERVERS
+    IKEY_BINARY_TEST_STOP_NATIVE_SERVERS = "binary_test_stop_native_servers"
+    IKEY_STOP_NATIVE_SERVERS = "STOP_NATIVE_SERVERS"
+
     # Internal keys, used internally, not exposed to user's config files.
     IKEY_USER_PARAM = "user_params"
     IKEY_TESTBED_NAME = "testbed_name"
@@ -58,11 +62,13 @@
     IKEY_SKIP_ON_32BIT_ABI = "skip_on_32bit_abi"
     IKEY_SKIP_ON_64BIT_ABI = "skip_on_64bit_abi"
     IKEY_SKIP_IF_THERMAL_THROTTLING = "skip_if_thermal_throttling"
+    IKEY_DISABLE_CPU_FREQUENCY_SCALING = "disable_cpu_frequency_scaling"
 
     IKEY_BUILD = "build"
     IKEY_DATA_FILE_PATH = "data_file_path"
 
-    IKEY_BUG_REPORT_ON_FAILURE = "bug_report_on_failure"
+    IKEY_BUG_REPORT_ON_FAILURE = "BUG_REPORT_ON_FAILURE"
+    IKEY_LOGCAT_ON_FAILURE = "LOGCAT_ON_FAILURE"
 
     # sub fields of test_bed
     IKEY_ANDROID_DEVICE = "AndroidDevice"
@@ -103,12 +109,16 @@
     IKEY_GLOBAL_COVERAGE = "global_coverage"
     IKEY_SANCOV_RESOURCES_PATH = "sancov_resources_path"
     IKEY_GCOV_RESOURCES_PATH = "gcov_resources_path"
+    IKEY_COVERAGE_REPORT_PATH = "coverage_report_path"
+    IKEY_EXCLUDE_COVERAGE_PATH = "exclude_coverage_path"
 
     # Keys for the HAL HIDL GTest type (see VtsMultiDeviceTest.java).
     IKEY_PRECONDITION_HWBINDER_SERVICE = "precondition_hwbinder_service"
     IKEY_PRECONDITION_FEATURE = "precondition_feature"
     IKEY_PRECONDITION_FILE_PATH_PREFIX = "precondition_file_path_prefix"
+    IKEY_PRECONDITION_FIRST_API_LEVEL = "precondition_first_api_level"
     IKEY_PRECONDITION_LSHAL = "precondition_lshal"
+    IKEY_PRECONDITION_SYSPROP = "precondition_sysprop"
     IKEY_PRECONDITION_VINTF = "precondition_vintf"
 
     # Keys for toggle passthrough mode
@@ -127,12 +137,21 @@
     IKEY_LOG_UPLOADING_USE_DATE_DIRECTORY = "log_uploading_use_date_directory"
     IKEY_LOG_UPLOADING_URL_PREFIX = "log_uploading_url_prefix"
 
+    # Keys for general user config types
+    IKEY_USER_CONFIG_STR = 'CONFIG_STR'
+    IKEY_USER_CONFIG_INT = 'CONFIG_INT'
+    IKEY_USER_CONFIG_BOOL = 'CONFIG_BOOL'
+
     # A list of keys whose values in configs should not be passed to test
     # classes without unpacking first.
     RESERVED_KEYS = (KEY_TESTBED, KEY_LOG_PATH, KEY_TEST_PATHS)
 
-    # Vts self test related keys
+    # Keys for special run modes
+    IKEY_COLLECT_TESTS_ONLY = "collect_tests_only"
     RUN_AS_VTS_SELFTEST = "run_as_vts_self_test"
 
     # Vts compliance test related keys
     RUN_AS_COMPLIANCE_TEST = "run_as_compliance_test"
+
+    # Mobly test related keys
+    MOBLY_TEST_MODULE = "MOBLY_TEST_MODULE"
diff --git a/runners/host/logger.py b/runners/host/logger.py
index a043d17..293b114 100755
--- a/runners/host/logger.py
+++ b/runners/host/logger.py
@@ -39,6 +39,7 @@
     "DEBUG": logging.DEBUG,
 }
 
+
 def _parse_logline_timestamp(t):
     """Parses a logline timestamp into a tuple.
 
@@ -137,13 +138,16 @@
         filename: Name of the log file. The default is the time the logger
                   is requested.
         log_severity: string, set the log severity level, default is INFO.
+                      This value affects console stream log handler and the python runner
+                      part of TradeFed host_log.
     """
     log = logging.getLogger()
     # Clean up any remaining handlers.
     killTestLogger(log)
     log.propagate = False
 
-    log.setLevel(log_severity_map.get(log_severity, logging.INFO))
+    log.setLevel(logging.DEBUG)
+
     # Log info to stream
     terminal_format = log_line_format
     if prefix:
@@ -152,17 +156,25 @@
     ch = logging.StreamHandler(sys.stdout)
     ch.setFormatter(c_formatter)
     ch.setLevel(log_severity_map.get(log_severity, logging.INFO))
+    log.addHandler(ch)
+
     # Log everything to file
     f_formatter = logging.Formatter(log_line_format, log_line_time_format)
+
     # All the logs of this test class go into one directory
     if filename is None:
         filename = getLogFileTimestamp()
         utils.create_dir(log_path)
-    fh = logging.FileHandler(os.path.join(log_path, 'test_run_details.txt'))
-    fh.setFormatter(f_formatter)
-    fh.setLevel(log_severity_map.get(log_severity, logging.INFO))
-    log.addHandler(ch)
-    log.addHandler(fh)
+
+    default_log_levels = ('ERROR', 'INFO', 'DEBUG')
+    for level in default_log_levels:
+        idx = filename.rfind('.')
+        if idx < 0:
+            idx = len(filename)
+        addLogFile(log_path=log_path,
+                   filename=filename[:idx] + '_' + level + filename[idx:],
+                   log_severity=level)
+
     log.log_path = log_path
     logging.log_path = log_path
 
@@ -200,21 +212,53 @@
     os.symlink(actual_path, link_path)
 
 
-def setupTestLogger(log_path, prefix=None, filename=None, log_severity="INFO"):
+def setupTestLogger(log_path,
+                    prefix=None,
+                    filename=None,
+                    log_severity="INFO",
+                    create_symlink=True):
     """Customizes the root logger for a test run.
 
     Args:
         log_path: Location of the report file.
         prefix: A prefix for each log line in terminal.
         filename: Name of the files. The default is the time the objects
-            are requested.
+                  are requested.
+        create_symlink: bool. determines whether to create the symlink or not.
+                        set to True as default.
+
+    Returns:
+        A string, abs path to the created log file.
     """
     if filename is None:
         filename = getLogFileTimestamp()
     utils.create_dir(log_path)
     logger = _initiateTestLogger(log_path, prefix, filename, log_severity)
-    if isSymlinkSupported():
+    if create_symlink and isSymlinkSupported():
         createLatestLogAlias(log_path)
+    return os.path.join(log_path, filename)
+
+
+def addLogFile(log_path, filename=None, log_severity="INFO"):
+    """Creates a log file and adds the handler to the root logger.
+
+    Args:
+        log_path: Location of the report file.
+        filename: Name of the log file. The default is the time the logger
+                  is requested.
+
+    Returns:
+        A string, abs path to the created log file.
+        logging.FileHandler instance which is added to the logger.
+    """
+    if filename is None:
+        filename = getLogFileTimestamp()
+    f_formatter = logging.Formatter(log_line_format, log_line_time_format)
+    fh = logging.FileHandler(os.path.join(log_path, filename))
+    fh.setFormatter(f_formatter)
+    fh.setLevel(log_severity_map.get(log_severity, logging.INFO))
+    logging.getLogger().addHandler(fh)
+    return os.path.join(log_path, filename), fh
 
 
 def normalizeLogLineTimestamp(log_line_timestamp):
diff --git a/runners/host/signals.py b/runners/host/signals.py
index ed23bfd..aa2e49b 100644
--- a/runners/host/signals.py
+++ b/runners/host/signals.py
@@ -18,6 +18,7 @@
 
 import functools
 import json
+import logging
 
 
 def GeneratedTest(func):
@@ -40,15 +41,24 @@
 class TestSignalError(Exception):
     """Raised when an error occurs inside a test signal."""
 
-
 class TestSignal(Exception):
-    """Base class for all test result control signals."""
+    """Base class for all test result control signals.
+
+    Attributes:
+        details: A string that describes the reason for raising this signal.
+        extras: A json-serializable data type to convey extra information about
+                a test result.
+    """
 
     def __init__(self, details, extras=None):
-        if not isinstance(details, str):
-            raise TestSignalError("Message has to be a string.")
         super(TestSignal, self).__init__(details)
-        self.details = details
+        try:
+            self.details = str(details)
+        except UnicodeEncodeError:
+            # TODO: remove when we stop supporting Python 2
+            logging.warning(u"Details contain non-ASCII characters: %s",
+                            details)
+            self.details = details.encode("utf-8")
         try:
             json.dumps(extras)
             self.extras = extras
diff --git a/runners/host/tcp_client/vts_tcp_client.py b/runners/host/tcp_client/vts_tcp_client.py
index 44a6a20..eec0aa1 100755
--- a/runners/host/tcp_client/vts_tcp_client.py
+++ b/runners/host/tcp_client/vts_tcp_client.py
@@ -175,10 +175,10 @@
                             target_component_name=None,
                             hw_binder_service_name=None):
         """RPC to LAUNCH_DRIVER_SERVICE."""
-        logging.info("service_name: %s", service_name)
-        logging.info("file_path: %s", file_path)
-        logging.info("bits: %s", bits)
-        logging.info("driver_type: %s", driver_type)
+        logging.debug("service_name: %s", service_name)
+        logging.debug("file_path: %s", file_path)
+        logging.debug("bits: %s", bits)
+        logging.debug("driver_type: %s", driver_type)
         self.SendCommand(
             SysMsg_pb2.LAUNCH_DRIVER_SERVICE,
             driver_type=driver_type,
@@ -192,7 +192,7 @@
             target_component_name=target_component_name,
             hw_binder_service_name=hw_binder_service_name)
         resp = self.RecvResponse()
-        logging.info("resp for LAUNCH_DRIVER_SERVICE: %s", resp)
+        logging.debug("resp for LAUNCH_DRIVER_SERVICE: %s", resp)
         if driver_type == SysMsg_pb2.VTS_DRIVER_TYPE_HAL_HIDL \
                 or driver_type == SysMsg_pb2.VTS_DRIVER_TYPE_HAL_CONVENTIONAL \
                 or driver_type == SysMsg_pb2.VTS_DRIVER_TYPE_HAL_LEGACY:
@@ -207,7 +207,7 @@
         """RPC to LIST_APIS."""
         self.SendCommand(SysMsg_pb2.LIST_APIS)
         resp = self.RecvResponse()
-        logging.info("resp for LIST_APIS: %s", resp)
+        logging.debug("resp for LIST_APIS: %s", resp)
         if (resp.response_code == SysMsg_pb2.SUCCESS):
             return resp.spec
         return None
@@ -298,12 +298,12 @@
                 logging.exception(e)
                 logging.error("Paring error\n%s", resp.result)
             if result.return_type.type == CompSpecMsg_pb2.TYPE_SUBMODULE:
-                logging.info("returned a submodule spec")
-                logging.info("spec: %s", result.return_type_submodule_spec)
+                logging.debug("returned a submodule spec")
+                logging.debug("spec: %s", result.return_type_submodule_spec)
                 return mirror_object.MirrorObject(
                     self, result.return_type_submodule_spec, None)
 
-            logging.info("result: %s", result.return_type_hidl)
+            logging.debug("result: %s", result.return_type_hidl)
             if len(result.return_type_hidl) == 1:
                 result_value = self.GetPythonDataOfVariableSpecMsg(
                     result.return_type_hidl[0])
@@ -344,8 +344,8 @@
                 logging.exception(e)
                 logging.error("Paring error\n%s", resp.result)
             if result.return_type.type == CompSpecMsg_pb2.TYPE_SUBMODULE:
-                logging.info("returned a submodule spec")
-                logging.info("spec: %s", result.return_type_submodule_spec)
+                logging.debug("returned a submodule spec")
+                logging.debug("spec: %s", result.return_type_submodule_spec)
                 return mirror_object.MirrorObject(
                     self, result.return_type_submodule_spec, None)
             elif result.return_type.type == CompSpecMsg_pb2.TYPE_SCALAR:
@@ -432,7 +432,7 @@
         """
         self.SendCommand(SysMsg_pb2.PING)
         resp = self.RecvResponse()
-        logging.info("resp for PING: %s", resp)
+        logging.debug("resp for PING: %s", resp)
         if resp is not None and resp.response_code == SysMsg_pb2.SUCCESS:
             return True
         return False
@@ -459,9 +459,9 @@
             target_version=target_version,
             target_package=target_package)
         resp = self.RecvResponse(retries=2)
-        logging.info("resp for VTS_AGENT_COMMAND_EXECUTE_READ_INTERFACE: %s",
+        logging.debug("resp for VTS_AGENT_COMMAND_EXECUTE_READ_INTERFACE: %s",
                      resp)
-        logging.info("proto: %s", resp.result)
+        logging.debug("proto: %s", resp.result)
         result = CompSpecMsg_pb2.ComponentSpecificationMessage()
         if resp.result == "error":
             raise errors.VtsTcpCommunicationError(
@@ -521,10 +521,10 @@
 
         command_msg = SysMsg_pb2.AndroidSystemControlCommandMessage()
         command_msg.command_type = command_type
-        logging.info("sending a command (type %s)",
+        logging.debug("sending a command (type %s)",
                      COMMAND_TYPE_NAME[command_type])
         if command_type == 202:
-            logging.info("target API: %s", arg)
+            logging.debug("target API: %s", arg)
 
         if target_class is not None:
             command_msg.target_class = target_class
@@ -577,7 +577,7 @@
             else:
                 command_msg.shell_command.append(shell_command)
 
-        logging.info("command %s" % command_msg)
+        logging.debug("command %s" % command_msg)
         message = command_msg.SerializeToString()
         message_len = len(message)
         logging.debug("sending %d bytes", message_len)
@@ -598,7 +598,7 @@
                     logging.info("retrying...")
                 header = self.channel.readline().strip("\n")
                 length = int(header) if header else 0
-                logging.info("resp %d bytes", length)
+                logging.debug("resp %d bytes", length)
                 data = self.channel.read(length)
                 response_msg = SysMsg_pb2.AndroidSystemControlResponseMessage()
                 response_msg.ParseFromString(data)
diff --git a/runners/host/tcp_server/callback_server.py b/runners/host/tcp_server/callback_server.py
index f61c19e..cdb23e2 100644
--- a/runners/host/tcp_server/callback_server.py
+++ b/runners/host/tcp_server/callback_server.py
@@ -167,7 +167,7 @@
             server_thread = threading.Thread(target=self._server.serve_forever)
             server_thread.daemon = True
             server_thread.start()
-            logging.info('TcpServer %s started (%s:%s)', server_thread.name,
+            logging.debug('TcpServer %s started (%s:%s)', server_thread.name,
                          self._ip, self._port)
             return self._ip, self._port
         except (RuntimeError, IOError, socket.error) as e:
diff --git a/runners/host/tcp_server/callback_server_test.py b/runners/host/tcp_server/callback_server_test.py
index ca66573..3edc1b4 100644
--- a/runners/host/tcp_server/callback_server_test.py
+++ b/runners/host/tcp_server/callback_server_test.py
@@ -116,7 +116,7 @@
         sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
         host = self._callback_server.ip
         port = self._callback_server.port
-        logging.info('Sending Request to host %s using port %s', host, port)
+        logging.debug('Sending Request to host %s using port %s', host, port)
 
         try:
             # Connect to server and send request_message
@@ -124,12 +124,12 @@
 
             message = request_message.SerializeToString()
             sock.sendall(str(len(message)) + "\n" + message)
-            logging.info("Sent: %s", message)
+            logging.debug("Sent: %s", message)
 
             # Receive request_message from the server and shut down
             received_message = sock.recv(1024)
             response_message.ParseFromString(received_message)
-            logging.info('Received: %s', received_message)
+            logging.debug('Received: %s', received_message)
         except socket_error as e:
             logging.error(e)
             raise errors.TcpServerConnectionError('Exception occurred.')
diff --git a/runners/host/test_runner.py b/runners/host/test_runner.py
index 3a9c9b0..4980a02 100644
--- a/runners/host/test_runner.py
+++ b/runners/host/test_runner.py
@@ -106,14 +106,6 @@
     test_identifiers = [(test_cls_name, None)]
 
     for config in test_configs:
-        if keys.ConfigKeys.KEY_TEST_MAX_TIMEOUT in config:
-            timeout_sec = int(config[
-                keys.ConfigKeys.KEY_TEST_MAX_TIMEOUT]) / 1000.0
-        else:
-            timeout_sec = 60 * 60 * 3
-            logging.warning("%s unspecified. Set timeout to %s seconds.",
-                            keys.ConfigKeys.KEY_TEST_MAX_TIMEOUT, timeout_sec)
-
         watcher_enabled = threading.Event()
 
         def watchStdin():
@@ -199,7 +191,10 @@
         self.log_severity = self.test_configs.get(
             keys.ConfigKeys.KEY_LOG_SEVERITY, "INFO").upper()
         logger.setupTestLogger(
-            self.log_path, self.testbed_name, log_severity=self.log_severity)
+            self.log_path,
+            self.testbed_name,
+            filename="test_run_details.txt",
+            log_severity=self.log_severity)
         self.controller_registry = {}
         self.controller_destructors = {}
         self.run_list = run_list
@@ -310,7 +305,7 @@
             ControllerError is raised if no corresponding config can be found,
             or if the controller module has already been registered.
         """
-        logging.info("cwd: %s", os.getcwd())
+        logging.debug("cwd: %s", os.getcwd())
         logging.info("adb devices: %s", module.list_adb_devices())
         self.verifyControllerModule(module)
         module_ref_name = module.__name__.split('.')[-1]
@@ -335,7 +330,7 @@
                 for config in controller_config:
                     if isinstance(config, dict):
                         config["log_severity"] = self.log_severity
-            logging.info("controller_config: %s", controller_config)
+            logging.debug("controller_config: %s", controller_config)
             if "use_vts_agent" not in self.testbed_configs:
                 objects = create(controller_config, start_services)
             else:
diff --git a/runners/host/utils.py b/runners/host/utils.py
index 834ef1b..972d991 100755
--- a/runners/host/utils.py
+++ b/runners/host/utils.py
@@ -221,10 +221,10 @@
         pypath = os.environ['PYTHONPATH']
         if pypath:
             for base_path in pypath.split(':'):
-                logging.info('checking %s', base_path)
+                logging.debug('checking base_path %s', base_path)
                 new_path = os.path.join(base_path, file_full_path)
                 if os.path.isfile(new_path):
-                    logging.info('found')
+                    logging.debug('new_path %s found', new_path)
                     file_full_path = new_path
                     break
 
@@ -362,6 +362,36 @@
     return os.name == "nt"
 
 
+def kill_process_group(proc, signal_no=signal.SIGTERM):
+    """Sends signal to a process group.
+
+    Logs when there is an OSError or PermissionError. The latter one only
+    happens on Mac.
+
+    On Windows, SIGABRT, SIGINT, and SIGTERM are replaced with CTRL_BREAK_EVENT
+    so as to kill every subprocess in the group.
+
+    Args:
+        proc: The Popen object whose pid is the group id.
+        signal_no: The signal sent to the subprocess group.
+    """
+    pid = proc.pid
+    try:
+        if not is_on_windows():
+            os.killpg(pid, signal_no)
+        else:
+            if signal_no in [signal.SIGABRT,
+                             signal.SIGINT,
+                             signal.SIGTERM]:
+                windows_signal_no = signal.CTRL_BREAK_EVENT
+            else:
+                windows_signal_no = signal_no
+            os.kill(pid, windows_signal_no)
+    except (OSError, PermissionError) as e:
+        logging.exception("Cannot send signal %s to process group %d: %s",
+                          signal_no, pid, str(e))
+
+
 def start_standing_subprocess(cmd, check_health_delay=0):
     """Starts a long-running subprocess.
 
@@ -403,38 +433,19 @@
     return proc
 
 
-def stop_standing_subprocess(proc, kill_signal=signal.SIGTERM):
+def stop_standing_subprocess(proc, signal_no=signal.SIGTERM):
     """Stops a subprocess started by start_standing_subprocess.
 
     Before killing the process, we check if the process is running, if it has
     terminated, VTSUtilsError is raised.
 
-    Catches and logs the PermissionError which only happens on Macs.
-
-    On Windows, SIGABRT, SIGINT, and SIGTERM are replaced with CTRL_BREAK_EVENT
-    so as to kill every subprocess in the group.
-
     Args:
         proc: Subprocess to terminate.
-        kill_signal: The signal sent to the subprocess group.
+        signal_no: The signal sent to the subprocess group.
     """
-    pid = proc.pid
-    logging.debug("Stop standing subprocess %d", pid)
+    logging.debug("Stop standing subprocess %d", proc.pid)
     _assert_subprocess_running(proc)
-    if not is_on_windows():
-        try:
-            os.killpg(pid, kill_signal)
-        except PermissionError as e:
-            logging.warning("os.killpg(%d, %s) PermissionError: %s",
-                            pid, str(kill_signal), str(e))
-    else:
-        if kill_signal in [signal.SIGABRT,
-                           signal.SIGINT,
-                           signal.SIGTERM]:
-            windows_signal = signal.CTRL_BREAK_EVENT
-        else:
-            windows_signal = kill_signal
-        os.kill(pid, windows_signal)
+    kill_process_group(proc, signal_no)
 
 
 def wait_for_standing_subprocess(proc, timeout=None):
diff --git a/runners/target/vts_hal_hidl_target/Android.bp b/runners/target/vts_hal_hidl_target/Android.bp
index c74582f..cda56ff 100644
--- a/runners/target/vts_hal_hidl_target/Android.bp
+++ b/runners/target/vts_hal_hidl_target/Android.bp
@@ -25,14 +25,18 @@
     cflags: ["-Wall", "-Werror"],
 
     shared_libs: [
-        "libbase",
         "libhidlbase",
         "liblog",
-        "libutils",
         "libcutils",
     ],
 
-    static_libs : ["libgtest"],
+    static_libs : [
+        "libgtest",
+        "libutils",
+    ],
     export_include_dirs: ["."],
-    export_static_lib_headers: ["libgtest"],
+    export_static_lib_headers: [
+        "libgtest",
+        "libutils",
+    ],
 }
diff --git a/runners/target/vts_hal_hidl_target/VtsHalHidlTargetTestBase.h b/runners/target/vts_hal_hidl_target/VtsHalHidlTargetTestBase.h
index 8214630..dafeca2 100644
--- a/runners/target/vts_hal_hidl_target/VtsHalHidlTargetTestBase.h
+++ b/runners/target/vts_hal_hidl_target/VtsHalHidlTargetTestBase.h
@@ -17,11 +17,11 @@
 #ifndef __VTS_HAL_HIDL_TARGET_TEST_BASE_H
 #define __VTS_HAL_HIDL_TARGET_TEST_BASE_H
 
+#include <VtsHalHidlTargetTestEnvBase.h>
 #include <gtest/gtest.h>
 #include <hidl/HidlSupport.h>
-#include <utils/Log.h>
+#include <log/log.h>
 #include <utils/RefBase.h>
-#include <VtsHalHidlTargetTestEnvBase.h>
 
 #define VTS_HAL_HIDL_GET_STUB "VTS_HAL_HIDL_GET_STUB"
 
diff --git a/runners/target/vts_hal_hidl_target/VtsHalHidlTargetTestEnvBase.cpp b/runners/target/vts_hal_hidl_target/VtsHalHidlTargetTestEnvBase.cpp
index 6b6b6a1..cd96fbc 100644
--- a/runners/target/vts_hal_hidl_target/VtsHalHidlTargetTestEnvBase.cpp
+++ b/runners/target/vts_hal_hidl_target/VtsHalHidlTargetTestEnvBase.cpp
@@ -20,7 +20,7 @@
 
 #include <string>
 
-#include <utils/Log.h>
+#include <log/log.h>
 
 static constexpr const char* kListFlag = "--list_registered_services";
 static constexpr const char* kServiceInstanceFlag = "--hal_service_instance";
@@ -125,6 +125,7 @@
   for (string service : registeredHalServices_) {
     printf("hal_service: %s\n", service.c_str());
   }
+  printf("service_comb_mode: %d\n", mode_);
 }
 
 }  // namespace testing
diff --git a/runners/target/vts_hal_hidl_target/VtsHalHidlTargetTestEnvBase.h b/runners/target/vts_hal_hidl_target/VtsHalHidlTargetTestEnvBase.h
index 9fb44e5..4a554b9 100644
--- a/runners/target/vts_hal_hidl_target/VtsHalHidlTargetTestEnvBase.h
+++ b/runners/target/vts_hal_hidl_target/VtsHalHidlTargetTestEnvBase.h
@@ -25,6 +25,22 @@
 
 namespace testing {
 
+// Enum class indicates the required combination mode for registered services.
+enum HalServiceCombMode {
+  // Get the full permutation of all the registered service instances.
+  // E.g. Hal service s1 with instances (n1, n2) and s2 with instances (n3, n4),
+  // Return combination (s1/n1, s2/n3), (s1/n1, s2/n4), (s1/n2, s2/n3),
+  // (s1/n2, s2/n4).
+  FULL_PERMUTATION = 0,
+  // Get the registered service instances with the same service name.
+  // E.g. Hal service s1 with instances (n1, n2) and s2 with instances (n1, n2),
+  // Return combination (s1/n1, s2/n1), (s1/n2, s2/n2).
+  NAME_MATCH,
+  // Do not return the service instance combinations. This is used in cases when
+  // the test logic specifically handles the testing instances. E.g. drm tests.
+  NO_COMBINATION,
+};
+
 // A class for test environment setup
 class VtsHalHidlTargetTestEnvBase : public ::testing::Environment {
  public:
@@ -78,6 +94,8 @@
     return getServiceName(T::descriptor, defaultName);
   }
 
+  void setServiceCombMode(HalServiceCombMode mode) { mode_ = mode; }
+
  private:
   /*
    * Parses VTS specific flags, currently support two flags:
@@ -119,6 +137,8 @@
   bool listService_ = false;
   // Flag whether init is called.
   bool inited_ = false;
+  // Required combination mode for hal service instances.
+  HalServiceCombMode mode_ = HalServiceCombMode::FULL_PERMUTATION;
 };
 
 }  // namespace testing
diff --git a/script/create-test-project.py b/script/create-test-project.py
index 471dbe0..87f4788 100755
--- a/script/create-test-project.py
+++ b/script/create-test-project.py
@@ -42,16 +42,19 @@
         build_top: string, equal to environment variable ANDROID_BUILD_TOP
         test_dir: string, test case absolute directory
         test_name: string, test case name in UpperCamel
+        test_plan: string, the plan that the test belongs to
         test_type: test type, such as HidlHalTest, HostDrivenTest, etc
         current_year: current year
         vts_test_case_dir: absolute dir of vts testcases directory
     '''
 
-    def __init__(self, test_name, test_dir_under_testcases, test_type):
+    def __init__(self, test_name, test_plan, test_dir_under_testcases,
+                 test_type):
         '''Initialize class attributes.
 
         Args:
             test_name: string, test case name in UpperCamel
+            test_plan: string, the plan that the test belongs to
             test_dir_under_testcases: string, test case relative directory under
                                       test/vts/testcases.
         '''
@@ -66,6 +69,11 @@
             sys.exit(4)
         self.test_name = test_name
 
+        if not test_plan:
+            self.test_plan = 'vts-misc'
+        else:
+            self.test_plan = test_plan
+
         if not test_type:
             self.test_type = 'HidlHalTest'
         else:
@@ -178,6 +186,7 @@
             f.write(
                 ANDROID_TEST_XML_TEMPLATE.format(
                     test_name=self.test_name,
+                    test_plan=self.test_plan,
                     test_type=self.test_type,
                     test_path_under_vts=self.test_dir[
                         len(os.path.join(self.build_top, VTS_PATH)) + 1:],
@@ -202,6 +211,11 @@
         required=True,
         help='Test case name in UpperCamel. Example: VtsKernelLtp')
     parser.add_argument(
+        '--plan',
+        dest='test_plan',
+        required=False,
+        help='The plan that the test belongs to. Example: vts-kernel')
+    parser.add_argument(
         '--dir',
         dest='test_dir',
         required=True,
@@ -213,8 +227,8 @@
         help='Test type, such as HidlHalTest, HostDrivenTest, etc.')
 
     args = parser.parse_args()
-    test_case_creater = TestCaseCreator(args.test_name, args.test_dir,
-                                        args.test_type)
+    test_case_creater = TestCaseCreator(args.test_name, args.test_plan,
+                                        args.test_dir, args.test_type)
     test_case_creater.InitTestCaseDir()
 
 
@@ -271,11 +285,10 @@
 '''
 
 ANDROID_TEST_XML_TEMPLATE = '''<configuration description="Config for VTS {test_name} test cases">
+    <option name="config-descriptor:metadata" key="plan" value="{test_plan}" />
     <target_preparer class="com.android.compatibility.common.tradefed.targetprep.VtsFilePusher">
         <option name="push-group" value="{test_type}.push" />
     </target_preparer>
-    <target_preparer class="com.android.tradefed.targetprep.VtsPythonVirtualenvPreparer">
-    </target_preparer>
     <test class="com.android.tradefed.testtype.VtsMultiDeviceTest">
         <option name="test-module-name" value="{test_name}" />
         <option name="test-case-path" value="vts/{test_path_under_vts}/{test_case_file_without_extension}" />
diff --git a/script/cts_test_list.txt b/script/cts_test_list.txt
index 2fbf521..1af7d8a 100644
--- a/script/cts_test_list.txt
+++ b/script/cts_test_list.txt
@@ -40,7 +40,6 @@
 CtsDisplayTestCases
 CtsDpiTestCases
 CtsDpiTestCases2
-CtsDramTestCases
 CtsDreamsTestCases
 CtsDrmTestCases
 CtsDumpsysHostTestCases
@@ -70,7 +69,6 @@
 CtsKeystoreTestCases
 CtsLeanbackJankTestCases
 CtsLibcoreFileIOTestCases
-CtsLibcoreJavaUtilCollectionsTestCases
 CtsLibcoreJsr166TestCases
 CtsLibcoreLegacy22TestCases
 CtsLibcoreOjTestCases
diff --git a/script/diagnose.py b/script/diagnose.py
old mode 100644
new mode 100755
index 6a6b976..479c987
--- a/script/diagnose.py
+++ b/script/diagnose.py
@@ -51,6 +51,9 @@
     # Get target device info with adb
     runCommand('adb shell getprop ro.build.fingerprint', 'Target device info [ro.build.fingerprint]')
 
+    runCommand('adb shell lshal --init-vintf', 'Read HAL Info')
+    runCommand('adb shell cat /vendor/manifest.xml', 'Read vendor/manifest.xml')
+
 
 if __name__ == "__main__":
     main()
diff --git a/script/monitor-runner-output.py b/script/monitor-runner-output.py
deleted file mode 100755
index 243cb64..0000000
--- a/script/monitor-runner-output.py
+++ /dev/null
@@ -1,111 +0,0 @@
-#!/usr/bin/env python
-#
-# Copyright (C) 2016 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-import os
-import sys
-import time
-
-LOG_DIR = '/tmp'
-
-
-def GetLatestLog():
-    '''Find the latest vts runner log folder in log directory and return its content.
-
-    Returns:
-        (string, string), returns the latest log file path and log content
-    '''
-    folders = [os.path.join(LOG_DIR, folder_name)
-               for folder_name in os.listdir(LOG_DIR)
-               if os.path.isdir(os.path.join(LOG_DIR, folder_name)) and
-               folder_name.startswith('vts-runner-log')]
-
-    try:
-        folders.sort(
-            lambda folder1, folder2: int(os.path.getmtime(folder1) - os.path.getmtime(folder2)))
-        folder_latest = folders[-1]
-        log_path_latest = os.path.join(folder_latest,
-                                       os.listdir(folder_latest)[0], 'latest',
-                                       'test_run_details.txt')
-        with open(log_path_latest, 'r') as log_latest:
-            return (log_path_latest, log_latest.read())
-    except Exception as e:
-        return (None, None)
-
-
-def StartMonitoring(path_only):
-    '''Pull the latest vts runner log in a loop, and print out any new contents.
-
-    Args:
-        path_only: bool, only print out the latest log path in temporary directory.
-    '''
-    is_first_time = True
-    last_log_path = None
-    last_text = ''
-    while True:
-        log_path_latest, text_latest = GetLatestLog()
-
-        if path_only:
-            print log_path_latest
-            return
-
-        if log_path_latest is None:  # Case: cannot find any runner log
-            time.sleep(2)
-            continue
-
-        if last_log_path == log_path_latest:
-            text_new = text_latest[len(last_text):]
-            last_text = text_latest
-            if text_new:  # Case: runner log file changed
-                if is_first_time:
-                    is_first_time = False
-                    print text_new
-                    continue
-                lines = text_new.split('\n')
-                for l in lines:
-                    print l
-                    time.sleep(0.6 / len(lines))
-            else:  # Case: runner log file didn't change
-                time.sleep(1)
-        else:  # Case: found a new runner log file
-            last_text = ''
-            last_log_path = log_path_latest
-            print '\n' * 6 + '=' * 24 + 'new file' + '=' * 24 + '\n' * 6
-            time.sleep(1)
-
-
-def PrintUsage():
-    print 'A script to read VTS Runner\'s log from temporary directory.'
-    print 'Usage:'
-    print '  -h, --help: print usage.'
-    print '  -p, --path-only: print path to the latest runner file only.'
-    print '                   You may pipe the result to vim for searching.'
-    print '                   Example: script/monitor-runner-output.py --path-only | xargs gedit'
-    print '  -m, --monitor: print VTS runner\'s output in close to real time'
-    print '  If no argument is provided, this script will keep pulling the latest log and print it out.'
-
-
-if __name__ == "__main__":
-    argv = sys.argv
-    path_only = False
-    if len(argv) == 1 or argv[1] == '-h' or argv[1] == '--help':
-        PrintUsage()
-        exit()
-    elif argv[1] == '-p' or argv[1] == '--path-only':
-        path_only = True
-    elif argv[1] == '-m' or argv[1] == '--monitor':
-        path_only = False
-    StartMonitoring(path_only)
diff --git a/script/pip_requirements.txt b/script/pip_requirements.txt
index 90dd6bd..d89e230 100644
--- a/script/pip_requirements.txt
+++ b/script/pip_requirements.txt
@@ -18,3 +18,6 @@
 # Required packages for Kernel tests
 parse
 ply
+
+# Required packages for USB gadget tests
+libusb1
diff --git a/script/run-unittest.sh b/script/run-unittest.sh
index d4346ea..3943e51 100755
--- a/script/run-unittest.sh
+++ b/script/run-unittest.sh
@@ -17,6 +17,11 @@
 pushd ${ANDROID_BUILD_TOP}/test/vts
 PYTHONPATH=$PYTHONPATH:.. python -m vts.runners.host.tcp_server.callback_server_test
 PYTHONPATH=$PYTHONPATH:.. python -m vts.utils.python.coverage.coverage_report_test
-PYTHONPATH=$PYTHONPATH:.. python -m vts.harnesses.host_controller.build.pab_client_test
+PYTHONPATH=$PYTHONPATH:.. python -m vts.harnesses.host_controller.build.build_provider_pab_test
+PYTHONPATH=$PYTHONPATH:.. python -m vts.utils.python.controllers.android_device_test
+PYTHONPATH=$PYTHONPATH:.. python -m vts.utils.python.controllers.customflasher_test
+PYTHONPATH=$PYTHONPATH:..:../framework/harnesses/ python -m host_controller.build.build_flasher_test
+PYTHONPATH=$PYTHONPATH:..:../framework/harnesses/ python -m host_controller.build.build_provider_test
+PYTHONPATH=$PYTHONPATH:..:../framework/harnesses/ python -m host_controller.console_test
+PYTHONPATH=$PYTHONPATH:.. python -m vts.utils.python.io.file_util_test
 popd
-
diff --git a/script/run_cts_with_profiling.sh b/script/run_cts_with_profiling.sh
index d64158c..07cc8c7 100755
--- a/script/run_cts_with_profiling.sh
+++ b/script/run_cts_with_profiling.sh
@@ -14,89 +14,70 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-# allow write to /system partition
-adb root
-adb disable-verity
-adb reboot
-adb wait-for-device
-adb root
-adb remount
+serial_no=$1
+if [ -z "$serial_no" ]
+then
+  echo "Must provide serial number of the testing device."
+  exit
+fi
+
+local_trace_dir=$2
+if [ -z "$local_trace_dir" ]
+then
+  local_trace_dir=/usr/local/backup/cts-traces
+fi
+
+test_list=$3
+if [ -z "$test_list" ]
+then
+  test_list=${ANDROID_BUILD_TOP}/test/vts/script/cts_test_list.txt
+fi
+
+# allow write to /vendor partition
+adb -s $serial_no root
+adb -s $serial_no disable-verity
+adb -s $serial_no reboot
+adb -s $serial_no wait-for-device
+adb -s $serial_no root
+adb -s $serial_no remount
+adb -s $serial_no shell setenforce 0
+adb -s $serial_no shell chmod 777 -R data/local/tmp
 
 # push profiler libs
-adb push ${ANDROID_BUILD_TOP}/out/host/linux-x86/vts/android-vts/testcases/DATA/lib64/android.hardware.audio@2.0-vts.profiler.so system/lib64/
-adb push ${ANDROID_BUILD_TOP}/out/host/linux-x86/vts/android-vts/testcases/DATA/lib64/android.hardware.audio.common@2.0-vts.profiler.so system/lib64/
-adb push ${ANDROID_BUILD_TOP}/out/host/linux-x86/vts/android-vts/testcases/DATA/lib64/android.hardware.audio.effect@2.0-vts.profiler.so system/lib64/
-adb push ${ANDROID_BUILD_TOP}/out/host/linux-x86/vts/android-vts/testcases/DATA/lib64/android.hardware.automotive.vehicle@2.0-vts.profiler.so system/lib64/
-adb push ${ANDROID_BUILD_TOP}/out/host/linux-x86/vts/android-vts/testcases/DATA/lib64/android.hardware.automotive.vehicle@2.1-vts.profiler.so system/lib64/
-adb push ${ANDROID_BUILD_TOP}/out/host/linux-x86/vts/android-vts/testcases/DATA/lib64/android.hardware.biometrics.fingerprint@2.1-vts.profiler.so system/lib64/
-adb push ${ANDROID_BUILD_TOP}/out/host/linux-x86/vts/android-vts/testcases/DATA/lib64/android.hardware.bluetooth@1.0-vts.profiler.so system/lib64/
-adb push ${ANDROID_BUILD_TOP}/out/host/linux-x86/vts/android-vts/testcases/DATA/lib64/android.hardware.boot@1.0-vts.profiler.so system/lib64/
-adb push ${ANDROID_BUILD_TOP}/out/host/linux-x86/vts/android-vts/testcases/DATA/lib64/android.hardware.broadcastradio@1.0-vts.profiler.so system/lib64/
-adb push ${ANDROID_BUILD_TOP}/out/host/linux-x86/vts/android-vts/testcases/DATA/lib64/android.hardware.camera.common@1.0-vts.profiler.so system/lib64/
-adb push ${ANDROID_BUILD_TOP}/out/host/linux-x86/vts/android-vts/testcases/DATA/lib64/android.hardware.camera.device@1.0-vts.profiler.so system/lib64/
-adb push ${ANDROID_BUILD_TOP}/out/host/linux-x86/vts/android-vts/testcases/DATA/lib64/android.hardware.camera.device@3.2-vts.profiler.so system/lib64/
-adb push ${ANDROID_BUILD_TOP}/out/host/linux-x86/vts/android-vts/testcases/DATA/lib64/android.hardware.camera.metadata@3.2-vts.profiler.so system/lib64/
-adb push ${ANDROID_BUILD_TOP}/out/host/linux-x86/vts/android-vts/testcases/DATA/lib64/android.hardware.camera.provider@2.4-vts.profiler.so system/lib64/
-adb push ${ANDROID_BUILD_TOP}/out/host/linux-x86/vts/android-vts/testcases/DATA/lib64/android.hardware.configstore@1.0-vts.profiler.so system/lib64/
-adb push ${ANDROID_BUILD_TOP}/out/host/linux-x86/vts/android-vts/testcases/DATA/lib64/android.hardware.contexthub@1.0-vts.profiler.so system/lib64/
-adb push ${ANDROID_BUILD_TOP}/out/host/linux-x86/vts/android-vts/testcases/DATA/lib64/android.hardware.drm@1.0-vts.profiler.so system/lib64/
-adb push ${ANDROID_BUILD_TOP}/out/host/linux-x86/vts/android-vts/testcases/DATA/lib64/android.hardware.dumpstate@1.0-vts.profiler.so system/lib64/
-adb push ${ANDROID_BUILD_TOP}/out/host/linux-x86/vts/android-vts/testcases/DATA/lib64/android.hardware.gatekeeper@1.0-vts.profiler.so system/lib64/
-adb push ${ANDROID_BUILD_TOP}/out/host/linux-x86/vts/android-vts/testcases/DATA/lib64/android.hardware.gnss@1.0-vts.profiler.so system/lib64/
-adb push ${ANDROID_BUILD_TOP}/out/host/linux-x86/vts/android-vts/testcases/DATA/lib64/android.hardware.graphics.allocator@2.0-vts.profiler.so system/lib64/
-adb push ${ANDROID_BUILD_TOP}/out/host/linux-x86/vts/android-vts/testcases/DATA/lib64/android.hardware.graphics.common@1.0-vts.profiler.so system/lib64/
-adb push ${ANDROID_BUILD_TOP}/out/host/linux-x86/vts/android-vts/testcases/DATA/lib64/android.hardware.graphics.composer@2.1-vts.profiler.so system/lib64/
-adb push ${ANDROID_BUILD_TOP}/out/host/linux-x86/vts/android-vts/testcases/DATA/lib64/android.hardware.graphics.mapper@2.0-vts.profiler.so system/lib64/
-adb push ${ANDROID_BUILD_TOP}/out/host/linux-x86/vts/android-vts/testcases/DATA/lib64/android.hardware.health@1.0-vts.profiler.so system/lib64/
-adb push ${ANDROID_BUILD_TOP}/out/host/linux-x86/vts/android-vts/testcases/DATA/lib64/android.hardware.ir@1.0-vts.profiler.so system/lib64/
-adb push ${ANDROID_BUILD_TOP}/out/host/linux-x86/vts/android-vts/testcases/DATA/lib64/android.hardware.keymaster@3.0-vts.profiler.so system/lib64/
-adb push ${ANDROID_BUILD_TOP}/out/host/linux-x86/vts/android-vts/testcases/DATA/lib64/android.hardware.light@2.0-vts.profiler.so system/lib64/
-adb push ${ANDROID_BUILD_TOP}/out/host/linux-x86/vts/android-vts/testcases/DATA/lib64/android.hardware.media.omx@1.0-vts.profiler.so system/lib64/
-adb push ${ANDROID_BUILD_TOP}/out/host/linux-x86/vts/android-vts/testcases/DATA/lib64/android.hardware.memtrack@1.0-vts.profiler.so system/lib64/
-adb push ${ANDROID_BUILD_TOP}/out/host/linux-x86/vts/android-vts/testcases/DATA/lib64/android.hardware.nfc@1.0-vts.profiler.so system/lib64/
-adb push ${ANDROID_BUILD_TOP}/out/host/linux-x86/vts/android-vts/testcases/DATA/lib64/android.hardware.power@1.0-vts.profiler.so system/lib64/
-adb push ${ANDROID_BUILD_TOP}/out/host/linux-x86/vts/android-vts/testcases/DATA/lib64/android.hardware.radio@1.0-vts.profiler.so system/lib64/
-adb push ${ANDROID_BUILD_TOP}/out/host/linux-x86/vts/android-vts/testcases/DATA/lib64/android.hardware.sensors@1.0-vts.profiler.so system/lib64/
-adb push ${ANDROID_BUILD_TOP}/out/host/linux-x86/vts/android-vts/testcases/DATA/lib64/android.hardware.soundtrigger@2.0-vts.profiler.so system/lib64/
-adb push ${ANDROID_BUILD_TOP}/out/host/linux-x86/vts/android-vts/testcases/DATA/lib64/android.hardware.thermal@1.0-vts.profiler.so system/lib64/
-adb push ${ANDROID_BUILD_TOP}/out/host/linux-x86/vts/android-vts/testcases/DATA/lib64/android.hardware.tv.cec@1.0-vts.profiler.so system/lib64/
-adb push ${ANDROID_BUILD_TOP}/out/host/linux-x86/vts/android-vts/testcases/DATA/lib64/android.hardware.tv.input@1.0-vts.profiler.so system/lib64/
-adb push ${ANDROID_BUILD_TOP}/out/host/linux-x86/vts/android-vts/testcases/DATA/lib64/android.hardware.usb@1.0-vts.profiler.so system/lib64/
-adb push ${ANDROID_BUILD_TOP}/out/host/linux-x86/vts/android-vts/testcases/DATA/lib64/android.hardware.vibrator@1.0-vts.profiler.so system/lib64/
-adb push ${ANDROID_BUILD_TOP}/out/host/linux-x86/vts/android-vts/testcases/DATA/lib64/android.hardware.vr@1.0-vts.profiler.so system/lib64/
-adb push ${ANDROID_BUILD_TOP}/out/host/linux-x86/vts/android-vts/testcases/DATA/lib64/android.hardware.wifi@1.0-vts.profiler.so system/lib64/
-adb push ${ANDROID_BUILD_TOP}/out/host/linux-x86/vts/android-vts/testcases/DATA/lib64/android.hardware.wifi.supplicant@1.0-vts.profiler.so system/lib64/
+adb -s $serial_no push ${ANDROID_BUILD_TOP}/out/host/linux-x86/vts/android-vts/testcases/DATA/lib64/*-vts.profiler.so vendor/lib64/
+adb -s $serial_no push ${ANDROID_BUILD_TOP}/out/host/linux-x86/vts/android-vts/testcases/DATA/lib/*-vts.profiler.so vendor/lib/
+adb -s $serial_no push ${ANDROID_BUILD_TOP}/out/host/linux-x86/vts/android-vts/testcases/DATA/lib64/libvts_* vendor/lib64/
+adb -s $serial_no push ${ANDROID_BUILD_TOP}/out/host/linux-x86/vts/android-vts/testcases/DATA/lib/libvts_* vendor/lib/
 
-adb push ${ANDROID_BUILD_TOP}/out/host/linux-x86/vts/android-vts/testcases/DATA/bin/vts_profiling_configure /data/local/tmp/
+# push vts_profiling_configure
+adb -s $serial_no push ${ANDROID_BUILD_TOP}/out/host/linux-x86/vts/android-vts/testcases/DATA/bin/vts_profiling_configure /data/local/tmp/
 
 # get cts testcases
 tests=()
 while read -r test
 do
   tests+=($test)
-done < "${ANDROID_BUILD_TOP}/test/vts/script/cts_test_list.txt"
+done < "$test_list"
 
 # run cts testcases
-DATE=`date +%Y-%m-%d`
 for i in ${tests[@]}
 do
   echo Running $i
-  adb shell rm /data/local/tmp/*.vts.trace
-  adb reboot
-  adb wait-for-device
-  adb root
-  sleep 5
-  adb shell setenforce 0
-  adb shell chmod 777 -R data/local/tmp
-  adb shell ./data/local/tmp/vts_profiling_configure enable /system/lib64/
-  cts-tradefed run commandAndExit cts --skip-device-info --skip-system-status-check com.android.compatibility.common.tradefed.targetprep.NetworkConnectivityChecker --skip-system-status-check com.android.tradefed.suite.checker.KeyguardStatusChecker -m $i
-  adb shell ls /data/local/tmp/*.vts.trace > temp
-  trace_path=$1/cts-traces/$DATE/$i
+  adb -s $serial_no shell rm /data/local/tmp/*.vts.trace
+  adb -s $serial_no shell ./data/local/tmp/vts_profiling_configure enable /vendor/lib/ /vendor/lib64/
+  cts-tradefed run commandAndExit cts -s $serial_no --primary-abi-only --skip-device-info \
+  --skip-system-status-check com.android.compatibility.common.tradefed.targetprep.NetworkConnectivityChecker \
+  --skip-system-status-check com.android.tradefed.suite.checker.KeyguardStatusChecker -m $i
+  # In case device restart during the test run.
+  adb -s $serial_no root
+  adb -s $serial_no shell setenforce 0
+  adb -s $serial_no shell ls /data/local/tmp/*.vts.trace > temp
+  trace_path=$local_trace_dir/$i
   rm -rf $trace_path
   mkdir -p $trace_path
   while read -r trace
   do
-    adb pull $trace $trace_path
+    adb -s $serial_no pull $trace $trace_path
   done < "temp"
 done
 
diff --git a/script/setup.sh b/script/setup.sh
index 7ca29e8..dec3adb 100755
--- a/script/setup.sh
+++ b/script/setup.sh
@@ -27,8 +27,12 @@
 sudo apt-get -y install python-pip
 sudo apt-get -y install python3-pip
 sudo apt-get -y install python-virtualenv
+sudo apt-get -y install build-essential
 
 echo "Install packages for Camera ITS tests"
 sudo apt-get -y install python-tk
 sudo apt-get -y install libjpeg-dev
 sudo apt-get -y install libtiff-dev
+
+echo "Install packaged for usb gadget tests"
+sudo pip install --upgrade libusb1
diff --git a/script/target/vts_adapter.sh b/script/target/vts_adapter.sh
new file mode 100755
index 0000000..d22240d
--- /dev/null
+++ b/script/target/vts_adapter.sh
@@ -0,0 +1,4 @@
+#!/system/bin/sh
+echo "start $1 -p $2 $3 $4"
+nohup $1 -p $2 $3 $4 &>/dev/null &
+echo "done"
diff --git a/testcases/Android.bp b/testcases/Android.bp
new file mode 100644
index 0000000..cb54aaa
--- /dev/null
+++ b/testcases/Android.bp
@@ -0,0 +1,18 @@
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//       http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+subdirs = [
+     "manual_tests/flaky_test",
+     "manual_tests/zero_testcase_binary_test",
+]
diff --git a/testcases/codelab/hello_world/Android.mk b/testcases/codelab/hello_world/Android.mk
index f5e9f5b..112126d 100644
--- a/testcases/codelab/hello_world/Android.mk
+++ b/testcases/codelab/hello_world/Android.mk
@@ -17,5 +17,4 @@
 
 include $(CLEAR_VARS)
 LOCAL_MODULE := VtsCodelabHelloWorldTest
-VTS_CONFIG_SRC_DIR := testcases/codelab/hello_world
 include test/vts/tools/build/Android.host_config.mk
diff --git a/testcases/codelab/hello_world/AndroidTest.xml b/testcases/codelab/hello_world/AndroidTest.xml
index 54188b9..31ea20d 100644
--- a/testcases/codelab/hello_world/AndroidTest.xml
+++ b/testcases/codelab/hello_world/AndroidTest.xml
@@ -14,11 +14,10 @@
      limitations under the License.
 -->
 <configuration description="Config for VTS CodeLab HelloWorld test case">
+    <option name="config-descriptor:metadata" key="plan" value="vts-codelab" />
     <target_preparer class="com.android.compatibility.common.tradefed.targetprep.VtsFilePusher">
         <option name="push-group" value="HostDrivenTest.push" />
     </target_preparer>
-    <target_preparer class="com.android.tradefed.targetprep.VtsPythonVirtualenvPreparer">
-    </target_preparer>
     <test class="com.android.tradefed.testtype.VtsMultiDeviceTest">
         <option name="test-module-name" value="VtsCodelabHelloWorldTest"/>
         <option name="test-case-path" value="vts/testcases/codelab/hello_world/VtsCodelabHelloWorldTest" />
diff --git a/tools/vts-hc/Android.mk b/testcases/codelab/hello_world_multi/Android.mk
similarity index 85%
rename from tools/vts-hc/Android.mk
rename to testcases/codelab/hello_world_multi/Android.mk
index 7733142..6ef6730 100644
--- a/tools/vts-hc/Android.mk
+++ b/testcases/codelab/hello_world_multi/Android.mk
@@ -1,3 +1,4 @@
+#
 # Copyright (C) 2017 The Android Open Source Project
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -11,12 +12,9 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
-
+#
 LOCAL_PATH := $(call my-dir)
 
 include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := optional
-LOCAL_PREBUILT_EXECUTABLES := run
-include $(BUILD_HOST_PREBUILT)
-
+LOCAL_MODULE := VtsCodelabHelloWorldMultiDeviceTest
+include test/vts/tools/build/Android.host_config.mk
diff --git a/testcases/codelab/hello_world_multi/AndroidTest.xml b/testcases/codelab/hello_world_multi/AndroidTest.xml
new file mode 100644
index 0000000..f123c20
--- /dev/null
+++ b/testcases/codelab/hello_world_multi/AndroidTest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="Config for VTS CodeLab HelloWorld test case">
+    <option name="config-descriptor:metadata" key="plan" value="vts-misc" />
+
+    <test class="com.android.tradefed.testtype.VtsMultiDeviceTest">
+        <option name="test-module-name" value="VtsCodelabHelloWorldMultiDeviceTest"/>
+        <option name="test-case-path" value="vts/testcases/codelab/hello_world_multi/VtsCodelabHelloWorldMultiDeviceTest" />
+    </test>
+
+</configuration>
diff --git a/testcases/codelab/hello_world_multi/VtsCodelabHelloWorldMultiDeviceTest.py b/testcases/codelab/hello_world_multi/VtsCodelabHelloWorldMultiDeviceTest.py
new file mode 100644
index 0000000..39c2434
--- /dev/null
+++ b/testcases/codelab/hello_world_multi/VtsCodelabHelloWorldMultiDeviceTest.py
@@ -0,0 +1,84 @@
+#!/usr/bin/env python
+#
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the 'License');
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an 'AS IS' BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+import logging
+
+from vts.runners.host import asserts
+from vts.runners.host import base_test
+from vts.runners.host import const
+from vts.runners.host import test_runner
+
+
+class VtsCodelabHelloWorldTest(base_test.BaseTestClass):
+    '''Two hello world test cases which use the shell driver.'''
+
+    def setUpClass(self):
+        logging.info('number of device: %s', self.android_devices)
+
+        asserts.assertEqual(
+            len(self.android_devices), 2, 'number of device is wrong.')
+
+        self.dut1 = self.android_devices[0]
+        self.dut2 = self.android_devices[1]
+        self.shell1 = self.dut1.shell
+        self.shell2 = self.dut2.shell
+
+    def testHelloWorld(self):
+        '''A simple testcase which sends a hello world command.'''
+        command = 'echo Hello World!'
+
+        res1 = self.shell1.Execute(command)
+        res2 = self.shell2.Execute(command)
+
+        asserts.assertFalse(
+            any(res1[const.EXIT_CODE]),
+            'command for device 1 failed: %s' % res1)  # checks the exit code
+        asserts.assertFalse(
+            any(res2[const.EXIT_CODE]),
+            'command for device 2 failed: %s' % res2)  # checks the exit code
+
+    def testSerialNotEqual(self):
+        '''Checks serial number from two device not being equal.'''
+        command = 'getprop | grep ro.serial'
+
+        res1 = self.shell1.Execute(command)
+        res2 = self.shell2.Execute(command)
+
+        asserts.assertFalse(
+            any(res1[const.EXIT_CODE]),
+            'command for device 1 failed: %s' % res1)  # checks the exit code
+        asserts.assertFalse(
+            any(res2[const.EXIT_CODE]),
+            'command for device 2 failed: %s' % res2)  # checks the exit code
+
+        def getSerial(output):
+            '''Get serial from getprop query'''
+            return output.strip().split(' ')[-1][1:-1]
+
+        serial1 = getSerial(res1[const.STDOUT][0])
+        serial2 = getSerial(res2[const.STDOUT][0])
+
+        logging.info('Serial number of device 1: %s', serial1)
+        logging.info('Serial number of device 2: %s', serial2)
+
+        asserts.assertNotEqual(
+            serial1, serial2,
+            'serials from two devices should not be the same')
+
+
+if __name__ == '__main__':
+    test_runner.main()
diff --git a/harnesses/host_controller/__init__.py b/testcases/codelab/hello_world_multi/__init__.py
similarity index 100%
copy from harnesses/host_controller/__init__.py
copy to testcases/codelab/hello_world_multi/__init__.py
diff --git a/testcases/codelab/hello_world_sl4a/Android.mk b/testcases/codelab/hello_world_sl4a/Android.mk
index 9842223..f84424f 100644
--- a/testcases/codelab/hello_world_sl4a/Android.mk
+++ b/testcases/codelab/hello_world_sl4a/Android.mk
@@ -17,5 +17,4 @@
 
 include $(CLEAR_VARS)
 LOCAL_MODULE := VtsCodelabHelloWorldSl4aTest
-VTS_CONFIG_SRC_DIR := testcases/codelab/hello_world_sl4a
 include test/vts/tools/build/Android.host_config.mk
diff --git a/testcases/codelab/hello_world_sl4a/AndroidTest.xml b/testcases/codelab/hello_world_sl4a/AndroidTest.xml
index ad22896..83111f9 100644
--- a/testcases/codelab/hello_world_sl4a/AndroidTest.xml
+++ b/testcases/codelab/hello_world_sl4a/AndroidTest.xml
@@ -17,8 +17,6 @@
     <target_preparer class="com.android.compatibility.common.tradefed.targetprep.VtsFilePusher">
         <option name="push-group" value="HostDrivenTest.push" />
     </target_preparer>
-    <target_preparer class="com.android.tradefed.targetprep.VtsPythonVirtualenvPreparer">
-    </target_preparer>
     <target_preparer class="com.android.compatibility.common.tradefed.targetprep.ApkInstaller">
         <option name="test-file-name" value="DATA/app/sl4a/sl4a.apk" />
         <option name="cleanup-apks" value="true" />
@@ -26,6 +24,6 @@
     <test class="com.android.tradefed.testtype.VtsMultiDeviceTest">
         <option name="test-module-name" value="VtsCodelabHelloWorldSl4aTest"/>
         <option name="test-case-path" value="vts/testcases/codelab/hello_world_sl4a/VtsCodelabHelloWorldSl4aTest" />
-        <option name="test-config-path" value="vts/testcases/codelab/hello_world_sl4a/VtsCodelabHelloWorldSl4aTest.config" />
+        <option name="test-config-path" value="vts/testcases/codelab/hello_world_sl4a/VtsCodelabHelloWorldSl4aTest.runner_conf" />
     </test>
 </configuration>
diff --git a/testcases/codelab/hello_world_sl4a/VtsCodelabHelloWorldSl4aTest.config b/testcases/codelab/hello_world_sl4a/VtsCodelabHelloWorldSl4aTest.runner_conf
similarity index 100%
rename from testcases/codelab/hello_world_sl4a/VtsCodelabHelloWorldSl4aTest.config
rename to testcases/codelab/hello_world_sl4a/VtsCodelabHelloWorldSl4aTest.runner_conf
diff --git a/testcases/codelab/hello_world_staging/Android.mk b/testcases/codelab/hello_world_staging/Android.mk
index 7214323..331c7ec 100644
--- a/testcases/codelab/hello_world_staging/Android.mk
+++ b/testcases/codelab/hello_world_staging/Android.mk
@@ -19,5 +19,4 @@
 include $(CLEAR_VARS)
 
 LOCAL_MODULE := VtsCodelabHelloWorldStagingTest
-VTS_CONFIG_SRC_DIR := testcases/codelab/hello_world_staging
 include test/vts/tools/build/Android.host_config.mk
diff --git a/testcases/codelab/hello_world_staging/AndroidTest.xml b/testcases/codelab/hello_world_staging/AndroidTest.xml
index c22a03d..39f536f 100644
--- a/testcases/codelab/hello_world_staging/AndroidTest.xml
+++ b/testcases/codelab/hello_world_staging/AndroidTest.xml
@@ -14,11 +14,10 @@
      limitations under the License.
 -->
 <configuration description="Config for VTS CodeLab HelloWorld Staging test case">
+    <option name="config-descriptor:metadata" key="plan" value="vts-codelab" />
     <target_preparer class="com.android.compatibility.common.tradefed.targetprep.VtsFilePusher">
         <option name="push-group" value="HostDrivenTest.push" />
     </target_preparer>
-    <target_preparer class="com.android.tradefed.targetprep.VtsPythonVirtualenvPreparer">
-    </target_preparer>
     <test class="com.android.tradefed.testtype.VtsMultiDeviceTest">
         <option name="test-module-name" value="VtsCodelabHelloWorldStagingTest" />
         <option name="test-case-path" value="vts/testcases/codelab/hello_world/VtsCodelabHelloWorldTest" />
diff --git a/testcases/codelab/host_multi_hal/Android.mk b/testcases/codelab/host_multi_hal/Android.mk
index 61b4dba..67e6405 100644
--- a/testcases/codelab/host_multi_hal/Android.mk
+++ b/testcases/codelab/host_multi_hal/Android.mk
@@ -19,5 +19,4 @@
 include $(CLEAR_VARS)
 
 LOCAL_MODULE := VtsCodelabHostDrivenMultiHalTest
-VTS_CONFIG_SRC_DIR := testcases/codelab/host_multi_hal
 include test/vts/tools/build/Android.host_config.mk
diff --git a/testcases/codelab/host_multi_hal/AndroidTest.xml b/testcases/codelab/host_multi_hal/AndroidTest.xml
index 73be01b..f8dba0b 100644
--- a/testcases/codelab/host_multi_hal/AndroidTest.xml
+++ b/testcases/codelab/host_multi_hal/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for VtsCodelabHostDrivenMultiHalTest test case">
+    <option name="config-descriptor:metadata" key="plan" value="vts-misc" />
     <target_preparer class="com.android.compatibility.common.tradefed.targetprep.VtsFilePusher">
         <option name="push-group" value="HostDrivenTest.push" />
         <option name="push" value="spec/hardware/interfaces/thermal/1.0/vts/Thermal.vts->/data/local/tmp/spec/android/hardware/thermal/1.0/Theraml.vts"/>
@@ -25,8 +26,6 @@
         <option name="push" value="DATA/lib/android.hardware.light@2.0-vts.driver.so->/data/local/tmp/32/android.hardware.light@2.0-vts.driver.so"/>
         <option name="push" value="DATA/lib64/android.hardware.light@2.0-vts.driver.so->/data/local/tmp/64/android.hardware.light@2.0-vts.driver.so"/>
     </target_preparer>
-    <target_preparer class="com.android.tradefed.targetprep.VtsPythonVirtualenvPreparer">
-    </target_preparer>
     <test class="com.android.tradefed.testtype.VtsMultiDeviceTest">
         <option name="test-module-name" value="VtsCodelabHostDrivenMultiHalTest" />
         <option name="test-case-path" value="vts/testcases/codelab/host_multi_hal/VtsCodelabHostMultiHalTest" />
diff --git a/testcases/codelab/target_binary/Android.mk b/testcases/codelab/target_binary/Android.mk
index fe5ba4b..b46364d 100644
--- a/testcases/codelab/target_binary/Android.mk
+++ b/testcases/codelab/target_binary/Android.mk
@@ -26,5 +26,4 @@
 
 include $(CLEAR_VARS)
 LOCAL_MODULE := VtsCodelabTargetBinary
-VTS_CONFIG_SRC_DIR := testcases/codelab/target_binary
 include test/vts/tools/build/Android.host_config.mk
diff --git a/testcases/codelab/target_binary/AndroidTest.xml b/testcases/codelab/target_binary/AndroidTest.xml
index b399a36..bd5120f 100644
--- a/testcases/codelab/target_binary/AndroidTest.xml
+++ b/testcases/codelab/target_binary/AndroidTest.xml
@@ -14,12 +14,14 @@
      limitations under the License.
 -->
 <configuration description="Config for VTS Target-Side Binary Codelab test cases">
+    <option name="config-descriptor:metadata" key="plan" value="vts-codelab" />
+    <option name="config-descriptor:metadata" key="plan" value="vts-staging-selftest" />
+
     <target_preparer class="com.android.compatibility.common.tradefed.targetprep.VtsFilePusher">
         <option name="push-group" value="HostDrivenTest.push" />
         <option name="push" value="DATA/nativetest/vts_codelab_target_binary->/data/nativetest/vts_codelab_target_binary" />
         <option name="push" value="DATA/nativetest64/vts_codelab_target_binary->/data/nativetest64/vts_codelab_target_binary" />
     </target_preparer>
-    <target_preparer class="com.android.tradefed.targetprep.VtsPythonVirtualenvPreparer" />
     <test class="com.android.tradefed.testtype.VtsMultiDeviceTest">
         <option name="test-module-name" value="VtsCodelabTargetBinary" />
         <option name="binary-test-source" value="_32bit::DATA/nativetest/vts_codelab_target_binary/vts_codelab_target_binary->/data/nativetest/vts_codelab_target_binary/vts_codelab_target_binary" />
diff --git a/testcases/codelab/target_binary/vts_codelab_target_binary.c b/testcases/codelab/target_binary/vts_codelab_target_binary.c
index 3f15d8e..3b2038e 100644
--- a/testcases/codelab/target_binary/vts_codelab_target_binary.c
+++ b/testcases/codelab/target_binary/vts_codelab_target_binary.c
@@ -17,7 +17,7 @@
 #include <stdio.h>
 
 #define LOG_TAG "VtsCodelabTargetBinary"
-#include <utils/Log.h>
+#include <log/log.h>
 
 int main() {
   printf("hello Android");
diff --git a/testcases/fuzz/lib_bionic/LibBionicLibmFuzzTest.config b/testcases/fuzz/lib_bionic/LibBionicLibmFuzzTest.runner_conf
similarity index 100%
rename from testcases/fuzz/lib_bionic/LibBionicLibmFuzzTest.config
rename to testcases/fuzz/lib_bionic/LibBionicLibmFuzzTest.runner_conf
diff --git a/testcases/host/camera_its/Android.mk b/testcases/host/camera_its/Android.mk
index d29f058..addbe82 100644
--- a/testcases/host/camera_its/Android.mk
+++ b/testcases/host/camera_its/Android.mk
@@ -18,5 +18,4 @@
 include $(CLEAR_VARS)
 
 LOCAL_MODULE := CameraITSTest
-VTS_CONFIG_SRC_DIR := testcases/host/camera_its
 include test/vts/tools/build/Android.host_config.mk
diff --git a/testcases/host/camera_its/AndroidTest.xml b/testcases/host/camera_its/AndroidTest.xml
index 0973b6e..cb849c7 100644
--- a/testcases/host/camera_its/AndroidTest.xml
+++ b/testcases/host/camera_its/AndroidTest.xml
@@ -14,21 +14,23 @@
      limitations under the License.
 -->
 <configuration description="Config for VTS CameraITS test case">
+    <option name="config-descriptor:metadata" key="plan" value="vts-misc" />
     <target_preparer class="com.android.compatibility.common.tradefed.targetprep.VtsFilePusher">
         <option name="push-group" value="HostDrivenTest.push" />
         <option name="cleanup" value="true" />
     </target_preparer>
-    <target_preparer class="com.android.tradefed.targetprep.VtsPythonVirtualenvPreparer">
+    <multi_target_preparer class="com.android.tradefed.targetprep.VtsPythonVirtualenvPreparer">
         <option name="dep-module" value="numpy" />
         <option name="dep-module" value="scipy" />
         <option name="dep-module" value="matplotlib" />
         <option name="dep-module" value="Pillow" />
-    </target_preparer>
+    </multi_target_preparer>
     <target_preparer class="com.android.compatibility.common.tradefed.targetprep.ApkInstaller">
         <option name="test-file-name" value="DATA/app/CtsVerifier/CtsVerifier.apk" />
         <option name="cleanup-apks" value="true" />
     </target_preparer>
     <test class="com.android.tradefed.testtype.VtsMultiDeviceTest">
+        <option name="test-module-name" value="CameraITSTest" />
         <option name="test-case-path" value="vts/testcases/host/camera_its/CameraITSTest" />
     </test>
 </configuration>
diff --git a/testcases/host/reboot/RebootRootRemountTest/Android.mk b/testcases/host/reboot/RebootRootRemountTest/Android.mk
index 40d167e..1356c5e 100644
--- a/testcases/host/reboot/RebootRootRemountTest/Android.mk
+++ b/testcases/host/reboot/RebootRootRemountTest/Android.mk
@@ -18,5 +18,4 @@
 include $(CLEAR_VARS)
 
 LOCAL_MODULE := RebootRootRemountTest
-VTS_CONFIG_SRC_DIR := testcases/host/reboot/RebootRootRemountTest
 include test/vts/tools/build/Android.host_config.mk
diff --git a/testcases/host/reboot/RebootRootRemountTest/AndroidTest.xml b/testcases/host/reboot/RebootRootRemountTest/AndroidTest.xml
index a4873ef..08dfe24 100644
--- a/testcases/host/reboot/RebootRootRemountTest/AndroidTest.xml
+++ b/testcases/host/reboot/RebootRootRemountTest/AndroidTest.xml
@@ -14,11 +14,10 @@
      limitations under the License.
 -->
 <configuration description="Config for VTS HAL shell test cases">
+    <option name="config-descriptor:metadata" key="plan" value="vts-misc" />
     <target_preparer class="com.android.compatibility.common.tradefed.targetprep.VtsFilePusher">
         <option name="push-group" value="HostDrivenTest.push" />
     </target_preparer>
-    <target_preparer class="com.android.tradefed.targetprep.VtsPythonVirtualenvPreparer">
-    </target_preparer>
     <test class="com.android.tradefed.testtype.VtsMultiDeviceTest">
         <option name="test-module-name" value="RebootRootRemountTest" />
         <option name="test-case-path" value="vts/testcases/host/reboot/RebootRootRemountTest/RebootRootRemountTest" />
diff --git a/testcases/host/reboot/RebootTest/Android.mk b/testcases/host/reboot/RebootTest/Android.mk
index dbd8e9f..0c24451 100644
--- a/testcases/host/reboot/RebootTest/Android.mk
+++ b/testcases/host/reboot/RebootTest/Android.mk
@@ -18,5 +18,4 @@
 include $(CLEAR_VARS)
 
 LOCAL_MODULE := RebootTest
-VTS_CONFIG_SRC_DIR := testcases/host/reboot/RebootTest
 include test/vts/tools/build/Android.host_config.mk
diff --git a/testcases/host/reboot/RebootTest/AndroidTest.xml b/testcases/host/reboot/RebootTest/AndroidTest.xml
index b520088..33606e1 100644
--- a/testcases/host/reboot/RebootTest/AndroidTest.xml
+++ b/testcases/host/reboot/RebootTest/AndroidTest.xml
@@ -14,11 +14,10 @@
      limitations under the License.
 -->
 <configuration description="Config for VTS HAL shell test cases">
+    <option name="config-descriptor:metadata" key="plan" value="vts-misc" />
     <target_preparer class="com.android.compatibility.common.tradefed.targetprep.VtsFilePusher">
         <option name="push-group" value="HostDrivenTest.push" />
     </target_preparer>
-    <target_preparer class="com.android.tradefed.targetprep.VtsPythonVirtualenvPreparer">
-    </target_preparer>
     <test class="com.android.tradefed.testtype.VtsMultiDeviceTest">
         <option name="test-module-name" value="RebootTest" />
         <option name="test-case-path" value="vts/testcases/host/reboot/RebootTest/RebootTest" />
diff --git a/testcases/host/shell/Android.mk b/testcases/host/shell/Android.mk
index c150da6..13ffbd0 100644
--- a/testcases/host/shell/Android.mk
+++ b/testcases/host/shell/Android.mk
@@ -18,5 +18,4 @@
 include $(CLEAR_VARS)
 
 LOCAL_MODULE := SampleShellTest
-VTS_CONFIG_SRC_DIR := testcases/host/shell
 include test/vts/tools/build/Android.host_config.mk
diff --git a/testcases/host/shell/AndroidTest.xml b/testcases/host/shell/AndroidTest.xml
index 4da0c4e..859a795 100644
--- a/testcases/host/shell/AndroidTest.xml
+++ b/testcases/host/shell/AndroidTest.xml
@@ -14,12 +14,13 @@
      limitations under the License.
 -->
 <configuration description="Config for VTS HAL shell test cases">
+    <option name="config-descriptor:metadata" key="plan" value="vts-host" />
+    <option name="config-descriptor:metadata" key="plan" value="vts-misc" />
     <target_preparer class="com.android.compatibility.common.tradefed.targetprep.VtsFilePusher">
         <option name="push-group" value="HostDrivenTest.push" />
     </target_preparer>
-    <target_preparer class="com.android.tradefed.targetprep.VtsPythonVirtualenvPreparer">
-    </target_preparer>
     <test class="com.android.tradefed.testtype.VtsMultiDeviceTest">
+        <option name="test-module-name" value="SampleShellTest" />
         <option name="test-case-path" value="vts/testcases/host/shell/SampleShellTest" />
     </test>
 </configuration>
diff --git a/testcases/host/shell/SampleShellTest.py b/testcases/host/shell/SampleShellTest.py
index cc1424c..32bfca2 100644
--- a/testcases/host/shell/SampleShellTest.py
+++ b/testcases/host/shell/SampleShellTest.py
@@ -30,12 +30,12 @@
     REPEAT_COUNT = 10
 
     def setUpClass(self):
-        self.dut = self.registerController(android_device)[0]
+        self.dut = self.android_devices[0]
 
     def testOneCommand(self):
         """A simple testcase which just emulates a normal usage pattern."""
         self.dut.shell.InvokeTerminal("my_shell1")
-        results = self.dut.shell.my_shell1.Execute("which ls")
+        results = self.dut.shell.Execute("which ls")
         logging.info(str(results[const.STDOUT]))
         asserts.assertEqual(len(results[const.STDOUT]), 1)
         asserts.assertEqual(results[const.STDOUT][0].strip(), "/system/bin/ls")
diff --git a/testcases/host/shell_binary_crash_test/Android.mk b/testcases/host/shell_binary_crash_test/Android.mk
index c11f739..5dd74ef 100644
--- a/testcases/host/shell_binary_crash_test/Android.mk
+++ b/testcases/host/shell_binary_crash_test/Android.mk
@@ -37,6 +37,25 @@
 
 include $(CLEAR_VARS)
 
+LOCAL_MODULE := vts_test_binary_seg_fault
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_SRC_FILES := \
+  seg_fault.c \
+
+LOCAL_SHARED_LIBRARIES := \
+  libutils \
+  libcutils \
+  liblog \
+
+LOCAL_C_INCLUDES += \
+  bionic \
+
+LOCAL_CFLAGS := -Werror -Wall
+
+include $(BUILD_EXECUTABLE)
+
+include $(CLEAR_VARS)
+
 LOCAL_MODULE := ShellBinaryCrashTest
-VTS_CONFIG_SRC_DIR := testcases/host/shell_binary_crash_test
 include test/vts/tools/build/Android.host_config.mk
diff --git a/testcases/host/shell_binary_crash_test/AndroidTest.xml b/testcases/host/shell_binary_crash_test/AndroidTest.xml
index c15c668..d7540e8 100644
--- a/testcases/host/shell_binary_crash_test/AndroidTest.xml
+++ b/testcases/host/shell_binary_crash_test/AndroidTest.xml
@@ -14,16 +14,16 @@
      limitations under the License.
 -->
 <configuration description="Config for VTS HAL shell binary crash test cases">
+    <option name="config-descriptor:metadata" key="plan" value="vts-host" />
+    <option name="config-descriptor:metadata" key="plan" value="vts-misc" />
     <target_preparer class="com.android.compatibility.common.tradefed.targetprep.VtsFilePusher">
         <option name="push-group" value="HostDrivenTest.push" />
         <option name="cleanup" value="true" />
-        <option name="push" value="DATA/bin/vts_test_binary_crash_app->/data/local/tmp/64/vts_test_binary_crash_app" />
-        <option name="push" value="DATA/nativetest64/ltp/testcases/bin/connect01->/data/local/tmp/64/connect01" />
-        <option name="push" value="DATA/nativetest/ltp/testcases/bin/connect01->/data/local/tmp/32/connect01" />
-    </target_preparer>
-    <target_preparer class="com.android.tradefed.targetprep.VtsPythonVirtualenvPreparer">
+        <option name="push" value="DATA/bin/vts_test_binary_crash_app->/data/local/tmp/vts_test_binary_crash_app" />
+        <option name="push" value="DATA/bin/vts_test_binary_seg_fault->/data/local/tmp/vts_test_binary_seg_fault" />
     </target_preparer>
     <test class="com.android.tradefed.testtype.VtsMultiDeviceTest">
+        <option name="test-module-name" value="ShellBinaryCrashTest" />
         <option name="test-case-path" value="vts/testcases/host/shell_binary_crash_test/ShellBinaryCrashTest" />
     </test>
 </configuration>
diff --git a/testcases/host/shell_binary_crash_test/ShellBinaryCrashTest.py b/testcases/host/shell_binary_crash_test/ShellBinaryCrashTest.py
index 2cac12f..8244d71 100644
--- a/testcases/host/shell_binary_crash_test/ShellBinaryCrashTest.py
+++ b/testcases/host/shell_binary_crash_test/ShellBinaryCrashTest.py
@@ -31,12 +31,12 @@
     EXIT_CODE_SEGFAULT = 139
 
     def setUpClass(self):
-        self.dut = self.registerController(android_device)[0]
+        self.dut = self.android_devices[0]
 
     def testCrashBinary(self):
         """Tests whether the agent survives when a called binary crashes."""
         self.dut.shell.InvokeTerminal("my_shell1")
-        target = "/data/local/tmp/64/vts_test_binary_crash_app"
+        target = "/data/local/tmp/vts_test_binary_crash_app"
         results = self.dut.shell.my_shell1.Execute(
             ["chmod 755 %s" % target, target])
         logging.info(str(results[const.STDOUT]))
@@ -51,7 +51,7 @@
     def testSegmentFaultBinary(self):
         """Tests whether the agent survives when a binary leads to segfault."""
         self.dut.shell.InvokeTerminal("my_shell1")
-        target = "/data/local/tmp/32/connect01"
+        target = "/data/local/tmp/vts_test_binary_seg_fault"
         results = self.dut.shell.my_shell1.Execute(
             ["chmod 755 %s" % target, target])
         logging.info(str(results[const.STDOUT]))
diff --git a/testcases/host/shell_binary_crash_test/crash_app.c b/testcases/host/shell_binary_crash_test/crash_app.c
index 2b2a79c..32e827c 100644
--- a/testcases/host/shell_binary_crash_test/crash_app.c
+++ b/testcases/host/shell_binary_crash_test/crash_app.c
@@ -16,7 +16,7 @@
 #include <stdio.h>
 
 #define LOG_TAG "VtsTestBinary"
-#include <utils/Log.h>
+#include <log/log.h>
 
 int main() {
   char* pt = 0;
diff --git a/testcases/host/shell_binary_crash_test/seg_fault.c b/testcases/host/shell_binary_crash_test/seg_fault.c
new file mode 100644
index 0000000..6f84efb
--- /dev/null
+++ b/testcases/host/shell_binary_crash_test/seg_fault.c
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <signal.h>
+#include <stdio.h>
+
+#define LOG_TAG "VtsSegFaultTestBinary"
+#include <log/log.h>
+
+int main() {
+  printf("seg_fault_bin: start");
+  ALOGI("seg_fault_bin: start");
+  raise(SIGSEGV);  // causes a segmentation fault
+  printf("seg_fault_bin: end");
+  ALOGI("seg_fault_bin: end");
+  return 0;
+}
diff --git a/tools/vts-hc/Android.mk b/testcases/host/verify_boot_header/Android.mk
similarity index 78%
copy from tools/vts-hc/Android.mk
copy to testcases/host/verify_boot_header/Android.mk
index 7733142..4a0b3c0 100644
--- a/tools/vts-hc/Android.mk
+++ b/testcases/host/verify_boot_header/Android.mk
@@ -1,4 +1,5 @@
-# Copyright (C) 2017 The Android Open Source Project
+#
+# Copyright (C) 2018 The Android Open Source Project
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -11,12 +12,9 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
-
+#
 LOCAL_PATH := $(call my-dir)
 
 include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := optional
-LOCAL_PREBUILT_EXECUTABLES := run
-include $(BUILD_HOST_PREBUILT)
-
+LOCAL_MODULE := VtsFirmwareBootHeaderVerification
+include test/vts/tools/build/Android.host_config.mk
diff --git a/testcases/host/verify_boot_header/AndroidTest.xml b/testcases/host/verify_boot_header/AndroidTest.xml
new file mode 100644
index 0000000..a29a5fa
--- /dev/null
+++ b/testcases/host/verify_boot_header/AndroidTest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="Config for VTS Boot Header Verification Test">
+    <option name="config-descriptor:metadata" key="plan" value="vts-firmware" />
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.VtsFilePusher">
+      <option name="push-group" value="HostDrivenTest.push" />
+      <option name="cleanup" value="true" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.VtsMultiDeviceTest">
+        <option name="test-module-name" value="VtsFirmwareBootHeaderVerification"/>
+        <option name="test-case-path" value="vts/testcases/host/verify_boot_header/VtsFirmwareBootHeaderVerification" />
+        <option name="precondition-first-api-level" value="28" />
+    </test>
+</configuration>
diff --git a/testcases/host/verify_boot_header/VtsFirmwareBootHeaderVerification.py b/testcases/host/verify_boot_header/VtsFirmwareBootHeaderVerification.py
new file mode 100644
index 0000000..c7a9c65
--- /dev/null
+++ b/testcases/host/verify_boot_header/VtsFirmwareBootHeaderVerification.py
@@ -0,0 +1,124 @@
+#!/usr/bin/env python
+#
+# Copyright (C) 2018 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+"""VTS tests to verify boot/recovery image header versions."""
+
+import logging
+import os
+import shutil
+from struct import unpack
+import tempfile
+
+from vts.runners.host import asserts
+from vts.runners.host import base_test
+from vts.runners.host import test_runner
+from vts.utils.python.android import api
+from vts.utils.python.file import target_file_utils
+
+BLOCK_DEV_PATH = "/dev/block/platform"  # path to platform block devices
+PROPERTY_SLOT_SUFFIX = "ro.boot.slot_suffix"  # indicates current slot suffix for A/B devices
+BOOT_HEADER_DTBO_SIZE_OFFSET = 1632  # offset of recovery dtbo size in boot header of version 1.
+
+
+class VtsFirmwareBootHeaderVerificationTest(base_test.BaseTestClass):
+    """Verifies boot/recovery image header.
+
+    Attributes:
+        temp_dir: The temporary directory on host.
+        slot_suffix: The current slot suffix for A/B devices.
+    """
+
+    def setUpClass(self):
+        """Initializes the DUT and creates temporary directories."""
+        self.dut = self.android_devices[0]
+        self.shell = self.dut.shell
+        self.adb = self.dut.adb
+        self.temp_dir = tempfile.mkdtemp()
+        logging.info("Create %s", self.temp_dir)
+        self.slot_suffix = self.dut.getProp(PROPERTY_SLOT_SUFFIX)
+        if self.slot_suffix is None:
+            self.slot_suffix = ""
+        logging.info("current slot suffix: %s", self.slot_suffix)
+
+    def setUp(self):
+        """Checks if the the preconditions to run the test are met."""
+        asserts.skipIf("x86" in self.dut.cpu_abi, "Skipping test for x86 ABI")
+
+    def CheckImageHeader(self, boot_image, is_recovery=False):
+        """Verifies the boot image header version, header size and recovery dtbo size.
+
+        Args:
+            boot_image: Path to the boot image.
+            is_recovery: Indicates that the image is recovery if true.
+        """
+        try:
+            with open(boot_image, "rb") as image_file:
+                image_file.read(8)  # read boot magic
+                host_image_header_version = unpack("10I",
+                                                   image_file.read(10 * 4))[8]
+                asserts.assertEqual(
+                    host_image_header_version, 1,
+                    "Device does not have boot image of version 1")
+                image_file.seek(BOOT_HEADER_DTBO_SIZE_OFFSET)
+                recovery_dtbo_size = unpack("I", image_file.read(4))[0]
+                image_file.read(8)  # ignore recovery dtbo load address
+                if is_recovery:
+                    asserts.assertNotEqual(
+                        recovery_dtbo_size, 0,
+                        "recovery partition for non-A/B devices must contain the recovery DTBO"
+                    )
+                boot_header_size = unpack("I", image_file.read(4))[0]
+                expected_header_size = image_file.tell()
+                asserts.assertEqual(
+                    boot_header_size, expected_header_size,
+                    "Test failure due to boot header size mismatch. Expected %s Actual %s"
+                    % (expected_header_size, boot_header_size))
+        except IOError as e:
+            logging.exception(e)
+            asserts.fail("Unable to open boot image file")
+
+    def testBootImageHeader(self):
+        """Validates boot image header."""
+        current_boot_partition = "boot" + str(self.slot_suffix)
+        boot_path = target_file_utils.FindFiles(
+            self.shell, BLOCK_DEV_PATH, current_boot_partition, "-type l")
+        logging.info("Boot path %s", boot_path)
+        if not boot_path:
+            asserts.fail("Unable to find path to boot image on device.")
+        host_boot_path = os.path.join(self.temp_dir, "boot.img")
+        self.adb.pull("%s %s" % (boot_path[0], host_boot_path))
+        self.CheckImageHeader(host_boot_path)
+
+    def testRecoveryImageHeader(self):
+        """Validates recovery image header."""
+        asserts.skipIf(self.slot_suffix,
+                       "A/B devices do not have a separate recovery partition")
+        recovery_path = target_file_utils.FindFiles(self.shell, BLOCK_DEV_PATH,
+                                                    "recovery", "-type l")
+        logging.info("recovery path %s", recovery_path)
+        if not recovery_path:
+            asserts.fail("Unable to find path to recovery image on device.")
+        host_recovery_path = os.path.join(self.temp_dir, "recovery.img")
+        self.adb.pull("%s %s" % (recovery_path[0], host_recovery_path))
+        self.CheckImageHeader(host_recovery_path, True)
+
+    def tearDownClass(self):
+        """Deletes temporary directories."""
+        shutil.rmtree(self.temp_dir)
+
+
+if __name__ == "__main__":
+    test_runner.main()
diff --git a/harnesses/host_controller/__init__.py b/testcases/host/verify_boot_header/__init__.py
similarity index 100%
rename from harnesses/host_controller/__init__.py
rename to testcases/host/verify_boot_header/__init__.py
diff --git a/tools/vts-hc/Android.mk b/testcases/host/verify_dtbo/Android.mk
similarity index 79%
copy from tools/vts-hc/Android.mk
copy to testcases/host/verify_dtbo/Android.mk
index 7733142..1145048 100644
--- a/tools/vts-hc/Android.mk
+++ b/testcases/host/verify_dtbo/Android.mk
@@ -1,4 +1,5 @@
-# Copyright (C) 2017 The Android Open Source Project
+#
+# Copyright (C) 2018 The Android Open Source Project
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -11,12 +12,9 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
-
+#
 LOCAL_PATH := $(call my-dir)
 
 include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := optional
-LOCAL_PREBUILT_EXECUTABLES := run
-include $(BUILD_HOST_PREBUILT)
-
+LOCAL_MODULE := VtsFirmwareDtboVerification
+include test/vts/tools/build/Android.host_config.mk
diff --git a/testcases/host/verify_dtbo/AndroidTest.xml b/testcases/host/verify_dtbo/AndroidTest.xml
new file mode 100644
index 0000000..f0ee1cd
--- /dev/null
+++ b/testcases/host/verify_dtbo/AndroidTest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="Config for VTS DTBO Verification Test">
+    <option name="config-descriptor:metadata" key="plan" value="vts-firmware" />
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.VtsFilePusher">
+      <option name="push-group" value="HostDrivenTest.push" />
+      <option name="cleanup" value="true" />
+      <option name="push" value="DATA/nativetest64/ufdt_verify_overlay/ufdt_verify_overlay->/data/local/tmp/64/ufdt_verify_overlay" />
+      <option name="push" value="DATA/nativetest/ufdt_verify_overlay/ufdt_verify_overlay->/data/local/tmp/32/ufdt_verify_overlay" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.VtsMultiDeviceTest">
+        <option name="test-module-name" value="VtsFirmwareDtboVerification"/>
+        <option name="test-case-path" value="vts/testcases/host/verify_dtbo/VtsFirmwareDtboVerification" />
+        <option name="precondition-first-api-level" value="28" />
+    </test>
+</configuration>
diff --git a/testcases/host/verify_dtbo/VtsFirmwareDtboVerification.py b/testcases/host/verify_dtbo/VtsFirmwareDtboVerification.py
new file mode 100644
index 0000000..47ed998
--- /dev/null
+++ b/testcases/host/verify_dtbo/VtsFirmwareDtboVerification.py
@@ -0,0 +1,130 @@
+#!/usr/bin/env python
+#
+# Copyright (C) 2018 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+"""VTS tests to verify DTBO partition/DT overlay application."""
+
+import logging
+import os
+import shutil
+import subprocess
+import tempfile
+
+from vts.runners.host import asserts
+from vts.runners.host import base_test
+from vts.runners.host import const
+from vts.runners.host import test_runner
+from vts.utils.python.android import api
+from vts.utils.python.file import target_file_utils
+from vts.utils.python.os import path_utils
+
+BLOCK_DEV_PATH = "/dev/block/platform"  # path to platform block devices
+DEVICE_TEMP_DIR = "/data/local/tmp/"  # temporary dir in device.
+FDT_PATH = "/sys/firmware/fdt"  # path to device tree.
+PROPERTY_SLOT_SUFFIX = "ro.boot.slot_suffix"  # indicates current slot suffix for A/B devices
+
+
+class VtsFirmwareDtboVerification(base_test.BaseTestClass):
+    """Validates DTBO partition and DT overlay application.
+
+    Attributes:
+        temp_dir: The temporary directory on host.
+        device_path: The temporary directory on device.
+    """
+
+    def setUpClass(self):
+        """Initializes the DUT and creates temporary directories."""
+        self.dut = self.android_devices[0]
+        self.shell = self.dut.shell
+        self.adb = self.dut.adb
+        self.temp_dir = tempfile.mkdtemp()
+        logging.info("Create %s", self.temp_dir)
+        self.device_path = str(
+            path_utils.JoinTargetPath(DEVICE_TEMP_DIR, self.abi_bitness))
+        self.shell.Execute("mkdir %s -p" % self.device_path)
+
+    def setUp(self):
+        """Checks if the the preconditions to run the test are met."""
+        asserts.skipIf("x86" in self.dut.cpu_abi, "Skipping test for x86 ABI")
+
+    def testCheckDTBOPartition(self):
+        """Validates DTBO partition using mkdtboimg.py."""
+        try:
+            slot_suffix = str(self.dut.getProp(PROPERTY_SLOT_SUFFIX))
+        except ValueError as e:
+            logging.exception(e)
+            slot_suffix = ""
+        current_dtbo_partition = "dtbo" + slot_suffix
+        dtbo_path = target_file_utils.FindFiles(
+            self.shell, BLOCK_DEV_PATH, current_dtbo_partition, "-type l")
+        logging.info("DTBO path %s", dtbo_path)
+        if not dtbo_path:
+            asserts.fail("Unable to find path to dtbo image on device.")
+        host_dtbo_image = os.path.join(self.temp_dir, "dtbo")
+        self.adb.pull("%s %s" % (dtbo_path[0], host_dtbo_image))
+        mkdtboimg_bin_path = os.path.join("host", "bin", "mkdtboimg.py")
+        unpacked_dtbo_path = os.path.join(self.temp_dir, "dumped_dtbo")
+        dtbo_dump_cmd = [
+            "python", "%s" % mkdtboimg_bin_path, "dump",
+            "%s" % host_dtbo_image, "-b",
+            "%s" % unpacked_dtbo_path
+        ]
+        try:
+            subprocess.check_call(dtbo_dump_cmd)
+        except Exception as e:
+            logging.exception(e)
+            logging.error('dtbo_dump_cmd is: %s', dtbo_dump_cmd)
+            asserts.fail("Invalid DTBO Image")
+
+    def testVerifyOverlay(self):
+        """Verifies application of DT overlays."""
+        overlay_idx_string = self.adb.shell(
+            "cat /proc/cmdline | "
+            "grep -o \"androidboot.dtbo_idx=[^ ]*\" |"
+            "cut -d \"=\" -f 2")
+        asserts.assertNotEqual(
+            len(overlay_idx_string), 0,
+            "Kernel command line missing androidboot.dtbo_idx")
+        overlay_idx_list = overlay_idx_string.split(",")
+        overlay_arg = []
+        for idx in overlay_idx_list:
+            overlay_file = "dumped_dtbo." + idx.rstrip()
+            overlay_path = os.path.join(self.temp_dir, overlay_file)
+            self.adb.push(overlay_path, self.device_path)
+            overlay_arg.append(overlay_file)
+        final_dt_path = path_utils.JoinTargetPath(self.device_path, "final_dt")
+        self.shell.Execute("cp %s %s" % (FDT_PATH, final_dt_path))
+        verification_test_path = path_utils.JoinTargetPath(
+            self.device_path, "ufdt_verify_overlay")
+        chmod_cmd = "chmod 755 %s" % verification_test_path
+        results = self.shell.Execute(chmod_cmd)
+        asserts.assertEqual(results[const.EXIT_CODE][0], 0, "Unable to chmod")
+        cd_cmd = "cd %s" % (self.device_path)
+        verify_cmd = "./ufdt_verify_overlay final_dt %s" % (
+            " ".join(overlay_arg))
+        cmd = str("%s && %s" % (cd_cmd, verify_cmd))
+        logging.info(cmd)
+        results = self.shell.Execute(cmd)
+        asserts.assertEqual(results[const.EXIT_CODE][0], 0,
+                            "Incorrect Overlay Application")
+
+    def tearDownClass(self):
+        """Deletes temporary directories."""
+        shutil.rmtree(self.temp_dir)
+        self.shell.Execute("rm -rf %s" % self.device_path)
+
+
+if __name__ == "__main__":
+    test_runner.main()
diff --git a/harnesses/host_controller/__init__.py b/testcases/host/verify_dtbo/__init__.py
similarity index 100%
copy from harnesses/host_controller/__init__.py
copy to testcases/host/verify_dtbo/__init__.py
diff --git a/testcases/library/bionic_unit_tests/Android.mk b/testcases/library/bionic_unit_tests/Android.mk
index dae52b1..6cfa830 100644
--- a/testcases/library/bionic_unit_tests/Android.mk
+++ b/testcases/library/bionic_unit_tests/Android.mk
@@ -18,5 +18,4 @@
 
 include $(CLEAR_VARS)
 LOCAL_MODULE := BionicUnitTests
-VTS_CONFIG_SRC_DIR := testcases/library/bionic_unit_tests
 include test/vts/tools/build/Android.host_config.mk
diff --git a/testcases/library/bionic_unit_tests/AndroidTest.xml b/testcases/library/bionic_unit_tests/AndroidTest.xml
index 5205da1..35ce12d 100644
--- a/testcases/library/bionic_unit_tests/AndroidTest.xml
+++ b/testcases/library/bionic_unit_tests/AndroidTest.xml
@@ -14,12 +14,12 @@
      limitations under the License.
 -->
 <configuration description="Config for VTS BionicUnitTests test cases">
+    <option name="config-descriptor:metadata" key="plan" value="vts-library" />
     <target_preparer class="com.android.compatibility.common.tradefed.targetprep.VtsFilePusher">
         <option name="push-group" value="HostDrivenTest.push" />
         <option name="push" value="DATA/nativetest/bionic-loader-test-libs->/data/nativetest/bionic-loader-test-libs" />
         <option name="push" value="DATA/nativetest64/bionic-loader-test-libs->/data/nativetest64/bionic-loader-test-libs" />
     </target_preparer>
-    <target_preparer class="com.android.tradefed.targetprep.VtsPythonVirtualenvPreparer" />
     <test class="com.android.tradefed.testtype.VtsMultiDeviceTest">
         <option name="test-module-name" value="BionicUnitTests" />
         <option name="binary-test-source" value="_32bit::DATA/nativetest/bionic-unit-tests/bionic-unit-tests->/data/nativetest/bionic-unit-tests/bionic-unit-tests" />
diff --git a/testcases/library/bionic_unit_tests_static/Android.mk b/testcases/library/bionic_unit_tests_static/Android.mk
index 3dade34..5a8af70 100644
--- a/testcases/library/bionic_unit_tests_static/Android.mk
+++ b/testcases/library/bionic_unit_tests_static/Android.mk
@@ -18,5 +18,4 @@
 
 include $(CLEAR_VARS)
 LOCAL_MODULE := BionicUnitTestsStatic
-VTS_CONFIG_SRC_DIR := testcases/library/bionic_unit_tests_static
 include test/vts/tools/build/Android.host_config.mk
diff --git a/testcases/library/bionic_unit_tests_static/AndroidTest.xml b/testcases/library/bionic_unit_tests_static/AndroidTest.xml
index 07f73b6..df6fbb4 100644
--- a/testcases/library/bionic_unit_tests_static/AndroidTest.xml
+++ b/testcases/library/bionic_unit_tests_static/AndroidTest.xml
@@ -14,12 +14,12 @@
      limitations under the License.
 -->
 <configuration description="Config for VTS BionicUnitTestsStatic test cases">
+    <option name="config-descriptor:metadata" key="plan" value="vts-library" />
     <target_preparer class="com.android.compatibility.common.tradefed.targetprep.VtsFilePusher">
         <option name="push-group" value="HostDrivenTest.push" />
         <option name="push" value="DATA/nativetest/bionic-loader-test-libs->/data/nativetest/bionic-loader-test-libs" />
         <option name="push" value="DATA/nativetest64/bionic-loader-test-libs->/data/nativetest64/bionic-loader-test-libs" />
     </target_preparer>
-    <target_preparer class="com.android.tradefed.targetprep.VtsPythonVirtualenvPreparer" />
     <test class="com.android.tradefed.testtype.VtsMultiDeviceTest">
         <option name="test-module-name" value="BionicUnitTestsStatic" />
         <option name="binary-test-source" value="_32bit::DATA/nativetest/bionic-unit-tests-static/bionic-unit-tests-static->/data/nativetest/bionic-unit-tests-static/bionic-unit-tests-static" />
diff --git a/tools/vts-hc/Android.mk b/testcases/system/device_health/Android.mk
similarity index 79%
copy from tools/vts-hc/Android.mk
copy to testcases/system/device_health/Android.mk
index 7733142..f8dbf60 100644
--- a/tools/vts-hc/Android.mk
+++ b/testcases/system/device_health/Android.mk
@@ -1,4 +1,5 @@
-# Copyright (C) 2017 The Android Open Source Project
+#
+# Copyright (C) 2018 The Android Open Source Project
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -11,12 +12,11 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
+#
 
 LOCAL_PATH := $(call my-dir)
 
 include $(CLEAR_VARS)
 
-LOCAL_MODULE_TAGS := optional
-LOCAL_PREBUILT_EXECUTABLES := run
-include $(BUILD_HOST_PREBUILT)
-
+LOCAL_MODULE := VtsDeviceHealth
+include test/vts/tools/build/Android.host_config.mk
diff --git a/testcases/system/device_health/AndroidTest.xml b/testcases/system/device_health/AndroidTest.xml
new file mode 100644
index 0000000..e5faa62
--- /dev/null
+++ b/testcases/system/device_health/AndroidTest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="Config for VTS VtsDeviceHalth test cases">
+    <option name="config-descriptor:metadata" key="plan" value="vts-device-health" />
+
+    <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+        <option name="test-file-name" value="DeviceHealthTests.apk" />
+        <option name="cleanup-apks" value="true" />
+    </target_preparer>
+
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="com.android.devicehealth.tests" />
+        <option name="runner" value="android.support.test.runner.AndroidJUnitRunner" />
+    </test>
+</configuration>
diff --git a/testcases/system/libc/Android.mk b/testcases/system/libc/Android.mk
index 90529da..e3367e3 100644
--- a/testcases/system/libc/Android.mk
+++ b/testcases/system/libc/Android.mk
@@ -18,5 +18,4 @@
 include $(CLEAR_VARS)
 
 LOCAL_MODULE := LibcTest
-VTS_CONFIG_SRC_DIR := testcases/system/libc
 include test/vts/tools/build/Android.host_config.mk
diff --git a/testcases/system/libc/AndroidTest.xml b/testcases/system/libc/AndroidTest.xml
index 48425aa..6eb6a78 100644
--- a/testcases/system/libc/AndroidTest.xml
+++ b/testcases/system/libc/AndroidTest.xml
@@ -14,14 +14,14 @@
      limitations under the License.
 -->
 <configuration description="Config for VTS libc basic file and socket IO testing">
+    <option name="config-descriptor:metadata" key="plan" value="vts-library" />
     <target_preparer class="com.android.compatibility.common.tradefed.targetprep.VtsFilePusher">
         <option name="push-group" value="HostDrivenTest.push" />
         <option name="cleanup" value="true" />
         <option name="push" value="spec/test/vts/specification/lib/ndk/bionic/1.0/libcV1.vts->/data/local/tmp/spec/lib/ndk/bionic/1.0/libcV1.vts" />
     </target_preparer>
-    <target_preparer class="com.android.tradefed.targetprep.VtsPythonVirtualenvPreparer">
-    </target_preparer>
     <test class="com.android.tradefed.testtype.VtsMultiDeviceTest">
+        <option name="test-module-name" value="LibcTest" />
         <option name="test-case-path" value="vts/testcases/system/libc/LibcTest" />
     </test>
 </configuration>
diff --git a/testcases/system/qtaguid/sample/AndroidTest.xml b/testcases/system/qtaguid/sample/AndroidTest.xml
index c97481d..4d4015f 100644
--- a/testcases/system/qtaguid/sample/AndroidTest.xml
+++ b/testcases/system/qtaguid/sample/AndroidTest.xml
@@ -14,12 +14,12 @@
      limitations under the License.
 -->
 <configuration description="Config for VTS system sample qtaguid test cases">
+    <option name="config-descriptor:metadata" key="plan" value="vts-misc" />
     <target_preparer class="com.android.compatibility.common.tradefed.targetprep.VtsFilePusher">
         <option name="push-group" value="HostDrivenTest.push" />
         <option name="push" value="spec/test/vts/specification/lib/ndk/bionic/1.0/libcutilsV1.vts->/data/local/tmp/spec/lib/bionic/ndk/libcutilsV1.vts" />
         <option name="cleanup" value="true" />
     </target_preparer>
-    <target_preparer class="com.android.tradefed.targetprep.VtsPythonVirtualenvPreparer" />
     <test class="com.android.tradefed.testtype.VtsMultiDeviceTest">
         <option name="test-case-path" value="vts/testcases/system/qtaguid/sample/SampleQtaguidTest" />
     </test>
diff --git a/testcases/target/hal_lights/AndroidTest.xml b/testcases/target/hal_lights/AndroidTest.xml
index d429e92..4523f58 100644
--- a/testcases/target/hal_lights/AndroidTest.xml
+++ b/testcases/target/hal_lights/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for VTS HAL Lights test cases">
+    <option name="config-descriptor:metadata" key="plan" value="vts-misc" />
     <target_preparer class="com.android.compatibility.common.tradefed.targetprep.VtsFilePusher">
         <option name="cleanup" value="true" />
         <option name="push" value="VtsHalLightsTestCases->/data/local/tmp/VtsHalLightsTestCases" />
diff --git a/testcases/target/hal_power/AndroidTest.xml b/testcases/target/hal_power/AndroidTest.xml
index 517b4c3..8b31176 100644
--- a/testcases/target/hal_power/AndroidTest.xml
+++ b/testcases/target/hal_power/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for VTS HAL Power test cases">
+    <option name="config-descriptor:metadata" key="plan" value="vts-misc" />
     <target_preparer class="com.android.compatibility.common.tradefed.targetprep.VtsFilePusher">
         <option name="cleanup" value="true" />
         <option name="push" value="VtsHalPowerTestCases->/data/local/tmp/VtsHalPowerTestCases" />
diff --git a/testcases/target/hal_power/hal_power_basic_test.cpp b/testcases/target/hal_power/hal_power_basic_test.cpp
index 3103ed7..c6eaeac 100644
--- a/testcases/target/hal_power/hal_power_basic_test.cpp
+++ b/testcases/target/hal_power/hal_power_basic_test.cpp
@@ -20,7 +20,7 @@
 
 #include <hardware/hardware.h>
 #include <hardware/power.h>
-#include <utils/Log.h>
+#include <log/log.h>
 
 #include <iostream>
 
diff --git a/testcases/template/binary_test/binary_test.py b/testcases/template/binary_test/binary_test.py
index ed47053..ba7dd31 100644
--- a/testcases/template/binary_test/binary_test.py
+++ b/testcases/template/binary_test/binary_test.py
@@ -47,14 +47,7 @@
         tags: all the tags that appeared in binary list
         DEVICE_TMP_DIR: string, temp location for storing binary
         TAG_DELIMITER: string, separator used to separate tag and path
-        SYSPROP_VTS_NATIVE_SERVER: string, the name of a system property which
-                                   tells whether to stop properly configured
-                                   native servers where properly configured
-                                   means a server's init.rc is configured to
-                                   stop when that property's value is 1.
     '''
-    SYSPROP_VTS_NATIVE_SERVER = "vts.native_server.on"
-
     DEVICE_TMP_DIR = '/data/local/tmp'
     TAG_DELIMITER = '::'
     PUSH_DELIMITER = '->'
@@ -77,10 +70,9 @@
             keys.ConfigKeys.IKEY_BINARY_TEST_ARGS,
             keys.ConfigKeys.IKEY_BINARY_TEST_LD_LIBRARY_PATH,
             keys.ConfigKeys.IKEY_BINARY_TEST_PROFILING_LIBRARY_PATH,
-            keys.ConfigKeys.IKEY_BINARY_TEST_DISABLE_FRAMEWORK,
-            keys.ConfigKeys.IKEY_BINARY_TEST_STOP_NATIVE_SERVERS,
             keys.ConfigKeys.IKEY_NATIVE_SERVER_PROCESS_NAME,
             keys.ConfigKeys.IKEY_PRECONDITION_FILE_PATH_PREFIX,
+            keys.ConfigKeys.IKEY_PRECONDITION_SYSPROP,
         ]
         self.getUserParams(
             req_param_names=required_params, opt_param_names=opt_params)
@@ -89,7 +81,7 @@
         self.getUserParam(
             keys.ConfigKeys.KEY_TESTBED_NAME, error_if_not_found=True)
 
-        logging.info("%s: %s", keys.ConfigKeys.IKEY_DATA_FILE_PATH,
+        logging.debug("%s: %s", keys.ConfigKeys.IKEY_DATA_FILE_PATH,
                      self.data_file_path)
 
         self.binary_test_source = self.getUserParam(
@@ -204,11 +196,11 @@
                     self.envp[tag] = coverage_utils.COVERAGE_TEST_ENV
 
         self.testcases = []
-
-        ret = precondition_utils.CanRunHidlHalTest(self, self._dut,
-                                                   self.shell, self.run_as_compliance_test)
-        if not ret:
-            self._skip_all_testcases = True
+        if not precondition_utils.CheckSysPropPrecondition(
+                self, self._dut, self.shell):
+            logging.warn('Precondition sysprop not met; '
+                         'all tests skipped.')
+            self.skipAllTests('precondition sysprop not met')
 
         self.tags = set()
         self.CreateTestCases()
@@ -220,41 +212,6 @@
             logging.error('Failed to set permission to some of the binaries:\n'
                           '%s\n%s', cmd, cmd_results)
 
-        stop_requested = False
-
-        if getattr(self, keys.ConfigKeys.IKEY_BINARY_TEST_DISABLE_FRAMEWORK,
-                   False):
-            # Stop Android runtime to reduce interference.
-            logging.debug("Stops the Android framework.")
-            self._dut.stop()
-            stop_requested = True
-
-        if getattr(self, keys.ConfigKeys.IKEY_BINARY_TEST_STOP_NATIVE_SERVERS,
-                   False):
-            logging.debug("Stops all properly configured native servers.")
-            results = self._dut.setProp(self.SYSPROP_VTS_NATIVE_SERVER, "1")
-            stop_requested = True
-
-        if stop_requested:
-            native_server_process_names = getattr(
-                self, keys.ConfigKeys.IKEY_NATIVE_SERVER_PROCESS_NAME, [])
-            if native_server_process_names:
-                for native_server_process_name in native_server_process_names:
-                    while True:
-                        cmd_result = self.shell.Execute("ps -A")
-                        if cmd_result[const.EXIT_CODE][0] != 0:
-                            logging.error("ps command failed (exit code: %s",
-                                          cmd_result[const.EXIT_CODE][0])
-                            break
-                        if (native_server_process_name not in cmd_result[
-                                const.STDOUT][0]):
-                            logging.info("Process %s not running",
-                                         native_server_process_name)
-                            break
-                        logging.info("Checking process %s",
-                                     native_server_process_name)
-                        time.sleep(1)
-
     def CreateTestCases(self):
         '''Push files to device and create test case objects.'''
         source_list = list(map(self.ParseTestSource, self.binary_test_source))
@@ -282,31 +239,35 @@
             if (tag.endswith(const.SUFFIX_32BIT) and self.abi_bitness == '64'
                 ) or (tag.endswith(const.SUFFIX_64BIT) and
                       self.abi_bitness == '32'):
-                logging.info('Bitness of test source, %s, does not match the '
-                             'abi_bitness, %s, of test run.', str(source[0]),
+                logging.debug('Bitness of test source, %s, does not match the '
+                             'abi_bitness, %s, of test run. Skipping',
+                             str(source[0]),
                              self.abi_bitness)
                 return False
 
             return True
 
         source_list = filter(isValidSource, source_list)
-        logging.info('Parsed test sources: %s', source_list)
+        logging.debug('Parsed test sources: %s', source_list)
 
         # Push source files first
         for src, dst, tag in source_list:
             if src:
                 if os.path.isdir(src):
                     src = os.path.join(src, '.')
-                logging.info('Pushing from %s to %s.', src, dst)
+                logging.debug('Pushing from %s to %s.', src, dst)
                 self._dut.adb.push('{src} {dst}'.format(src=src, dst=dst))
                 self.shell.Execute('ls %s' % dst)
 
+        if not hasattr(self, 'testcases'):
+            self.testcases = []
+
         # Then create test cases
         for src, dst, tag in source_list:
             if tag is not None:
                 # tag not being None means to create a test case
                 self.tags.add(tag)
-                logging.info('Creating test case from %s with tag %s', dst,
+                logging.debug('Creating test case from %s with tag %s', dst,
                              tag)
                 testcase = self.CreateTestCase(dst, tag)
                 if not testcase:
@@ -317,8 +278,8 @@
                 else:
                     self.testcases.append(testcase)
 
-        if type(self.testcases) is not list or len(self.testcases) == 0:
-            asserts.fail("No test case is found or generated.")
+        if not self.testcases:
+            logging.warn("No test case is found or generated.")
 
     def PutTag(self, name, tag):
         '''Put tag on name and return the resulting string.
@@ -359,24 +320,13 @@
 
     def tearDownClass(self):
         '''Perform clean-up tasks'''
-        if getattr(self, keys.ConfigKeys.IKEY_BINARY_TEST_STOP_NATIVE_SERVERS,
-                   False):
-            logging.debug("Restarts all properly configured native servers.")
-            results = self._dut.setProp(self.SYSPROP_VTS_NATIVE_SERVER, "0")
-
-        # Restart Android runtime.
-        if getattr(self, keys.ConfigKeys.IKEY_BINARY_TEST_DISABLE_FRAMEWORK,
-                   False):
-            logging.debug("Starts the Android framework.")
-            self._dut.start()
-
         # Retrieve coverage if applicable
         if self.coverage.enabled and self.coverage.global_coverage:
-            if not self._skip_all_testcases:
+            if not self.isSkipAllTests():
                 self.coverage.SetCoverageData(dut=self._dut, isGlobal=True)
 
         # Clean up the pushed binaries
-        logging.info('Start class cleaning up jobs.')
+        logging.debug('Start class cleaning up jobs.')
         # Delete pushed files
 
         sources = [
@@ -399,10 +349,10 @@
         if not cmd_results or any(cmd_results[const.EXIT_CODE]):
             logging.warning('Failed to remove: %s', cmd_results)
 
-        if not self._skip_all_testcases and self.profiling.enabled:
+        if not self.isSkipAllTests() and self.profiling.enabled:
             self.profiling.ProcessAndUploadTraceData()
 
-        logging.info('Finished class cleaning up jobs.')
+        logging.debug('Finished class cleaning up jobs.')
 
     def ParseTestSource(self, source):
         '''Convert host side binary path to device side path.
@@ -529,7 +479,7 @@
                                               test_case.profiling_library_path)
 
         cmd = test_case.GetRunCommand()
-        logging.info("Executing binary test command: %s", cmd)
+        logging.debug("Executing binary test command: %s", cmd)
         command_results = self.shell.Execute(cmd)
 
         self.VerifyTestResult(test_case, command_results)
diff --git a/testcases/template/binary_test/binary_test_case.py b/testcases/template/binary_test/binary_test_case.py
index c69882f..335658f 100644
--- a/testcases/template/binary_test/binary_test_case.py
+++ b/testcases/template/binary_test/binary_test_case.py
@@ -49,6 +49,9 @@
         envp: string, environment veriable. shoud be in format 'name1=value1 name2=value2...'
               Will be called using 'env <envp> <cmd> <args>'
         args: string, arguments following cmd.
+        name_appendix: string, appendix attached to the test name in display,
+                       typically contains info of parameters used in the test,
+                       e.e. service name used for hal hidl test.
     '''
 
     def __init__(self,
@@ -62,7 +65,8 @@
                  profiling_library_path=None,
                  cmd='',
                  envp='',
-                 args=''):
+                 args='',
+                 name_appendix=''):
         self.test_suite = test_suite
         self.test_name = test_name
         self.path = path
@@ -74,9 +78,27 @@
         self.cmd = cmd
         self.envp = envp
         self.args = args
+        self.name_appendix = name_appendix
 
     def __str__(self):
-        return self.put_tag_func(self.full_name, self.tag)
+        return self._GenerateDisplayName()
+
+    def _GenerateDisplayName(self):
+        '''Get a string of test name for display.
+
+        The display name contains three parts: the original full test name, the
+        name appendix which includes more info about the test run, and the
+        tag(s) used by the test.
+        '''
+        return self.put_tag_func(self.full_name + self.name_appendix, self.tag)
+
+    @property
+    def name_appendix(self):
+        return self._name_appendix
+
+    @name_appendix.setter
+    def name_appendix(self, name_appendix):
+        self._name_appendix = name_appendix
 
     @property
     def full_name(self):
diff --git a/testcases/template/cts_test/cts_test.py b/testcases/template/cts_test/cts_test.py
index 18a7dbf..2821c5e 100644
--- a/testcases/template/cts_test/cts_test.py
+++ b/testcases/template/cts_test/cts_test.py
@@ -39,9 +39,8 @@
     ]
 
     def setUpClass(self):
-        self.dut = self.registerController(android_device)[0]
-        self.dut.shell.InvokeTerminal("one")
-        self.dut.shell.one.Execute("setenforce 0")  # SELinux permissive mode
+        self.dut = self.android_devices[0]
+        self.dut.shell.Execute("setenforce 0")  # SELinux permissive mode
         self.testcases = []
         self.CreateTestCases()
 
@@ -55,7 +54,7 @@
     def CreateTestCases(self):
         '''Create test configs.'''
         for testcase in self.CTS_TESTS:
-            logging.info('Creating test case %s.', testcase["name"])
+            logging.debug('Creating test case %s.', testcase["name"])
             self.testcases.append(testcase)
 
     def RunTestCase(self, test_case):
diff --git a/testcases/template/gtest_binary_test/gtest_binary_test.py b/testcases/template/gtest_binary_test/gtest_binary_test.py
index e5ccab7..7856c9c 100644
--- a/testcases/template/gtest_binary_test/gtest_binary_test.py
+++ b/testcases/template/gtest_binary_test/gtest_binary_test.py
@@ -45,10 +45,18 @@
     # @Override
     def setUpClass(self):
         '''Prepare class, push binaries, set permission, create test cases.'''
+        self.collect_tests_only = self.getUserParam(
+            keys.ConfigKeys.IKEY_COLLECT_TESTS_ONLY, default_value=False)
         self.batch_mode = self.getUserParam(
             keys.ConfigKeys.IKEY_GTEST_BATCH_MODE, default_value=False)
+
         if self.batch_mode:
-            self._gtest_results = []
+            if self.collect_tests_only:
+                self.batch_mode = False
+                logging.debug("Disable batch mode when collecting tests.")
+            else:
+                self._gtest_results = []
+
         super(GtestBinaryTest, self).setUpClass()
 
     # @Override
@@ -113,7 +121,7 @@
                     test_suite, test_name, path, tag, self.PutTag,
                     working_directory, ld_library_path, profiling_library_path,
                     envp=envp, args=args)
-                logging.info('Gtest test case: %s' % test_case)
+                logging.debug('Gtest test case: %s' % test_case)
                 test_cases.append(test_case)
             else:  # Test suite name
                 test_suite = line.strip()
@@ -174,20 +182,28 @@
             self._ParseBatchResults(test_case, xml_str)
             return
 
-        for stdout in command_results[const.STDOUT]:
-            if stdout and stdout.strip():
-                for line in stdout.split('\n'):
-                    logging.info(line)
-
         asserts.assertFalse(
             command_results[const.EXIT_CODE][1],
             'Failed to show Gtest XML output: %s' % command_results)
 
         root = xml.etree.ElementTree.fromstring(xml_str)
         asserts.assertEqual(root.get('tests'), '1', 'No tests available')
+        success = True
         if root.get('errors') != '0' or root.get('failures') != '0':
             messages = [x.get('message') for x in root.findall('.//failure')]
+            success = False
+
+        for stdout in command_results[const.STDOUT]:
+            if stdout and stdout.strip():
+                for line in stdout.split('\n'):
+                    if success:
+                        logging.debug(line)
+                    else:
+                        logging.error(line)
+
+        if not success:
             asserts.fail('\n'.join([x for x in messages if x]))
+
         asserts.skipIf(root.get('disabled') == '1', 'Gtest test case disabled')
 
     def _ParseBatchResults(self, test_case_original, xml_str):
@@ -200,12 +216,14 @@
         root = xml.etree.ElementTree.fromstring(xml_str)
 
         for test_suite in root:
-            print test_suite.tag, test_suite.attrib
+            logging.debug('Test tag: %s, attribute: %s',
+                          test_suite.tag,
+                          test_suite.attrib)
             for test_case in test_suite:
                 result = gtest_test_case.GtestTestCase(
                     test_suite.get('name'),
                     test_case.get('name'), '', test_case_original.tag,
-                    self.PutTag)
+                    self.PutTag, name_appendix=test_case_original.name_appendix)
 
                 failure_message = None
                 for sub in test_case:
diff --git a/testcases/template/gtest_binary_test/gtest_test_case.py b/testcases/template/gtest_binary_test/gtest_test_case.py
index 1f4a5c4..88a7ece 100644
--- a/testcases/template/gtest_binary_test/gtest_test_case.py
+++ b/testcases/template/gtest_binary_test/gtest_test_case.py
@@ -88,12 +88,12 @@
             output_base_name = '{}.xml'.format(uuid.uuid4())
             output_file_path = path_utils.JoinTargetPath(
                 output_dir_name, output_base_name)
-            logging.info('Output file path is set as "%s".', output_file_path)
+            logging.debug('Output file path is set as "%s".', output_file_path)
 
         if len(output_file_path) > utils.MAX_PATH_LEN:
             logging.warn('File path of output file "{}" is longer than {}.'.
                          format(output_file_path, utils.MAX_PATH_LEN))
             output_file_path = output_base_name
-            logging.info('Output file path is set as "%s".', output_file_path)
+            logging.debug('Output file path is set as "%s".', output_file_path)
 
         self._output_file_path = output_file_path
diff --git a/testcases/template/hal_hidl_gtest/hal_hidl_gtest.py b/testcases/template/hal_hidl_gtest/hal_hidl_gtest.py
index 038c19b..5d0d5a5 100644
--- a/testcases/template/hal_hidl_gtest/hal_hidl_gtest.py
+++ b/testcases/template/hal_hidl_gtest/hal_hidl_gtest.py
@@ -24,10 +24,14 @@
 from vts.testcases.template.gtest_binary_test import gtest_test_case
 from vts.utils.python.cpu import cpu_frequency_scaling
 from vts.utils.python.hal import hal_service_name_utils
+from vts.utils.python.precondition import precondition_utils
 
+# The pattern indicating a full hal test name including the service name info.
+# e.g. TM.TC(default)_32bit
+_HAL_TEST_NAME_PATTERN = ".*\(.*\).*"
 
 class HidlHalGTest(gtest_binary_test.GtestBinaryTest):
-    '''Base class to run a VTS target-side HIDL HAL test.
+    """Base class to run a VTS target-side HIDL HAL test.
 
     Attributes:
         DEVICE_TEST_DIR: string, temp location for storing binary
@@ -36,44 +40,48 @@
         testcases: list of GtestTestCase objects, list of test cases to run
         _cpu_freq: CpuFrequencyScalingController instance of a target device.
         _dut: AndroidDevice, the device under test as config
-        _hal_precondition: String, the name of the HAL preconditioned
-    '''
+        _target_hals: List of String, the targeting hal service of the test.
+                      e.g (["android.hardware.foo@1.0::IFoo"])
+    """
 
     def setUpClass(self):
         """Checks precondition."""
         super(HidlHalGTest, self).setUpClass()
+        if not hasattr(self, "_target_hals"):
+            self._target_hals = []
 
-        opt_params = [keys.ConfigKeys.IKEY_SKIP_IF_THERMAL_THROTTLING]
+        opt_params = [keys.ConfigKeys.IKEY_SKIP_IF_THERMAL_THROTTLING,
+                      keys.ConfigKeys.IKEY_DISABLE_CPU_FREQUENCY_SCALING]
         self.getUserParams(opt_param_names=opt_params)
 
         self._skip_if_thermal_throttling = self.getUserParam(
             keys.ConfigKeys.IKEY_SKIP_IF_THERMAL_THROTTLING,
             default_value=False)
+        self._disable_cpu_frequency_scaling = self.getUserParam(
+            keys.ConfigKeys.IKEY_DISABLE_CPU_FREQUENCY_SCALING,
+            default_value=True)
 
-        if not self._skip_all_testcases:
-            logging.info("Disable CPU frequency scaling")
+        if not self.isSkipAllTests():
             self._cpu_freq = cpu_frequency_scaling.CpuFrequencyScalingController(
                 self._dut)
-            self._cpu_freq.DisableCpuScaling()
+            if self._disable_cpu_frequency_scaling:
+                logging.debug("Disabling CPU frequency scaling")
+                self._cpu_freq.DisableCpuScaling()
         else:
             self._cpu_freq = None
 
-        self._hal_precondition = None
-        if hasattr(self, keys.ConfigKeys.IKEY_PRECONDITION_LSHAL):
-            self._hal_precondition = getattr(
-                self, keys.ConfigKeys.IKEY_PRECONDITION_LSHAL)
-        elif hasattr(self, keys.ConfigKeys.IKEY_PRECONDITION_VINTF):
-            self._hal_precondition = getattr(
-                self, keys.ConfigKeys.IKEY_PRECONDITION_VINTF)
-        elif hasattr(self, keys.ConfigKeys.IKEY_PRECONDITION_HWBINDER_SERVICE):
-            self._hal_precondition = getattr(
-                self, keys.ConfigKeys.IKEY_PRECONDITION_HWBINDER_SERVICE)
+        if not self.isSkipAllTests():
+            ret = precondition_utils.CanRunHidlHalTest(
+                self, self._dut, self.shell, self.run_as_compliance_test)
+            if not ret:
+                self.skipAllTests("HIDL HAL precondition check failed.")
 
-        if self.sancov.enabled and self._hal_precondition is not None:
+        if self.sancov.enabled and self._target_hals:
             self.sancov.InitializeDeviceCoverage(self._dut,
-                                                 self._hal_precondition)
-        if self.coverage.enabled and self._hal_precondition is not None:
-            self.coverage.SetHalNames([self._hal_precondition])
+                                                 self._target_hals)
+        if self.coverage.enabled and self._target_hals:
+            self.coverage.SetHalNames(self._target_hals)
+            self.coverage.SetCoverageReportFilePrefix(self.test_module_name + self.abi_bitness)
 
     def CreateTestCases(self):
         """Create testcases and conditionally enable passthrough mode.
@@ -93,7 +101,7 @@
 
     # @Override
     def CreateTestCase(self, path, tag=''):
-        '''Create a list of GtestTestCase objects from a binary path.
+        """Create a list of GtestTestCase objects from a binary path.
 
         Support testing against different service names by first executing a
         dummpy test case which lists all the registered hal services. Then
@@ -107,7 +115,7 @@
 
         Returns:
             A list of GtestTestCase objects.
-        '''
+        """
         initial_test_cases = super(HidlHalGTest, self).CreateTestCase(path,
                                                                       tag)
         if not initial_test_cases:
@@ -117,51 +125,71 @@
         list_service_test_case.args += " --list_registered_services"
         results = self.shell.Execute(list_service_test_case.GetRunCommand())
         if (results[const.EXIT_CODE][0]):
-            logging.error('Failed to list test cases from binary %s',
+            logging.error("Failed to list test cases from binary %s",
                           list_service_test_case.path)
         # parse the results to get the registered service list.
         registered_services = []
-        for line in results[const.STDOUT][0].split('\n'):
+        comb_mode = hal_service_name_utils.CombMode.FULL_PERMUTATION
+        # TODO: consider to use a standard data format (e.g. json) instead of
+        # parsing the print output.
+        for line in results[const.STDOUT][0].split("\n"):
             line = str(line)
-            if line.startswith('hal_service: '):
-                service = line[len('hal_service: '):]
+            if line.startswith("hal_service: "):
+                service = line[len("hal_service: "):]
                 registered_services.append(service)
+            if line.startswith("service_comb_mode: "):
+                comb_mode = int(line[len("service_comb_mode: "):])
 
         # If no service registered, return the initial test cases directly.
         if not registered_services:
+            logging.error("No hal service registered.")
             return initial_test_cases
 
+        self._target_hals = copy.copy(registered_services)
+
         # find the correponding service name(s) for each registered service and
         # store the mapping in dict service_instances.
         service_instances = {}
         for service in registered_services:
-            _, service_names = hal_service_name_utils.GetHalServiceName(
+            testable, service_names = hal_service_name_utils.GetHalServiceName(
                 self.shell, service, self.abi_bitness,
                 self.run_as_compliance_test)
+            if not testable:
+                self.skipAllTests("Hal: %s is not testable, "
+                                  "skip all tests." % service)
+                return initial_test_cases
             if not service_names:
-                logging.error("No service name found for: %s, skip all tests.",
-                              service)
-                self._skip_all_testcases = True
+                self.skipAllTests("No service name found for: %s, skip all tests." % service)
                 # If any of the test services are not available, return the
                 # initial test cases directly.
                 return initial_test_cases
             else:
                 service_instances[service] = service_names
-        logging.info("registered service instances: %s", service_instances)
+        logging.debug("registered service instances: %s", service_instances)
+        logging.debug("service comb mode: %d", comb_mode)
+
+        # If request NO_COMBINATION mode, return the initial test cases directly.
+        if comb_mode == hal_service_name_utils.CombMode.NO_COMBINATION:
+            return initial_test_cases
 
         # get all the combination of service instances.
         service_instance_combinations = hal_service_name_utils.GetServiceInstancesCombinations(
-            registered_services, service_instances)
+            registered_services, service_instances, comb_mode);
 
         new_test_cases = []
-        for test_case in initial_test_cases:
-            for instance_combination in service_instance_combinations:
+        appendix_list = []
+        for instance_combination in service_instance_combinations:
+            for test_case in initial_test_cases:
                 new_test_case = copy.copy(test_case)
+                service_name_list = []
                 for instance in instance_combination:
                     new_test_case.args += " --hal_service_instance=" + instance
-                    new_test_case.tag = instance[instance.find(
-                        '/'):] + new_test_case.tag
+                    service_name_list.append(instance[instance.find('/')+1:])
+                name_appendix = "({0})".format(",".join(service_name_list))
+                new_test_case.name_appendix = name_appendix
                 new_test_cases.append(new_test_case)
+            appendix_list.append(name_appendix)
+        self.test_filter.ExpandAppendix(appendix_list, _HAL_TEST_NAME_PATTERN)
         return new_test_cases
 
     def _EnablePassthroughMode(self):
@@ -199,14 +227,15 @@
 
     def tearDownClass(self):
         """Turns off CPU frequency scaling."""
-        if (not self._skip_all_testcases and getattr(self, "_cpu_freq", None)):
-            logging.info("Enable CPU frequency scaling")
+        if (not self.isSkipAllTests() and getattr(self, "_cpu_freq", None)
+            and self._disable_cpu_frequency_scaling):
+            logging.debug("Enabling CPU frequency scaling")
             self._cpu_freq.EnableCpuScaling()
 
-        if self.sancov.enabled and self._hal_precondition is not None:
-            self.sancov.FlushDeviceCoverage(self._dut, self._hal_precondition)
+        if self.sancov.enabled and self._target_hals:
+            self.sancov.FlushDeviceCoverage(self._dut, self._target_hals)
             self.sancov.ProcessDeviceCoverage(self._dut,
-                                              self._hal_precondition)
+                                              self._target_hals)
             self.sancov.Upload()
 
         super(HidlHalGTest, self).tearDownClass()
diff --git a/testcases/template/hal_hidl_host_test/hal_hidl_host_test.py b/testcases/template/hal_hidl_host_test/hal_hidl_host_test.py
index 476d21c..ff91d2b 100644
--- a/testcases/template/hal_hidl_host_test/hal_hidl_host_test.py
+++ b/testcases/template/hal_hidl_host_test/hal_hidl_host_test.py
@@ -13,16 +13,17 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 #
-
+import copy
 import logging
 
 from vts.runners.host import base_test
-from vts.runners.host import test_runner
+from vts.testcases.template.param_test import param_test
 from vts.utils.python.controllers import android_device
+from vts.utils.python.hal import hal_service_name_utils
 from vts.utils.python.precondition import precondition_utils
 
 
-class HalHidlHostTest(base_test.BaseTestClass):
+class HalHidlHostTest(param_test.ParamTestClass):
     """Base class to run a host-driver hidl hal test.
 
     Attributes:
@@ -32,21 +33,25 @@
     """
     TEST_HAL_SERVICES = set()
 
+    # @Override
+    def initParams(self):
+        """Get the service combination according to the registered test HAL."""
+        self.dut = self.android_devices[0]
+        self.shell = self.dut.shell
+        service_instance_combinations = self._GetServiceInstanceCombinations()
+        self.params = service_instance_combinations
+
+    # @Override
     def setUpClass(self):
         """Basic setup process for host-side hidl hal tests.
 
-        Register default device and shell mirror, set permission, check whether
-        the test satisfy the precondition requirement, prepare for test
-        profiling and coverage measurement if enabled.
+        Test precondition check, prepare for profiling and coverage measurement
+        if enabled.
         """
-        self.dut = self.registerController(android_device)[0]
-        self.shell = self.dut.shell
-        self.shell.Execute("setenforce 0")  # SELinux permissive mode
-
         # Testability check.
         if not precondition_utils.CanRunHidlHalTest(
                 self, self.dut, self.shell, self.run_as_compliance_test):
-            self._skip_all_testcases = True
+            self.skipAllTests("precondition check for hidl hal tests didn't pass")
             return
 
         # Initialization for coverage measurement.
@@ -54,11 +59,13 @@
             self.coverage.InitializeDeviceCoverage(self.dut)
             if self.TEST_HAL_SERVICES:
                 self.coverage.SetHalNames(self.TEST_HAL_SERVICES)
+                self.coverage.SetCoverageReportFilePrefix(self.test_module_name + self.abi_bitness)
 
         # Enable profiling.
         if self.profiling.enabled:
             self.profiling.EnableVTSProfiling(self.shell)
 
+    # @Override
     def tearDownClass(self):
         """Basic cleanup process for host-side hidl hal tests.
 
@@ -67,7 +74,7 @@
         If coverage is enabled for the test, collect the coverage data and
         upload it to dashboard.
         """
-        if self._skip_all_testcases:
+        if self.isSkipAllTests():
             return
 
         if self.coverage.enabled and self.coverage.global_coverage:
@@ -76,14 +83,80 @@
         if self.profiling.enabled:
             self.profiling.ProcessAndUploadTraceData()
 
+    # @Override
     def setUp(self):
         """Setup process for each test case."""
         if self.profiling.enabled:
             self.profiling.EnableVTSProfiling(self.shell)
 
+    # @Override
     def tearDown(self):
         """Cleanup process for each test case."""
         if self.profiling.enabled:
             self.profiling.ProcessTraceDataForTestCase(self.dut)
             self.profiling.DisableVTSProfiling(self.shell)
 
+    # @Override
+    def getParamTag(self, param):
+        """Concatenate names for all services passed as the param test tag.
+
+        Args:
+            param: a list of service instances. e.g [s1/n1, s2/n2]
+
+        Returns:
+            a string of concatenated service names. e.g. n1/n2
+        """
+        names = map(lambda instance: instance.split("/")[1], param)
+        return "({})".format(",".join(names))
+
+    def getHalServiceName(self, hal_service):
+        """Get corresponding name for hal_service from the current parameter.
+
+        The current parameter should be a list of service instances with the
+        format [hal_service/name], e.g [s1/n1, s2/n2]
+
+        Args:
+            hal_service: string, hal@version e.g. foo@1.0
+
+        Returns:
+            Name for hal_service, "default" if could not find the hal_service in
+            the list of service instances.
+        """
+        for instance in self.cur_param:
+            service, name = instance.split("/")
+            if service == hal_service:
+                return str(name)
+        # In case could not find the name for given hal_service, fall back to
+        # use the "default" name.
+        logging.warning(
+            "Could not find the service name for %s, using default name instead",
+            hal_service)
+        return "default"
+
+    def _GetServiceInstanceCombinations(self):
+        """Create combinations of instances for registered HAL services.
+
+        Returns:
+            A list of instance combination for registered HAL.
+        """
+        registered_services = copy.copy(self.TEST_HAL_SERVICES)
+        service_instances = {}
+
+        for service in registered_services:
+            testable, service_names = hal_service_name_utils.GetHalServiceName(
+                self.shell, service, self.abi_bitness,
+                self.run_as_compliance_test)
+            if not testable:
+                self.skipAllTests("Hal: %s is not testable, "
+                                  "skip all tests." % service)
+                return []
+            if service_names:
+                service_instances[service] = service_names
+            else:
+                self.skipAllTests("No service name found for: %s, "
+                                  "skip all tests." % service)
+                return []
+        logging.info("registered service instances: %s", service_instances)
+
+        return hal_service_name_utils.GetServiceInstancesCombinations(
+                registered_services, service_instances)
diff --git a/testcases/template/hal_hidl_replay_test/hal_hidl_replay_test.py b/testcases/template/hal_hidl_replay_test/hal_hidl_replay_test.py
index f4ecf43..ecb263d 100644
--- a/testcases/template/hal_hidl_replay_test/hal_hidl_replay_test.py
+++ b/testcases/template/hal_hidl_replay_test/hal_hidl_replay_test.py
@@ -45,7 +45,7 @@
         self._test_hal_services = set()
         super(HalHidlReplayTest, self).setUpClass()
 
-        if self._skip_all_testcases:
+        if self.isSkipAllTests():
             return
 
         if self.coverage.enabled and self._test_hal_services is not None:
@@ -64,7 +64,7 @@
         self.abi_bitness = str(self.abi_bitness)
         self.trace_paths = map(str, self.hal_hidl_replay_test_trace_paths)
 
-        self.driver_binary_path = path_utils.JoinTargetPath(
+        self.replayer_binary_path = path_utils.JoinTargetPath(
             self.DEVICE_TMP_DIR, self.abi_bitness,
             "vts_hal_replayer%s" % self.abi_bitness)
         self.custom_ld_library_path = path_utils.JoinTargetPath(
@@ -78,30 +78,49 @@
                 path_utils.JoinTargetPath(self.data_file_path,
                                           "hal-hidl-trace", trace_path),
                 target_trace_path)
-
             service_instance_combinations = self._GetServiceInstanceCombinations(
                 target_trace_path)
 
-            for instance_combination in service_instance_combinations:
-                test_case = super(HalHidlReplayTest, self).CreateTestCase(
-                    self.driver_binary_path, '')
-                test_case.envp += "LD_LIBRARY_PATH=%s:$LD_LIBRARY_PATH" % self.custom_ld_library_path
-                test_case.args += " --spec_dir_path=" + self.DEVICE_VTS_SPEC_FILE_PATH
-                test_case.test_name = "replay_test_" + trace_file_name
-                for instance in instance_combination:
-                    test_case.args += " --hal_service_instance=" + instance
-                    test_case.args += " " + target_trace_path
-                    test_case.tag += instance[instance.find('/'):]
+            if service_instance_combinations:
+                for instance_combination in service_instance_combinations:
+                    test_case = self.CreateReplayTestCase(
+                        trace_file_name, target_trace_path)
+                    service_name_list = []
+                    for instance in instance_combination:
+                        test_case.args += " --hal_service_instance=" + instance
+                        service_name_list.append(
+                            instance[instance.find('/') + 1:])
+                    name_appendix = "({0})".format(",".join(service_name_list))
+                    test_case.name_appendix = name_appendix
+                    self.testcases.append(test_case)
+            else:
+                test_case = self.CreateReplayTestCase(trace_file_name,
+                                                      target_trace_path)
                 self.testcases.append(test_case)
 
-    def VerifyTestResult(self, test_case, command_results):
-        '''Parse Gtest xml result output.
+    def CreateReplayTestCase(self, trace_file_name, target_trace_path):
+        """Create a replay test case object.
 
         Args:
-            test_case: GtestTestCase object, the test being run. This param
+            trace_file_name: string, name of the trace file used in the test.
+            target_trace_path: string, full path of the trace file or the target device.
+        """
+        test_case = super(HalHidlReplayTest, self).CreateTestCase(
+            self.replayer_binary_path, '')
+        test_case.envp += "LD_LIBRARY_PATH=%s:$LD_LIBRARY_PATH" % self.custom_ld_library_path
+        test_case.args += " --spec_dir_path=" + self.DEVICE_VTS_SPEC_FILE_PATH
+        test_case.args += " " + target_trace_path
+        test_case.test_name = "replay_test_" + trace_file_name
+        return test_case
+
+    def VerifyTestResult(self, test_case, command_results):
+        """Parse Gtest xml result output.
+
+        Args:
+            test_case: BinaryTestCase object, the test being run. This param
                        is not currently used in this method.
             command_results: dict of lists, shell command result
-        '''
+        """
         asserts.assertTrue(command_results, 'Empty command response.')
         asserts.assertEqual(
             len(command_results), 3, 'Abnormal command response.')
@@ -109,7 +128,7 @@
         for stdout in command_results[const.STDOUT]:
             if stdout and stdout.strip():
                 for line in stdout.split('\n'):
-                    logging.info(line)
+                    logging.debug(line)
 
         if any(command_results[const.EXIT_CODE]):
             # print stderr only when test fails.
@@ -117,19 +136,20 @@
                 if stderr and stderr.strip():
                     for line in stderr.split('\n'):
                         logging.error(line)
-            asserts.fail('Test {} failed with the following results: {}'.format(
+            asserts.fail(
+                'Test {} failed with the following results: {}'.format(
                     test_case, command_results))
 
     def tearDownClass(self):
         """Performs clean-up tasks."""
         # Delete the pushed file.
-        if not self._skip_all_testcases:
+        if not self.isSkipAllTests():
             for trace_path in self.trace_paths:
                 trace_file_name = str(os.path.basename(trace_path))
                 target_trace_path = path_utils.JoinTargetPath(
                     self.DEVICE_TMP_DIR, "vts_replay_trace", trace_file_name)
-                cmd_results = self.shell.Execute("rm -f %s" %
-                                                 target_trace_path)
+                cmd_results = self.shell.Execute(
+                    "rm -f %s" % target_trace_path)
                 if not cmd_results or any(cmd_results[const.EXIT_CODE]):
                     logging.warning("Failed to remove: %s", cmd_results)
 
@@ -161,13 +181,17 @@
         """
         registered_services = []
         service_instances = {}
+        self.shell.Execute("chmod 755 %s" % self.replayer_binary_path)
         results = self.shell.Execute(
             "LD_LIBRARY_PATH=%s:$LD_LIBRARY_PATH %s --list_service %s" %
-            (self.custom_ld_library_path, self.driver_binary_path, trace_path))
+            (self.custom_ld_library_path, self.replayer_binary_path,
+             trace_path))
 
-        if (results[const.EXIT_CODE][0]):
-            logging.error('Failed to list test cases.')
-            return None
+        asserts.assertFalse(
+            results[const.EXIT_CODE][0],
+            'Failed to list test cases. EXIT_CODE: %s\n STDOUT: %s\n STDERR: %s\n'
+            % (results[const.EXIT_CODE][0], results[const.STDOUT][0],
+               results[const.STDERR][0]))
 
         # parse the results to get the registered service list.
         for line in results[const.STDOUT][0].split('\n'):
@@ -177,12 +201,20 @@
                 registered_services.append(service)
 
         for service in registered_services:
-            _, service_names = hal_service_name_utils.GetHalServiceName(
+            testable, service_names = hal_service_name_utils.GetHalServiceName(
                 self.shell, service, self.abi_bitness,
                 self.run_as_compliance_test)
+            if not testable:
+                self.skipAllTests("Hal: %s is not testable, "
+                                  "skip all tests." % service)
+                return []
             if service_names:
                 service_instances[service] = service_names
                 self._test_hal_services.add(service)
+            else:
+                self.skipAllTests("No service name found for: %s, "
+                                  "skip all tests." % service)
+                return []
         logging.info("registered service instances: %s", service_instances)
 
         service_instance_combinations = \
diff --git a/testcases/template/host_binary_test/host_binary_test.py b/testcases/template/host_binary_test/host_binary_test.py
index 9dcec41..4a63bdf 100644
--- a/testcases/template/host_binary_test/host_binary_test.py
+++ b/testcases/template/host_binary_test/host_binary_test.py
@@ -52,9 +52,9 @@
 
             cmd_result = cmd_utils.ExecuteShellCommand(binary_path)
             asserts.assertFalse(
-                any(results[cmd_utils.EXIT_CODE]),
+                any(cmd_result[cmd_utils.EXIT_CODE]),
                 "Test failed with the following results:\n "
-                "command result: %s" % results)
+                "command result: %s" % cmd_result)
 
 
 if __name__ == "__main__":
diff --git a/testcases/template/llvmfuzzer_test/llvmfuzzer_test.py b/testcases/template/llvmfuzzer_test/llvmfuzzer_test.py
index 2440a4c..6216e75 100644
--- a/testcases/template/llvmfuzzer_test/llvmfuzzer_test.py
+++ b/testcases/template/llvmfuzzer_test/llvmfuzzer_test.py
@@ -36,7 +36,10 @@
     Attributes:
         _dut: AndroidDevice, the device under test as config
         _testcases: string list, list of testcases to run
+        start_vts_agents: whether to start vts agents when registering new
+                          android devices.
     """
+    start_vts_agents = False
 
     def setUpClass(self):
         """Creates a remote shell instance, and copies data files."""
@@ -48,13 +51,13 @@
 
         self._testcases = map(lambda x: str(x), self.fuzzer_configs.keys())
 
-        logging.info("Testcases: %s", self._testcases)
-        logging.info("%s: %s", keys.ConfigKeys.IKEY_DATA_FILE_PATH,
+        logging.debug("Testcases: %s", self._testcases)
+        logging.debug("%s: %s", keys.ConfigKeys.IKEY_DATA_FILE_PATH,
                      self.data_file_path)
-        logging.info("%s: %s", config.ConfigKeys.FUZZER_CONFIGS,
+        logging.debug("%s: %s", config.ConfigKeys.FUZZER_CONFIGS,
                      self.fuzzer_configs)
 
-        self._dut = self.registerController(android_device, False)[0]
+        self._dut = self.android_devices[0]
         self._dut.adb.shell("mkdir %s -p" % config.FUZZER_TEST_DIR)
 
     def tearDownClass(self):
@@ -70,7 +73,7 @@
         push_src = os.path.join(self.data_file_path, config.FUZZER_SRC_DIR,
                                 testcase)
         self._dut.adb.push("%s %s" % (push_src, config.FUZZER_TEST_DIR))
-        logging.info("Adb pushed: %s", testcase)
+        logging.debug("Adb pushed: %s", testcase)
 
     def CreateFuzzerFlags(self, fuzzer_config):
         """Creates flags for the fuzzer executable.
@@ -177,7 +180,7 @@
         fuzz_cmd = "%s && %s %s %s %s > /dev/null" % (cd_cmd, ld_path,
                                                       test_cmd, corpus_dir,
                                                       test_flags)
-        logging.info("Executing: %s", fuzz_cmd)
+        logging.debug("Executing: %s", fuzz_cmd)
         # TODO(trong): vts shell doesn't handle timeouts properly, change this after it does.
         try:
             stdout = self._dut.adb.shell("'%s'" % fuzz_cmd)
@@ -217,7 +220,7 @@
         for offset in xrange(0, len(output), 2):
             crash_report += "\\x%s" % output[offset:offset + 2]
 
-        logging.info('FUZZER_TEST_CRASH_REPORT for %s: "%s"', fuzzer,
+        logging.debug('FUZZER_TEST_CRASH_REPORT for %s: "%s"', fuzzer,
                      crash_report)
 
     # TODO(trong): differentiate between crashes and sanitizer rule violations.
@@ -232,7 +235,7 @@
             fuzzer: string, name of fuzzer executable.
             result: dict(str, str, int), command results from shell.
         """
-        logging.info("Test result: %s" % result)
+        logging.debug("Test result: %s" % result)
         if not self._dut.hasBooted():
             self._dut.waitForBootCompletion()
             asserts.fail("%s left the device in unresponsive state." % fuzzer)
diff --git a/utils/python/retry/__init__.py b/testcases/template/mobly/__init__.py
similarity index 100%
copy from utils/python/retry/__init__.py
copy to testcases/template/mobly/__init__.py
diff --git a/testcases/template/mobly/mobly_test.py b/testcases/template/mobly/mobly_test.py
new file mode 100644
index 0000000..aabcbab
--- /dev/null
+++ b/testcases/template/mobly/mobly_test.py
@@ -0,0 +1,285 @@
+#
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+import importlib
+import json
+import logging
+import os
+import sys
+import time
+import yaml
+
+from vts.runners.host import asserts
+from vts.runners.host import base_test
+from vts.runners.host import config_parser
+from vts.runners.host import keys
+from vts.runners.host import records
+from vts.runners.host import test_runner
+from vts.utils.python.io import capture_printout
+from vts.utils.python.io import file_util
+
+from mobly import test_runner as mobly_test_runner
+
+
+LIST_TEST_OUTPUT_START = '==========> '
+LIST_TEST_OUTPUT_END = ' <=========='
+# Temp directory inside python log path. The name is required to be
+# the set value for tradefed to skip reading contents as logs.
+TEMP_DIR_NAME = 'temp'
+CONFIG_FILE_NAME = 'test_config.yaml'
+MOBLY_RESULT_JSON_FILE_NAME = 'test_run_summary.json'
+MOBLY_RESULT_YAML_FILE_NAME = 'test_summary.yaml'
+
+
+MOBLY_CONFIG_TEXT = '''TestBeds:
+  - Name: {module_name}
+    Controllers:
+        AndroidDevice:
+          - serial: {serial1}
+          - serial: {serial2}
+
+MoblyParams:
+    LogPath: {log_path}
+'''
+
+#TODO(yuexima):
+# 1. make DEVICES_REQUIRED configurable
+# 2. add include filter function
+DEVICES_REQUIRED = 2
+
+RESULT_KEY_TYPE = 'Type'
+RESULT_TYPE_SUMMARY = 'Summary'
+RESULT_TYPE_RECORD = 'Record'
+RESULT_TYPE_TEST_NAME_LIST = 'TestNameList'
+RESULT_TYPE_CONTROLLER_INFO = 'ControllerInfo'
+
+
+class MoblyTest(base_test.BaseTestClass):
+    '''Template class for running mobly test cases.
+
+    Attributes:
+        mobly_dir: string, mobly test temp directory for mobly runner
+        mobly_config_file_path: string, mobly test config file path
+        result_handlers: dict, map of result type and handler functions
+    '''
+    def setUpClass(self):
+        asserts.assertEqual(
+            len(self.android_devices), DEVICES_REQUIRED,
+            'Exactly %s devices are required for this test.' % DEVICES_REQUIRED
+        )
+
+        for ad in self.android_devices:
+            logging.debug('Android device serial: %s' % ad.serial)
+
+        logging.debug('Test cases: %s' % self.ListTestCases())
+
+        self.mobly_dir = os.path.join(logging.log_path, TEMP_DIR_NAME,
+                                      'mobly', str(time.time()))
+
+        file_util.Makedirs(self.mobly_dir)
+
+        logging.debug('mobly log path: %s' % self.mobly_dir)
+
+        self.result_handlers = {
+            RESULT_TYPE_SUMMARY: self.HandleSimplePrint,
+            RESULT_TYPE_RECORD: self.HandleRecord,
+            RESULT_TYPE_TEST_NAME_LIST: self.HandleSimplePrint,
+            RESULT_TYPE_CONTROLLER_INFO: self.HandleSimplePrint,
+        }
+
+    def tearDownClass(self):
+        ''' Clear the mobly directory.'''
+        file_util.Rmdirs(self.mobly_dir, ignore_errors=True)
+
+    def PrepareConfigFile(self):
+        '''Prepare mobly config file for running test.'''
+        self.mobly_config_file_path = os.path.join(self.mobly_dir,
+                                                   CONFIG_FILE_NAME)
+        config_text = MOBLY_CONFIG_TEXT.format(
+              module_name=self.test_module_name,
+              serial1=self.android_devices[0].serial,
+              serial2=self.android_devices[1].serial,
+              log_path=self.mobly_dir
+        )
+        with open(self.mobly_config_file_path, 'w') as f:
+            f.write(config_text)
+
+    def ListTestCases(self):
+        '''List test cases.
+
+        Returns:
+            List of string, test names.
+        '''
+        classes = mobly_test_runner._find_test_class()
+
+        with capture_printout.CaptureStdout() as output:
+            mobly_test_runner._print_test_names(classes)
+
+        test_names = []
+
+        for line in output:
+            if (not line.startswith(LIST_TEST_OUTPUT_START)
+                and line.endswith(LIST_TEST_OUTPUT_END)):
+                test_names.append(line)
+                tr_record = records.TestResultRecord(line, self.test_module_name)
+                self.results.requested.append(tr_record)
+
+        return test_names
+
+    def RunMoblyModule(self):
+        '''Execute mobly test module.'''
+        # Because mobly and vts uses a similar runner, both will modify
+        # log_path from python logging. The following step is to preserve
+        # log path after mobly test finishes.
+
+        # An alternative way is to start a new python process through shell
+        # command. In that case, test print out needs to be piped.
+        # This will also help avoid log overlapping
+
+        logger = logging.getLogger()
+        logger_path = logger.log_path
+        logging_path = logging.log_path
+
+        try:
+            mobly_test_runner.main(argv=['-c', self.mobly_config_file_path])
+        finally:
+            logger.log_path = logger_path
+            logging.log_path = logging_path
+
+    def GetMoblyResults(self):
+        '''Get mobly module run results and put in vts results.'''
+        file_handlers = (
+            (MOBLY_RESULT_YAML_FILE_NAME, self.ParseYamlResults),
+            (MOBLY_RESULT_JSON_FILE_NAME, self.ParseJsonResults),
+        )
+
+        for pair in file_handlers:
+            file_path = file_util.FindFile(self.mobly_dir, pair[0])
+
+            if file_path:
+                logging.debug('Mobly test yaml result path: %s', file_path)
+                pair[1](file_path)
+                return
+
+        asserts.fail('Mobly test result file not found.')
+
+    def generateAllTests(self):
+        '''Run the mobly test module and parse results.'''
+        #TODO(yuexima): report test names
+
+        self.PrepareConfigFile()
+        self.RunMoblyModule()
+        #TODO(yuexima): check whether DEBUG logs from mobly run are included
+        self.GetMoblyResults()
+
+    def ParseJsonResults(self, result_path):
+        '''Parse mobly test json result.
+
+        Args:
+            result_path: string, result json file path.
+        '''
+        with open(path, 'r') as f:
+            mobly_summary = json.load(f)
+
+        mobly_results = mobly_summary['Results']
+        for result in mobly_results:
+            logging.debug('Adding result for %s' % result[records.TestResultEnums.RECORD_NAME])
+            record = records.TestResultRecord(result[records.TestResultEnums.RECORD_NAME])
+            record.test_class = result[records.TestResultEnums.RECORD_CLASS]
+            record.begin_time = result[records.TestResultEnums.RECORD_BEGIN_TIME]
+            record.end_time = result[records.TestResultEnums.RECORD_END_TIME]
+            record.result = result[records.TestResultEnums.RECORD_RESULT]
+            record.uid = result[records.TestResultEnums.RECORD_UID]
+            record.extras = result[records.TestResultEnums.RECORD_EXTRAS]
+            record.details = result[records.TestResultEnums.RECORD_DETAILS]
+            record.extra_errors = result[records.TestResultEnums.RECORD_EXTRA_ERRORS]
+
+            self.results.addRecord(record)
+
+    def ParseYamlResults(self, result_path):
+        '''Parse mobly test yaml result.
+
+        Args:
+            result_path: string, result yaml file path.
+        '''
+        with open(result_path, 'r') as stream:
+            try:
+                docs = yaml.load_all(stream)
+                for doc in docs:
+                    type = doc.get(RESULT_KEY_TYPE)
+                    if type is None:
+                        logging.warn(
+                            'Mobly result document type unrecognized: %s', doc)
+                        continue
+
+                    logging.debug('Parsing result type: %s', type)
+
+                    handler = self.result_handlers.get(type)
+                    if handler is None:
+                        logging.debug('Unknown result type: %s', type)
+                        handler = self.HandleSimplePrint
+
+                    handler(doc)
+            except yaml.YAMLError as exc:
+                print(exc)
+
+    def HandleRecord(self, doc):
+        '''Handle record result document type.
+
+        Args:
+            doc: dict, result document item
+        '''
+        logging.debug('Adding result for %s' % doc.get(records.TestResultEnums.RECORD_NAME))
+        record = records.TestResultRecord(doc.get(records.TestResultEnums.RECORD_NAME))
+        record.test_class = doc.get(records.TestResultEnums.RECORD_CLASS)
+        record.begin_time = doc.get(records.TestResultEnums.RECORD_BEGIN_TIME)
+        record.end_time = doc.get(records.TestResultEnums.RECORD_END_TIME)
+        record.result = doc.get(records.TestResultEnums.RECORD_RESULT)
+        record.uid = doc.get(records.TestResultEnums.RECORD_UID)
+        record.extras = doc.get(records.TestResultEnums.RECORD_EXTRAS)
+        record.details = doc.get(records.TestResultEnums.RECORD_DETAILS)
+        record.extra_errors = doc.get(records.TestResultEnums.RECORD_EXTRA_ERRORS)
+
+        # 'Stacktrace' in yaml result is ignored. 'Stacktrace' is a more
+        # detailed version of record.details when exception is emitted.
+
+        self.results.addRecord(record)
+
+    def HandleSimplePrint(self, doc):
+        '''Simply print result document to log.
+
+        Args:
+            doc: dict, result document item
+        '''
+        for k, v in doc.items():
+            logging.debug(str(k) + ": " + str(v))
+
+def GetTestModuleNames():
+    '''Returns a list of mobly test module specified in test configuration.'''
+    configs = config_parser.load_test_config_file(sys.argv[1])
+    reduce_func = lambda x, y: x + y.get(keys.ConfigKeys.MOBLY_TEST_MODULE, [])
+    return reduce(reduce_func, configs, [])
+
+def ImportTestModules():
+    '''Dynamically import mobly test modules.'''
+    for module_name in GetTestModuleNames():
+        module, cls = module_name.rsplit('.', 1)
+        sys.modules['__main__'].__dict__[cls] = getattr(
+            importlib.import_module(module), cls)
+
+if __name__ == "__main__":
+    ImportTestModules()
+    test_runner.main()
diff --git a/harnesses/host_controller/__init__.py b/testcases/template/param_test/__init__.py
similarity index 100%
copy from harnesses/host_controller/__init__.py
copy to testcases/template/param_test/__init__.py
diff --git a/testcases/template/param_test/param_test.py b/testcases/template/param_test/param_test.py
new file mode 100644
index 0000000..aafa715
--- /dev/null
+++ b/testcases/template/param_test/param_test.py
@@ -0,0 +1,107 @@
+#
+# Copyright (C) 2018 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+import logging
+
+from vts.runners.host import base_test
+from vts.runners.host import records
+from vts.runners.host import test_runner
+
+
+class ParamTestClass(base_test.BaseTestClass):
+    """Base class to run a parameterized test.
+
+    A parameterized test is a test with a set of parameters and the test will be
+    run against each parameter. This allows to test logic with different
+    parameters without without writing multiple copies of the same test.
+
+    An example use case of parameterized test is service name aware HAL testing
+    which we expect to run the same test logic against all service instances
+    through their corresponding service names. e.g to test graphics.composer HAL
+    against two different instance: default and vr.
+
+    Attributes:
+        params: list, a list of parameters for test run.
+        cur_param: the parameter used for the current run.
+    """
+
+    def __init__(self, configs):
+        super(ParamTestClass, self).__init__(configs)
+        self.initParams()
+
+    def initParams(self):
+        """Initialize test parameters. Expected to be overridden by a subclass."""
+        self._params = []
+
+    def getParamTag(self, param):
+        """Get the test tag used to attach with test name from the parameter.
+
+        expected to be overridden by a subclass.
+
+        Args:
+            param: the current test parameter.
+        """
+        return str(param)
+
+    @property
+    def params(self):
+        """Get params"""
+        return self._params
+
+    @params.setter
+    def params(self, params):
+        """Set params"""
+        self._params = params
+
+    @property
+    def cur_param(self):
+        """Get cur_param"""
+        return self._cur_param
+
+    @cur_param.setter
+    def cur_param(self, cur_param):
+        """Set cur_param"""
+        self._cur_param = cur_param
+
+    def run(self, test_names=None):
+        """Run a parameterized test.
+
+        For each parameter initialized for the test, runs test cases within
+        this test class against that parameter.
+
+        Args:
+            test_names: A list of string that are test case names requested in
+                cmd line.
+
+        Returns:
+            The test results object of this class.
+        """
+        logging.info("==========> %s <==========", self.test_module_name)
+        # Get the original tests.
+        tests = self.getTests(test_names)
+        # Run the set of original tests against each parameter.
+        for param in self.params:
+            self.cur_param = param
+            for idx, (test_name, test_func) in enumerate(tests):
+                param_test_name = str(test_name + self.getParamTag(param))
+                tests[idx] = (param_test_name, test_func)
+            if not self.run_as_vts_self_test:
+                self.results.requested = [
+                    records.TestResultRecord(test_name, self.test_module_name)
+                    for test_name, _ in tests
+                ]
+            self.runTests(tests)
+        return self.results
diff --git a/testcases/vts_selftest/manual_tests/README.txt b/testcases/vts_selftest/manual_tests/README.txt
new file mode 100644
index 0000000..5aff089
--- /dev/null
+++ b/testcases/vts_selftest/manual_tests/README.txt
@@ -0,0 +1,10 @@
+This directory contains test cases that require to be manually run.
+
+Test cases in this directory could be designed to:
+  crash device
+  crash test framework
+  have inconsistent test result
+  etc.
+
+It is up to the user's understanding and precaution before running tests
+from this directory.
\ No newline at end of file
diff --git a/harnesses/host_controller/__init__.py b/testcases/vts_selftest/manual_tests/__init__.py
similarity index 100%
copy from harnesses/host_controller/__init__.py
copy to testcases/vts_selftest/manual_tests/__init__.py
diff --git a/tools/vts-hc/Android.mk b/testcases/vts_selftest/manual_tests/flaky_hidl_test/Android.mk
similarity index 79%
copy from tools/vts-hc/Android.mk
copy to testcases/vts_selftest/manual_tests/flaky_hidl_test/Android.mk
index 7733142..ca1258f 100644
--- a/tools/vts-hc/Android.mk
+++ b/testcases/vts_selftest/manual_tests/flaky_hidl_test/Android.mk
@@ -1,4 +1,5 @@
-# Copyright (C) 2017 The Android Open Source Project
+#
+# Copyright (C) 2018 The Android Open Source Project
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -11,12 +12,9 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
-
+#
 LOCAL_PATH := $(call my-dir)
 
 include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := optional
-LOCAL_PREBUILT_EXECUTABLES := run
-include $(BUILD_HOST_PREBUILT)
-
+LOCAL_MODULE := VtsSelfTestFlakyHidlTest
+include test/vts/tools/build/Android.host_config.mk
diff --git a/testcases/vts_selftest/manual_tests/flaky_hidl_test/AndroidTest.xml b/testcases/vts_selftest/manual_tests/flaky_hidl_test/AndroidTest.xml
new file mode 100644
index 0000000..ff65df5
--- /dev/null
+++ b/testcases/vts_selftest/manual_tests/flaky_hidl_test/AndroidTest.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="Config for VTS Self Test Flaky HIDL Test Case(s)">
+    <option name="config-descriptor:metadata" key="plan" value="vts-staging-selftest" />
+
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.VtsFilePusher">
+        <option name="abort-on-push-failure" value="false"/>
+        <option name="push-group" value="HalHidlTargetTest.push"/>
+    </target_preparer>
+
+    <test class="com.android.tradefed.testtype.VtsMultiDeviceTest">
+        <option name="test-module-name" value="VtsSelfTestFlakyHidlTest" />
+        <option name="binary-test-source" value="_32bit::DATA/nativetest/vts_selftest_flaky_test/vts_selftest_flaky_test" />
+        <option name="binary-test-source" value="_64bit::DATA/nativetest64/vts_selftest_flaky_test/vts_selftest_flaky_test" />
+        <option name="binary-test-type" value="hal_hidl_gtest"/>
+        <option name="binary-test-disable-framework" value="true"/>
+        <option name="binary-test-stop-native-servers" value="true"/>
+        <option name="precondition-lshal" value="android.hardware.nfc@1.0"/>
+        <option name="test-timeout" value="30s"/>
+    </test>
+</configuration>
diff --git a/testcases/vts_selftest/manual_tests/flaky_test/Android.bp b/testcases/vts_selftest/manual_tests/flaky_test/Android.bp
new file mode 100644
index 0000000..9858d68
--- /dev/null
+++ b/testcases/vts_selftest/manual_tests/flaky_test/Android.bp
@@ -0,0 +1,26 @@
+//
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+cc_test {
+    name: "vts_selftest_flaky_test",
+    srcs: ["vts_selftest_flaky_test.cpp"],
+    cflags: [
+        "-Wall",
+        "-Werror",
+        "-O0",
+        "-g",
+    ],
+}
diff --git a/tools/vts-hc/Android.mk b/testcases/vts_selftest/manual_tests/flaky_test/Android.mk
similarity index 79%
copy from tools/vts-hc/Android.mk
copy to testcases/vts_selftest/manual_tests/flaky_test/Android.mk
index 7733142..d743e41 100644
--- a/tools/vts-hc/Android.mk
+++ b/testcases/vts_selftest/manual_tests/flaky_test/Android.mk
@@ -1,4 +1,5 @@
-# Copyright (C) 2017 The Android Open Source Project
+#
+# Copyright (C) 2018 The Android Open Source Project
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -11,12 +12,9 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
-
+#
 LOCAL_PATH := $(call my-dir)
 
 include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := optional
-LOCAL_PREBUILT_EXECUTABLES := run
-include $(BUILD_HOST_PREBUILT)
-
+LOCAL_MODULE := VtsSelfTestFlakyTest
+include test/vts/tools/build/Android.host_config.mk
diff --git a/testcases/vts_selftest/manual_tests/flaky_test/AndroidTest.xml b/testcases/vts_selftest/manual_tests/flaky_test/AndroidTest.xml
new file mode 100644
index 0000000..53200a2
--- /dev/null
+++ b/testcases/vts_selftest/manual_tests/flaky_test/AndroidTest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="Config for VTS Self Test Flaky Test Case(s)">
+    <option name="config-descriptor:metadata" key="plan" value="vts-staging-selftest" />
+
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.VtsFilePusher">
+        <option name="push-group" value="HostDrivenTest.push" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.VtsMultiDeviceTest">
+        <option name="test-module-name" value="VtsSelfTestFlakyTest" />
+        <option name="binary-test-working-directory" value="_32bit::/data/nativetest/" />
+        <option name="binary-test-working-directory" value="_64bit::/data/nativetest64/" />
+        <option name="binary-test-source" value="_32bit::DATA/nativetest/vts_selftest_flaky_test/vts_selftest_flaky_test" />
+        <option name="binary-test-source" value="_64bit::DATA/nativetest64/vts_selftest_flaky_test/vts_selftest_flaky_test" />
+        <option name="binary-test-type" value="gtest" />
+        <option name="test-timeout" value="30s"/>
+    </test>
+</configuration>
diff --git a/testcases/vts_selftest/manual_tests/flaky_test/vts_selftest_flaky_test.cpp b/testcases/vts_selftest/manual_tests/flaky_test/vts_selftest_flaky_test.cpp
new file mode 100644
index 0000000..b63adfe
--- /dev/null
+++ b/testcases/vts_selftest/manual_tests/flaky_test/vts_selftest_flaky_test.cpp
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <gtest/gtest.h>
+#define LOG_TAG "VtsSelfTestFlakyTest"
+#include <log/log.h>
+
+class VtsSelfTestFlakyTest : public ::testing::Test {
+ public:
+  virtual void SetUp() override {
+    struct timeval time;
+    gettimeofday(&time, NULL);
+
+    srand((time.tv_sec * 1000) + (time.tv_usec / 1000));
+  }
+
+  virtual void TearDown() override {}
+};
+
+/**
+ * Always passing.
+ */
+TEST_F(VtsSelfTestFlakyTest, TestAlwaysPassing1) {
+  printf("PASS (with 100%% chance)\n");
+}
+
+TEST_F(VtsSelfTestFlakyTest, TestAlwaysPassing2) {
+  printf("PASS (with 100%% chance)\n");
+}
+
+/**
+ * Fails with 50% of chance.
+ */
+TEST_F(VtsSelfTestFlakyTest, TestFlaky1) {
+  int number = abs(rand());
+  printf("number: %d\n", number);
+  ASSERT_TRUE((number % 2) == 0);
+}
+
+TEST_F(VtsSelfTestFlakyTest, TestFlaky2) {
+  int number = abs(rand());
+  printf("number: %d\n", number);
+  ASSERT_TRUE((number % 2) == 0);
+}
+
+TEST_F(VtsSelfTestFlakyTest, TestFlaky3) {
+  int number = abs(rand());
+  printf("number: %d\n", number);
+  ASSERT_TRUE((number % 2) == 0);
+}
+
+TEST_F(VtsSelfTestFlakyTest, TestFlaky4) {
+  int number = abs(rand());
+  printf("number: %d\n", number);
+  ASSERT_TRUE((number % 2) == 0);
+}
+
+TEST_F(VtsSelfTestFlakyTest, TestFlaky5) {
+  int number = abs(rand());
+  printf("number: %d\n", number);
+  ASSERT_TRUE((number % 2) == 0);
+}
+
+int main(int argc, char **argv) {
+  ::testing::InitGoogleTest(&argc, argv);
+  return RUN_ALL_TESTS();
+}
diff --git a/tools/vts-hc/Android.mk b/testcases/vts_selftest/manual_tests/shell_performance/Android.mk
similarity index 79%
copy from tools/vts-hc/Android.mk
copy to testcases/vts_selftest/manual_tests/shell_performance/Android.mk
index 7733142..8474ea1 100644
--- a/tools/vts-hc/Android.mk
+++ b/testcases/vts_selftest/manual_tests/shell_performance/Android.mk
@@ -1,4 +1,5 @@
-# Copyright (C) 2017 The Android Open Source Project
+#
+# Copyright (C) 2018 The Android Open Source Project
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -11,12 +12,9 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
-
+#
 LOCAL_PATH := $(call my-dir)
 
 include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := optional
-LOCAL_PREBUILT_EXECUTABLES := run
-include $(BUILD_HOST_PREBUILT)
-
+LOCAL_MODULE := VtsSelfTestShellPerformance
+include test/vts/tools/build/Android.host_config.mk
diff --git a/testcases/vts_selftest/manual_tests/shell_performance/AndroidTest.xml b/testcases/vts_selftest/manual_tests/shell_performance/AndroidTest.xml
new file mode 100644
index 0000000..d21d5cc
--- /dev/null
+++ b/testcases/vts_selftest/manual_tests/shell_performance/AndroidTest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="Config for VTS Self Test Shell Performance Banchmark">
+    <option name="config-descriptor:metadata" key="plan" value="vts-staging-selftest" />
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.VtsFilePusher">
+        <option name="push-group" value="HostDrivenTest.push" />
+        <option name="push" value="DATA/nativetest64/vts_selftest_zero_testcase_binary_test/vts_selftest_zero_testcase_binary_test->/data/local/tmp/zero_testcase"/>
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.VtsMultiDeviceTest">
+        <option name="test-module-name" value="VtsSelfTestShellPerformance" />
+        <option name="test-case-path" value="vts/testcases/vts_selftest/manual_tests/shell_performance/VtsSelfTestShellPerformance" />
+    </test>
+</configuration>
diff --git a/testcases/vts_selftest/manual_tests/shell_performance/VtsSelfTestShellPerformance.py b/testcases/vts_selftest/manual_tests/shell_performance/VtsSelfTestShellPerformance.py
new file mode 100644
index 0000000..e95d348
--- /dev/null
+++ b/testcases/vts_selftest/manual_tests/shell_performance/VtsSelfTestShellPerformance.py
@@ -0,0 +1,99 @@
+#!/usr/bin/env python
+#
+# Copyright (C) 2018 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+import logging
+
+from vts.runners.host import asserts
+from vts.runners.host import base_test
+from vts.runners.host import const
+from vts.runners.host import test_runner
+import time
+
+
+class VtsSelfTestShellPerformance(base_test.BaseTestClass):
+    '''A simple performance test to compare adb shell and VTS shell.'''
+
+    def setUpClass(self):
+        # Since we are running the actual test cases, run_as_vts_self_test
+        # must be set to False.
+        self.run_as_vts_self_test = False
+
+        self.dut = self.android_devices[0]
+        self.shell = self.dut.shell
+
+    def VtsShell(self, cmd, n):
+        '''Execute a command for n times via VTS shell.
+
+        Args:
+            cmd: string, command to execute
+            n: int, number of repeated calls
+        '''
+        for i in range(n):
+            self.shell.Execute(cmd)
+
+    def AdbShell(self, cmd, n):
+        '''Execute a command for n times via ADB shell.
+
+        Args:
+            cmd: string, command to execute
+            n: int, number of repeated calls
+        '''
+        for i in range(n):
+            self.dut.adb.shell(cmd)
+
+    def VtsShellList(self, cmd, n):
+        '''Execute a command for n times via VTS shell as a list.
+
+        Args:
+            cmd: string, command to execute
+            n: int, number of repeated calls
+        '''
+        self.shell.Execute([cmd] * n)
+
+    def Measure(self, func, *args):
+        '''Measure time lapsed when executing a function.
+
+        Args:
+            func: function, function to execute
+            *args: list of arguments for the functions
+        '''
+        start = time.time()
+        func(*args)
+        return time.time() - start
+
+    def testPerformance(self):
+        '''Run a empty test case on device for 100 times and log the times.'''
+
+        cmd = "/data/local/tmp/zero_testcase"
+
+        # First call to eliminate caching effects
+        self.AdbShell(cmd, 1)
+        self.VtsShell(cmd, 1)
+
+        repeats = 100
+
+        adb_time = self.Measure(self.AdbShell, cmd, repeats)
+        vts_time = self.Measure(self.VtsShell, cmd, repeats)
+        vts_list_time = self.Measure(self.VtsShellList, cmd, repeats)
+
+        logging.info("adb shell for 100 times = %s", adb_time)
+        logging.info("vts shell for 100 times = %s", vts_time)
+        logging.info("vts shell with for 100 times = %s", vts_list_time)
+
+
+if __name__ == "__main__":
+    test_runner.main()
diff --git a/harnesses/host_controller/__init__.py b/testcases/vts_selftest/manual_tests/shell_performance/__init__.py
similarity index 100%
copy from harnesses/host_controller/__init__.py
copy to testcases/vts_selftest/manual_tests/shell_performance/__init__.py
diff --git a/testcases/vts_selftest/manual_tests/zero_testcase_binary_test/Android.bp b/testcases/vts_selftest/manual_tests/zero_testcase_binary_test/Android.bp
new file mode 100644
index 0000000..8c21fb5
--- /dev/null
+++ b/testcases/vts_selftest/manual_tests/zero_testcase_binary_test/Android.bp
@@ -0,0 +1,26 @@
+//
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+cc_test {
+    name: "vts_selftest_zero_testcase_binary_test",
+    srcs: ["vts_selftest_zero_testcase_binary_test.cpp"],
+    cflags: [
+        "-Wall",
+        "-Werror",
+        "-O0",
+        "-g",
+    ],
+}
diff --git a/tools/vts-hc/Android.mk b/testcases/vts_selftest/manual_tests/zero_testcase_binary_test/Android.mk
similarity index 78%
copy from tools/vts-hc/Android.mk
copy to testcases/vts_selftest/manual_tests/zero_testcase_binary_test/Android.mk
index 7733142..8cbd7ce 100644
--- a/tools/vts-hc/Android.mk
+++ b/testcases/vts_selftest/manual_tests/zero_testcase_binary_test/Android.mk
@@ -1,4 +1,5 @@
-# Copyright (C) 2017 The Android Open Source Project
+#
+# Copyright (C) 2018 The Android Open Source Project
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -11,12 +12,9 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
-
+#
 LOCAL_PATH := $(call my-dir)
 
 include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := optional
-LOCAL_PREBUILT_EXECUTABLES := run
-include $(BUILD_HOST_PREBUILT)
-
+LOCAL_MODULE := VtsSelfTestZeroTestCaseBinaryTest
+include test/vts/tools/build/Android.host_config.mk
diff --git a/testcases/vts_selftest/manual_tests/zero_testcase_binary_test/AndroidTest.xml b/testcases/vts_selftest/manual_tests/zero_testcase_binary_test/AndroidTest.xml
new file mode 100644
index 0000000..dab87b0
--- /dev/null
+++ b/testcases/vts_selftest/manual_tests/zero_testcase_binary_test/AndroidTest.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="Config for VTS Self Test Zero Test Case Binary Test Case(s)">
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.VtsFilePusher">
+        <option name="push-group" value="HostDrivenTest.push" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.VtsMultiDeviceTest">
+        <option name="test-module-name" value="VtsSelfTestZeroTestCaseBinaryTest" />
+        <option name="binary-test-source" value="_32bit::DATA/nativetest/vts_selftest_zero_testcase_binary_test/vts_selftest_zero_testcase_binary_test" />
+        <option name="binary-test-type" value="gtest" />
+        <option name="skip-on-32bit-abi" value="true" />
+        <option name="test-timeout" value="10s"/>
+    </test>
+    <test class="com.android.tradefed.testtype.VtsMultiDeviceTest">
+        <option name="test-module-name" value="VtsSelfTestZeroTestCaseBinaryTest" />
+        <option name="binary-test-source" value="_64bit::DATA/nativetest64/vts_selftest_zero_testcase_binary_test/vts_selftest_zero_testcase_binary_test" />
+        <option name="binary-test-type" value="gtest" />
+        <option name="skip-on-64bit-abi" value="true" />
+        <option name="test-timeout" value="10s"/>
+    </test>
+</configuration>
diff --git a/testcases/vts_selftest/manual_tests/zero_testcase_binary_test/vts_selftest_zero_testcase_binary_test.cpp b/testcases/vts_selftest/manual_tests/zero_testcase_binary_test/vts_selftest_zero_testcase_binary_test.cpp
new file mode 100644
index 0000000..510204a
--- /dev/null
+++ b/testcases/vts_selftest/manual_tests/zero_testcase_binary_test/vts_selftest_zero_testcase_binary_test.cpp
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gtest/gtest.h>
+#define LOG_TAG "VtsSelfTestZeroTestCaseBinaryTest"
+#include <log/log.h>
+
+class VtsSelfTestFlakyTest : public ::testing::Test {
+ public:
+  virtual void SetUp() override {}
+
+  virtual void TearDown() override {}
+};
+
+/**
+ * Always passing.
+ */
+TEST_F(VtsSelfTestFlakyTest, EmptyTest) {}
diff --git a/testcases/vts_selftest/test_framework/base_test/Android.mk b/testcases/vts_selftest/test_framework/base_test/Android.mk
index 7bfa47a..a95e592 100644
--- a/testcases/vts_selftest/test_framework/base_test/Android.mk
+++ b/testcases/vts_selftest/test_framework/base_test/Android.mk
@@ -17,5 +17,4 @@
 
 include $(CLEAR_VARS)
 LOCAL_MODULE := VtsSelfTestBaseTest
-VTS_CONFIG_SRC_DIR := testcases/vts_selftest/test_framework/base_test
 include test/vts/tools/build/Android.host_config.mk
diff --git a/testcases/vts_selftest/test_framework/base_test/AndroidTest.xml b/testcases/vts_selftest/test_framework/base_test/AndroidTest.xml
index 5a8192b..60353f1 100644
--- a/testcases/vts_selftest/test_framework/base_test/AndroidTest.xml
+++ b/testcases/vts_selftest/test_framework/base_test/AndroidTest.xml
@@ -14,14 +14,19 @@
      limitations under the License.
 -->
 <configuration description="Config for VTS Framework Integration Test Case">
+    <option name="config-descriptor:metadata" key="plan" value="vts-misc" />
     <target_preparer class="com.android.compatibility.common.tradefed.targetprep.VtsFilePusher">
         <option name="push-group" value="HostDrivenTest.push" />
     </target_preparer>
-    <target_preparer class="com.android.tradefed.targetprep.VtsPythonVirtualenvPreparer">
-    </target_preparer>
     <test class="com.android.tradefed.testtype.VtsMultiDeviceTest">
         <option name="test-module-name" value="VtsSelfTestBaseTest"/>
         <option name="test-case-path" value="vts/testcases/vts_selftest/test_framework/base_test/VtsSelfTestBaseTest" />
+        <option name="config-str" key="a" value="a"/>
+        <option name="config-str" key="b" value="b"/>
+        <option name="config-int" key="a" value="1"/>
+        <option name="config-int" key="b" value="2"/>
+        <option name="config-bool" key="a" value="true"/>
+        <option name="config-bool" key="b" value="false"/>
     </test>
     <test class="com.android.tradefed.testtype.VtsMultiDeviceTest">
         <option name="test-module-name" value="VtsSelfTestBaseTestFilterInclude"/>
diff --git a/testcases/vts_selftest/test_framework/base_test/VtsSelfTestBaseTest.py b/testcases/vts_selftest/test_framework/base_test/VtsSelfTestBaseTest.py
index b2c3d71..1070602 100644
--- a/testcases/vts_selftest/test_framework/base_test/VtsSelfTestBaseTest.py
+++ b/testcases/vts_selftest/test_framework/base_test/VtsSelfTestBaseTest.py
@@ -86,6 +86,61 @@
         asserts.assertTrue(self.dut.total_memory > 0,
                            'Failed to get device memory info.')
 
+    def test_getUserConfigStr1(self):
+        '''Test getUserConfigStr.'''
+        asserts.assertEqual(self.getUserConfigStr('a'), 'a')
+
+    def test_getUserConfigStr2(self):
+        '''Test getUserConfigStr.'''
+        asserts.assertEqual(self.getUserConfigStr('b'), 'b')
+
+    def test_getUserConfigStr3(self):
+        '''Test getUserConfigStr.'''
+        asserts.assertEqual(self.getUserConfigStr('c'), None)
+
+    def test_getUserConfigStr4(self):
+        '''Test getUserConfigStr.'''
+        asserts.assertEqual(self.getUserConfigStr('c', to_str=True), None)
+
+    def test_getUserConfigInt1(self):
+        '''Test getUserConfigInt.'''
+        asserts.assertEqual(self.getUserConfigInt('a'), 1)
+
+    def test_getUserConfigInt2(self):
+        '''Test getUserConfigInt.'''
+        asserts.assertEqual(self.getUserConfigInt('b'), 2)
+
+    def test_getUserConfigInt3(self):
+        '''Test getUserConfigInt.'''
+        asserts.assertEqual(self.getUserConfigInt('b', to_str=True), '2')
+
+    def test_getUserConfigInt4(self):
+        '''Test getUserConfigInt.'''
+        asserts.assertEqual(self.getUserConfigInt('c'), None)
+
+    def test_getUserConfigInt5(self):
+        '''Test getUserConfigInt.'''
+        asserts.assertEqual(self.getUserConfigInt('c', to_str=True), None)
+
+    def test_getUserConfigBool1(self):
+        '''Test getUserConfigBool.'''
+        asserts.assertEqual(self.getUserConfigBool('a'), True)
+
+    def test_getUserConfigBool2(self):
+        '''Test getUserConfigBool.'''
+        asserts.assertEqual(self.getUserConfigBool('b'), False)
+
+    def test_getUserConfigBool3(self):
+        '''Test getUserConfigBool.'''
+        asserts.assertEqual(self.getUserConfigBool('b', to_str=True), 'False')
+
+    def test_getUserConfigBool4(self):
+        '''Test getUserConfigBool.'''
+        asserts.assertEqual(self.getUserConfigBool('c'), None)
+
+    def test_getUserConfigBool5(self):
+        '''Test getUserConfigBool.'''
+        asserts.assertEqual(self.getUserConfigBool('c', to_str=True), None)
 
 if __name__ == "__main__":
     test_runner.main()
diff --git a/testcases/vts_selftest/test_framework/base_test/VtsSelfTestBaseTestFilter.py b/testcases/vts_selftest/test_framework/base_test/VtsSelfTestBaseTestFilter.py
index 5d53ee9..6cf15da 100644
--- a/testcases/vts_selftest/test_framework/base_test/VtsSelfTestBaseTestFilter.py
+++ b/testcases/vts_selftest/test_framework/base_test/VtsSelfTestBaseTestFilter.py
@@ -37,6 +37,10 @@
 
     # Override
     def setUpClass(self):
+        # Since we are running the actual test cases, run_as_vts_self_test
+        # must be set to False.
+        self.run_as_vts_self_test = False
+
         self.dut = self.android_devices[0]
         self.shell = self.dut.shell
 
diff --git a/testcases/vts_selftest/test_framework/base_test/VtsSelfTestBaseTestFilterExclude.py b/testcases/vts_selftest/test_framework/base_test/VtsSelfTestBaseTestFilterExclude.py
index a346eee..c57fbe0 100644
--- a/testcases/vts_selftest/test_framework/base_test/VtsSelfTestBaseTestFilterExclude.py
+++ b/testcases/vts_selftest/test_framework/base_test/VtsSelfTestBaseTestFilterExclude.py
@@ -39,6 +39,9 @@
         'suite2.test1_32bit',
         'suite2.test1_64bit',
         'suite3.test2_64bit',
+        # Since include_filter is empty, any pattern not matching the ones in
+        # exclude filter should pass
+        'other.test_names',
     ]
 
     SHOULD_NOT_PASS_FILTER = [
@@ -50,8 +53,19 @@
         'suite3.test1_32bit',
         'suite3.test1_64bit',
         'suite3.test2_32bit',
+        # The following test is added to exclude filter through
+        # filter's add_to_exclude_filter method when expand_bitness is True
+        'added.test1_32bit',
+        # The following test is added to include filter with negative pattern by
+        # filter's add_to_exclude_filter method when expand_bitness is True
+        'added.test2_64bit',
     ]
 
+    # Override
+    def setUpClass(self):
+        super(VtsSelfTestBaseTestFilterExclude, self).setUpClass()
+        self.test_filter.add_to_exclude_filter('added.test1')
+        self.test_filter.add_to_include_filter('-added.test2')
 
 if __name__ == "__main__":
     test_runner.main()
diff --git a/testcases/vts_selftest/test_framework/base_test/VtsSelfTestBaseTestFilterInclude.py b/testcases/vts_selftest/test_framework/base_test/VtsSelfTestBaseTestFilterInclude.py
index 3198fba..4817c0f 100644
--- a/testcases/vts_selftest/test_framework/base_test/VtsSelfTestBaseTestFilterInclude.py
+++ b/testcases/vts_selftest/test_framework/base_test/VtsSelfTestBaseTestFilterInclude.py
@@ -39,6 +39,7 @@
         'suite1.test2_32bit',
         'suite2.any_matching_regex',
         'suite3.test1',
+        'added.test1_64bit',
     ]
 
     SHOULD_NOT_PASS_FILTER = [
@@ -48,6 +49,11 @@
         'suite3.test2',
     ]
 
+    # Override
+    def setUpClass(self):
+        super(VtsSelfTestBaseTestFilterInclude, self).setUpClass()
+        self.test_filter.add_to_include_filter('added.test1')
+
 
 if __name__ == "__main__":
     test_runner.main()
diff --git a/testcases/vts_selftest/test_framework/python_virtualenv_preparer_test/README.md b/testcases/vts_selftest/test_framework/python_virtualenv_preparer_test/README.md
new file mode 100644
index 0000000..5288514
--- /dev/null
+++ b/testcases/vts_selftest/test_framework/python_virtualenv_preparer_test/README.md
@@ -0,0 +1,12 @@
+# PythonVirtualenvPreparerTest
+
+This directory tests the functionality of VtsPythonVirtualenvPreparer.
+
+
+Two modules are included in this project:
+
+* VtsSelfTestPythonVirtualenvPreparerTestPart0: to verify the python module (numpy) that's going to be tested has not been installed by default.
+* VtsSelfTestPythonVirtualenvPreparerTestPart1: test duplicated module preparer to install a new module and empty module preparer.
+* VtsSelfTestPythonVirtualenvPreparerTestPart2: test whether a python module installed in previous tests is still available through plan level virtual environment
+
+The naming of `Part0`, `Part1` and `Part2` is to ensure the order of execution.
diff --git a/harnesses/host_controller/__init__.py b/testcases/vts_selftest/test_framework/python_virtualenv_preparer_test/__init__.py
similarity index 100%
copy from harnesses/host_controller/__init__.py
copy to testcases/vts_selftest/test_framework/python_virtualenv_preparer_test/__init__.py
diff --git a/tools/vts-hc/Android.mk b/testcases/vts_selftest/test_framework/python_virtualenv_preparer_test/part0/Android.mk
similarity index 77%
copy from tools/vts-hc/Android.mk
copy to testcases/vts_selftest/test_framework/python_virtualenv_preparer_test/part0/Android.mk
index 7733142..0e09299 100644
--- a/tools/vts-hc/Android.mk
+++ b/testcases/vts_selftest/test_framework/python_virtualenv_preparer_test/part0/Android.mk
@@ -1,4 +1,5 @@
-# Copyright (C) 2017 The Android Open Source Project
+#
+# Copyright (C) 2018 The Android Open Source Project
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -11,12 +12,9 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
-
+#
 LOCAL_PATH := $(call my-dir)
 
 include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := optional
-LOCAL_PREBUILT_EXECUTABLES := run
-include $(BUILD_HOST_PREBUILT)
-
+LOCAL_MODULE := VtsSelfTestPythonVirtualenvPreparerTestPart0
+include test/vts/tools/build/Android.host_config.mk
diff --git a/testcases/vts_selftest/test_framework/python_virtualenv_preparer_test/part0/AndroidTest.xml b/testcases/vts_selftest/test_framework/python_virtualenv_preparer_test/part0/AndroidTest.xml
new file mode 100644
index 0000000..1ad1e46
--- /dev/null
+++ b/testcases/vts_selftest/test_framework/python_virtualenv_preparer_test/part0/AndroidTest.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="Config for VtsSelfTestPythonVirtualenvPreparerTestPart0 Test Case">
+    <option name="config-descriptor:metadata" key="plan" value="vts-misc" />
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.VtsFilePusher">
+        <option name="push-group" value="HostDrivenTest.push" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.VtsMultiDeviceTest">
+        <option name="test-module-name" value="VtsSelfTestPythonVirtualenvPreparerTestPart0"/>
+        <option name="test-case-path" value="vts/testcases/vts_selftest/test_framework/python_virtualenv_preparer_test/part0/VtsSelfTestPythonVirtualenvPreparerTestPart0" />
+    </test>
+</configuration>
diff --git a/testcases/vts_selftest/test_framework/python_virtualenv_preparer_test/part0/VtsSelfTestPythonVirtualenvPreparerTestPart0.py b/testcases/vts_selftest/test_framework/python_virtualenv_preparer_test/part0/VtsSelfTestPythonVirtualenvPreparerTestPart0.py
new file mode 100644
index 0000000..d534362
--- /dev/null
+++ b/testcases/vts_selftest/test_framework/python_virtualenv_preparer_test/part0/VtsSelfTestPythonVirtualenvPreparerTestPart0.py
@@ -0,0 +1,48 @@
+#!/usr/bin/env python
+#
+# Copyright (C) 2018 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+import logging
+
+from vts.runners.host import asserts
+from vts.runners.host import base_test
+from vts.runners.host import const
+from vts.runners.host import test_runner
+
+
+class VtsSelfTestPythonVirtualenvPreparerTestPart0(base_test.BaseTestClass):
+    '''Tests plan and module level VirtualenvPreparer.'''
+
+    def setUpClass(self):
+        # Since we are running the actual test cases, run_as_vts_self_test
+        # must be set to False.
+        self.run_as_vts_self_test = False
+
+    def testNonExistingModule(self):
+        '''Test whether numpy is not installed from default packages.
+
+        This test assumes numpy is not in default package install list.
+        If this turned otherwise, test logic here should be updated.
+        '''
+        try:
+            import numpy
+            asserts.fail('numpy should not have been not installed yet.')
+        except ImportError:
+            pass
+
+
+if __name__ == "__main__":
+    test_runner.main()
diff --git a/harnesses/host_controller/__init__.py b/testcases/vts_selftest/test_framework/python_virtualenv_preparer_test/part0/__init__.py
similarity index 100%
copy from harnesses/host_controller/__init__.py
copy to testcases/vts_selftest/test_framework/python_virtualenv_preparer_test/part0/__init__.py
diff --git a/tools/vts-hc/Android.mk b/testcases/vts_selftest/test_framework/python_virtualenv_preparer_test/part1/Android.mk
similarity index 77%
copy from tools/vts-hc/Android.mk
copy to testcases/vts_selftest/test_framework/python_virtualenv_preparer_test/part1/Android.mk
index 7733142..17bc31a 100644
--- a/tools/vts-hc/Android.mk
+++ b/testcases/vts_selftest/test_framework/python_virtualenv_preparer_test/part1/Android.mk
@@ -1,4 +1,5 @@
-# Copyright (C) 2017 The Android Open Source Project
+#
+# Copyright (C) 2018 The Android Open Source Project
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -11,12 +12,9 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
-
+#
 LOCAL_PATH := $(call my-dir)
 
 include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := optional
-LOCAL_PREBUILT_EXECUTABLES := run
-include $(BUILD_HOST_PREBUILT)
-
+LOCAL_MODULE := VtsSelfTestPythonVirtualenvPreparerTestPart1
+include test/vts/tools/build/Android.host_config.mk
diff --git a/testcases/vts_selftest/test_framework/python_virtualenv_preparer_test/part1/AndroidTest.xml b/testcases/vts_selftest/test_framework/python_virtualenv_preparer_test/part1/AndroidTest.xml
new file mode 100644
index 0000000..119b565
--- /dev/null
+++ b/testcases/vts_selftest/test_framework/python_virtualenv_preparer_test/part1/AndroidTest.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="Config for VtsSelfTestPythonVirtualenvPreparerTestPart1 Test Case">
+    <option name="config-descriptor:metadata" key="plan" value="vts-misc" />
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.VtsFilePusher">
+        <option name="push-group" value="HostDrivenTest.push" />
+    </target_preparer>
+    <multi_target_preparer class="com.android.tradefed.targetprep.VtsPythonVirtualenvPreparer">
+        <option name="dep-module" value="numpy" />
+    </multi_target_preparer>
+    <multi_target_preparer class="com.android.tradefed.targetprep.VtsPythonVirtualenvPreparer">
+        <option name="dep-module" value="numpy" />
+    </multi_target_preparer>
+    <multi_target_preparer class="com.android.tradefed.targetprep.VtsPythonVirtualenvPreparer">
+    </multi_target_preparer>
+    <test class="com.android.tradefed.testtype.VtsMultiDeviceTest">
+        <option name="test-module-name" value="VtsSelfTestPythonVirtualenvPreparerTestPart1"/>
+        <option name="test-case-path" value="vts/testcases/vts_selftest/test_framework/python_virtualenv_preparer_test/part1/VtsSelfTestPythonVirtualenvPreparerTestPart1" />
+    </test>
+</configuration>
diff --git a/testcases/vts_selftest/test_framework/python_virtualenv_preparer_test/part1/VtsSelfTestPythonVirtualenvPreparerTestPart1.py b/testcases/vts_selftest/test_framework/python_virtualenv_preparer_test/part1/VtsSelfTestPythonVirtualenvPreparerTestPart1.py
new file mode 100644
index 0000000..6c3fdbc
--- /dev/null
+++ b/testcases/vts_selftest/test_framework/python_virtualenv_preparer_test/part1/VtsSelfTestPythonVirtualenvPreparerTestPart1.py
@@ -0,0 +1,43 @@
+#!/usr/bin/env python
+#
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+import logging
+
+from vts.runners.host import asserts
+from vts.runners.host import base_test
+from vts.runners.host import const
+from vts.runners.host import test_runner
+
+
+class VtsSelfTestPythonVirtualenvPreparerTestPart1(base_test.BaseTestClass):
+    '''Tests plan and module level VirtualenvPreparer.'''
+
+    def setUpClass(self):
+        # Since we are running the actual test cases, run_as_vts_self_test
+        # must be set to False.
+        self.run_as_vts_self_test = False
+
+    def testInstalledModule(self):
+        '''Test a module installed in this test.'''
+        try:
+            import numpy
+        except ImportError:
+            asserts.fail('numpy should have been installed.')
+
+
+if __name__ == "__main__":
+    test_runner.main()
diff --git a/harnesses/host_controller/__init__.py b/testcases/vts_selftest/test_framework/python_virtualenv_preparer_test/part1/__init__.py
similarity index 100%
copy from harnesses/host_controller/__init__.py
copy to testcases/vts_selftest/test_framework/python_virtualenv_preparer_test/part1/__init__.py
diff --git a/tools/vts-hc/Android.mk b/testcases/vts_selftest/test_framework/python_virtualenv_preparer_test/part2/Android.mk
similarity index 77%
copy from tools/vts-hc/Android.mk
copy to testcases/vts_selftest/test_framework/python_virtualenv_preparer_test/part2/Android.mk
index 7733142..687e5ce 100644
--- a/tools/vts-hc/Android.mk
+++ b/testcases/vts_selftest/test_framework/python_virtualenv_preparer_test/part2/Android.mk
@@ -1,4 +1,5 @@
-# Copyright (C) 2017 The Android Open Source Project
+#
+# Copyright (C) 2018 The Android Open Source Project
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -11,12 +12,9 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
-
+#
 LOCAL_PATH := $(call my-dir)
 
 include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := optional
-LOCAL_PREBUILT_EXECUTABLES := run
-include $(BUILD_HOST_PREBUILT)
-
+LOCAL_MODULE := VtsSelfTestPythonVirtualenvPreparerTestPart2
+include test/vts/tools/build/Android.host_config.mk
diff --git a/testcases/vts_selftest/test_framework/python_virtualenv_preparer_test/part2/AndroidTest.xml b/testcases/vts_selftest/test_framework/python_virtualenv_preparer_test/part2/AndroidTest.xml
new file mode 100644
index 0000000..678e891
--- /dev/null
+++ b/testcases/vts_selftest/test_framework/python_virtualenv_preparer_test/part2/AndroidTest.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="Config for VtsSelfTestPythonVirtualenvPreparerTestPart2 Test Case">
+    <option name="config-descriptor:metadata" key="plan" value="vts-misc" />
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.VtsFilePusher">
+        <option name="push-group" value="HostDrivenTest.push" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.VtsMultiDeviceTest">
+        <option name="test-module-name" value="VtsSelfTestPythonVirtualenvPreparerTestPart2"/>
+        <option name="test-case-path" value="vts/testcases/vts_selftest/test_framework/python_virtualenv_preparer_test/part2/VtsSelfTestPythonVirtualenvPreparerTestPart2" />
+    </test>
+</configuration>
diff --git a/testcases/vts_selftest/test_framework/python_virtualenv_preparer_test/part2/VtsSelfTestPythonVirtualenvPreparerTestPart2.py b/testcases/vts_selftest/test_framework/python_virtualenv_preparer_test/part2/VtsSelfTestPythonVirtualenvPreparerTestPart2.py
new file mode 100644
index 0000000..0e43289
--- /dev/null
+++ b/testcases/vts_selftest/test_framework/python_virtualenv_preparer_test/part2/VtsSelfTestPythonVirtualenvPreparerTestPart2.py
@@ -0,0 +1,43 @@
+#!/usr/bin/env python
+#
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+import logging
+
+from vts.runners.host import asserts
+from vts.runners.host import base_test
+from vts.runners.host import const
+from vts.runners.host import test_runner
+
+
+class VtsSelfTestPythonVirtualenvPreparerTestPart2(base_test.BaseTestClass):
+    '''Tests plan and module level VirtualenvPreparer.'''
+
+    def setUpClass(self):
+        # Since we are running the actual test cases, run_as_vts_self_test
+        # must be set to False.
+        self.run_as_vts_self_test = False
+
+    def testExistingModule(self):
+        '''Test previously installed module's availability.'''
+        try:
+            import numpy
+        except ImportError:
+            asserts.fail('numpy should have been installed in Part1.')
+
+
+if __name__ == "__main__":
+    test_runner.main()
diff --git a/harnesses/host_controller/__init__.py b/testcases/vts_selftest/test_framework/python_virtualenv_preparer_test/part2/__init__.py
similarity index 100%
copy from harnesses/host_controller/__init__.py
copy to testcases/vts_selftest/test_framework/python_virtualenv_preparer_test/part2/__init__.py
diff --git a/tools/build/tasks/list/vts_apk_package_list.mk b/tools/build/tasks/list/vts_apk_package_list.mk
index cccae0d..ec79590 100644
--- a/tools/build/tasks/list/vts_apk_package_list.mk
+++ b/tools/build/tasks/list/vts_apk_package_list.mk
@@ -13,11 +13,15 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+# APKs used by VTS framework.
 vts_apk_packages := \
   VtsAgentApp \
   CtsVerifier \
   sl4a \
 
+# Other tests APKs included as part of VTS.
+vts_apk_packages += \
+  DeviceHealthTests
 
 vts_prebuilt_apk_packages := \
 
diff --git a/tools/build/tasks/list/vts_bin_package_list.mk b/tools/build/tasks/list/vts_bin_package_list.mk
index 76e3be1..86e6808 100644
--- a/tools/build/tasks/list/vts_bin_package_list.mk
+++ b/tools/build/tasks/list/vts_bin_package_list.mk
@@ -21,3 +21,7 @@
   vts_profiling_configure \
   vts_coverage_configure \
   vts_testability_checker \
+
+# Extra apk utils for VTS framework.
+vts_bin_packages += \
+    WifiUtil \
diff --git a/tools/build/tasks/list/vts_test_bin_package_list.mk b/tools/build/tasks/list/vts_test_bin_package_list.mk
index 07ecec4..d23f809 100644
--- a/tools/build/tasks/list/vts_test_bin_package_list.mk
+++ b/tools/build/tasks/list/vts_test_bin_package_list.mk
@@ -27,7 +27,10 @@
     libhwbinder_latency \
     libbinder_benchmark \
     vts_codelab_target_binary \
+    vts_selftest_flaky_test \
+    vts_selftest_zero_testcase_binary_test \
     vts_test_binary_crash_app \
+    vts_test_binary_seg_fault \
     vts_test_binary_syscall_exists \
     simpleperf_cpu_hotplug_test \
     binderThroughputTest \
@@ -38,6 +41,7 @@
     stressapptest \
     libcutils_test \
     vts_test_binary_qtaguid_module \
+    vts_test_binary_bpf_module \
 
 # Proto fuzzer executable
 vts_test_bin_packages += \
@@ -45,14 +49,17 @@
 
 # VTS Treble VINTF Test
 vts_test_bin_packages += \
+    vts_ibase_test \
     vts_treble_vintf_test \
 
 # Netd tests
 vts_test_bin_packages += \
     netd_integration_test \
 
-# Tun device tests.
+# Kernel tests.
 vts_test_bin_packages += \
+    dt_early_mount_test \
+    kernel_net_tests \
     vts_kernel_tun_test \
 
 # Binder tests.
@@ -64,6 +71,7 @@
     binderLibTest_IPC_32 \
     binderTextOutputTest \
     binderSafeInterfaceTest \
+    memunreachable_binder_test \
 
 # VTS security PoC tests
 vts_test_bin_packages += \
@@ -71,4 +79,8 @@
     28838221 \
     32219453 \
     31707909 \
-    32402310
+    32402310 \
+
+# VTS DTBO verification tests
+vts_test_bin_packages += \
+    ufdt_verify_overlay
diff --git a/tools/build/tasks/list/vts_test_host_bin_package_list.mk b/tools/build/tasks/list/vts_test_host_bin_package_list.mk
index 932a0c0..bb190e9 100644
--- a/tools/build/tasks/list/vts_test_host_bin_package_list.mk
+++ b/tools/build/tasks/list/vts_test_host_bin_package_list.mk
@@ -2,3 +2,10 @@
     host_cross_vndk-vtable-dumper \
     trace_processor \
     vndk-vtable-dumper \
+    img2simg \
+    simg2img \
+    mkuserimg_mke2fs.sh \
+
+# Need to package mkdtboimg.py since the tool is not just used by the VTS test.
+vts_test_host_bin_packages += \
+    mkdtboimg.py \
diff --git a/tools/build/tasks/list/vts_test_host_lib_package_list.mk b/tools/build/tasks/list/vts_test_host_lib_package_list.mk
index eef8357..c15180f 100644
--- a/tools/build/tasks/list/vts_test_host_lib_package_list.mk
+++ b/tools/build/tasks/list/vts_test_host_lib_package_list.mk
@@ -1,9 +1,9 @@
 vts_test_host_lib_packages := \
-    host_cross_libLLVM \
+    host_cross_libLLVM_android \
     libbase \
     libc++ \
     libcutils \
-    libLLVM \
+    libLLVM_android \
     liblog \
     libprotobuf-cpp-full \
     libvts_multidevice_proto \
diff --git a/tools/build/tasks/list/vts_test_lib_hidl_package_list.mk b/tools/build/tasks/list/vts_test_lib_hidl_package_list.mk
index bdc0f3e..34e99ab 100644
--- a/tools/build/tasks/list/vts_test_lib_hidl_package_list.mk
+++ b/tools/build/tasks/list/vts_test_lib_hidl_package_list.mk
@@ -49,6 +49,7 @@
   android.hardware.memtrack@1.0-vts.driver \
   android.hardware.neuralnetworks@1.0-vts.driver \
   android.hardware.nfc@1.0-vts.driver \
+  android.hardware.nfc@1.1-vts.driver \
   android.hardware.oemlock@1.0-vts.driver \
   android.hardware.power@1.0-vts.driver \
   android.hardware.power@1.1-vts.driver \
@@ -56,6 +57,7 @@
   android.hardware.radio@1.1-vts.driver \
   android.hardware.radio.deprecated@1.0-vts.driver \
   android.hardware.renderscript@1.0-vts.driver \
+  android.hardware.secure_element@1.0-vts.driver \
   android.hardware.sensors@1.0-vts.driver \
   android.hardware.soundtrigger@2.0-vts.driver \
   android.hardware.tetheroffload.config@1.0-vts.driver \
@@ -111,6 +113,7 @@
   android.hardware.memtrack@1.0-vts.profiler \
   android.hardware.neuralnetworks@1.0-vts.profiler \
   android.hardware.nfc@1.0-vts.profiler \
+  android.hardware.nfc@1.1-vts.profiler \
   android.hardware.oemlock@1.0-vts.profiler \
   android.hardware.power@1.0-vts.profiler \
   android.hardware.power@1.1-vts.profiler \
@@ -118,6 +121,7 @@
   android.hardware.radio@1.1-vts.profiler \
   android.hardware.radio.deprecated@1.0-vts.profiler \
   android.hardware.renderscript@1.0-vts.profiler \
+  android.hardware.secure_element@1.0-vts.profiler \
   android.hardware.sensors@1.0-vts.profiler \
   android.hardware.soundtrigger@2.0-vts.profiler \
   android.hardware.tetheroffload.config@1.0-vts.profiler \
@@ -150,6 +154,7 @@
   VtsHalContexthubV1_0TargetTest \
   VtsHalDrmV1_0TargetTest \
   VtsHalDumpstateV1_0TargetTest \
+  VtsHalEvsV1_0TargetTest \
   VtsHalGatekeeperV1_0TargetTest \
   VtsHalGnssV1_0TargetTest \
   VtsHalGraphicsComposerV2_1TargetTest \
@@ -166,13 +171,16 @@
   VtsHalMemtrackV1_0TargetTest \
   VtsHalNeuralnetworksV1_0TargetTest \
   VtsHalNfcV1_0TargetTest \
+  VtsHalNfcV1_1TargetTest \
   VtsHalOemLockV1_0TargetTest \
   VtsHalPowerV1_0TargetTest \
   VtsHalPowerV1_1TargetTest \
   VtsHalRadioV1_0TargetTest \
   VtsHalRadioV1_1TargetTest \
+  VtsHalRadioV1_2TargetTest \
   VtsHalRenderscriptV1_0TargetTest \
   VtsHalSapV1_0TargetTest \
+  VtsHalSecureElementV1_0TargetTest \
   VtsHalSensorsV1_0TargetTest \
   VtsHalSoundtriggerV2_0TargetTest \
   VtsHalTetheroffloadConfigV1_0TargetTest \
diff --git a/tools/build/tasks/list/vts_test_lib_package_list.mk b/tools/build/tasks/list/vts_test_lib_package_list.mk
index 7f33503..84c6b23 100644
--- a/tools/build/tasks/list/vts_test_lib_package_list.mk
+++ b/tools/build/tasks/list/vts_test_lib_package_list.mk
@@ -41,6 +41,10 @@
     libfortify2-tests-clang \
     libgnu-hash-table-library \
     libnstest_dlopened \
+    libnstest_ns_a_public1 \
+    libnstest_ns_a_public1_internal \
+    libnstest_ns_b_public2 \
+    libnstest_ns_b_public3 \
     libnstest_private \
     libnstest_private_external \
     libnstest_public \
diff --git a/tools/build/tasks/vts_package.mk b/tools/build/tasks/vts_package.mk
index 72b2efe..b63d0ee 100644
--- a/tools/build/tasks/vts_package.mk
+++ b/tools/build/tasks/vts_package.mk
@@ -36,7 +36,7 @@
 -include external/ltp/android/ltp_package_list.mk
 
 VTS_OUT_ROOT := $(HOST_OUT)/vts
-VTS_TESTCASES_OUT := $(HOST_OUT)/vts/android-vts/testcases
+VTS_TESTCASES_OUT := $(VTS_OUT_ROOT)/android-vts/testcases
 VTS_TOOLS_OUT := $(VTS_OUT_ROOT)/android-vts/tools
 
 # Packaging rule for android-vts.zip
@@ -47,7 +47,7 @@
 include $(BUILD_SYSTEM)/tasks/tools/compatibility.mk
 
 .PHONY: vts
-vts: $(compatibility_zip) run
+vts: $(compatibility_zip) vtslab adb
 $(call dist-for-goals, vts, $(compatibility_zip))
 
 # Packaging rule for android-vts.zip's testcases dir (DATA subdir).
@@ -111,15 +111,14 @@
   $(call host-native-copy-pairs,$(target_hostdriven_modules),$(VTS_TESTCASES_OUT))
 
 host_additional_deps_copy_pairs := \
-  test/vts/tools/vts-hc/run:$(VTS_TOOLS_OUT)/run \
   test/vts/tools/vts-tradefed/etc/vts-tradefed_win.bat:$(VTS_TOOLS_OUT)/vts-tradefed_win.bat \
-  test/vts/tools/vts-tradefed/CtsDynamicConfig.xml:$(VTS_TESTCASES_OUT)/cts.dynamic
+  test/vts/tools/vts-tradefed/DynamicConfig.xml:$(VTS_TESTCASES_OUT)/cts.dynamic
 
 # Packaging rule for host-side Python logic, configs, and data files
 
 host_framework_files := \
   $(call find-files-in-subdirs,test/vts,"*.py" -and -type f,.) \
-  $(call find-files-in-subdirs,test/vts,"*.config" -and -type f,.) \
+  $(call find-files-in-subdirs,test/vts,"*.runner_conf" -and -type f,.) \
   $(call find-files-in-subdirs,test/vts,"*.push" -and -type f,.)
 
 host_framework_copy_pairs := \
@@ -128,7 +127,7 @@
 
 host_testcase_files := \
   $(call find-files-in-subdirs,test/vts-testcase,"*.py" -and -type f,.) \
-  $(call find-files-in-subdirs,test/vts-testcase,"*.config" -and -type f,.) \
+  $(call find-files-in-subdirs,test/vts-testcase,"*.runner_conf" -and -type f,.) \
   $(call find-files-in-subdirs,test/vts-testcase,"*.push" -and -type f,.) \
   $(call find-files-in-subdirs,test/vts-testcase,"*.dump" -and -type f,.)
 
@@ -143,6 +142,8 @@
   $(foreach f,$(host_kernel_config_files),\
     kernel/configs/$(f):$(VTS_TESTCASES_OUT)/vts/testcases/kernel/config/data/$(f))
 
+ifneq ($(TARGET_BUILD_PDK),true)
+
 host_camera_its_files := \
   $(call find-files-in-subdirs,cts/apps/CameraITS,"*.py" -and -type f,.) \
   $(call find-files-in-subdirs,cts/apps/CameraITS,"*.pdf" -and -type f,.) \
@@ -152,6 +153,12 @@
   $(foreach f,$(host_camera_its_files),\
     cts/apps/CameraITS/$(f):$(VTS_TESTCASES_OUT)/CameraITS/$(f))
 
+else
+
+host_camera_its_copy_pairs :=
+
+endif  # ifneq ($(TARGET_BUILD_PDK),true)
+
 host_systrace_files := \
   $(filter-out .git/%, \
     $(call find-files-in-subdirs,external/chromium-trace,"*" -and -type f,.))
@@ -167,6 +174,13 @@
   $(foreach f,$(media_test_res_files),\
     hardware/interfaces/media/res/$(f):$(VTS_TESTCASES_OUT)/DATA/media/res/$(f))
 
+nbu_p2p_apk_files := \
+  $(call find-files-in-subdirs,test/vts-testcase/nbu/src,"*.apk" -and -type f,.)
+
+nbu_p2p_apk_copy_pairs := \
+  $(foreach f,$(nbu_p2p_apk_files),\
+      test/vts-testcase/nbu/src/$(f):$(VTS_TESTCASES_OUT)/DATA/app/nbu/$(f))
+
 performance_test_res_files := \
   $(call find-files-in-subdirs,test/vts-testcase/performance/res/,"*.*" -and -type f,.) \
 
@@ -202,23 +216,48 @@
   $(foreach f,$(acts_testcases_files),\
     tools/test/connectivity/acts/tests/google/$(f):$(VTS_TESTCASES_OUT)/vts/testcases/acts/$(f))
 
-$(compatibility_zip): \
-  $(call copy-many-files,$(target_native_copy_pairs)) \
-  $(call copy-many-files,$(target_spec_copy_pairs)) \
-  $(call copy-many-files,$(target_trace_copy_pairs)) \
-  $(call copy-many-files,$(target_hostdriven_copy_pairs)) \
-  $(call copy-many-files,$(target_hal_hash_copy_pairs)) \
-  $(call copy-many-files,$(host_additional_deps_copy_pairs)) \
+target_script_files := \
+  $(call find-files-in-subdirs,test/vts/script/target,"*.sh" -and -type f,.)
+
+target_script_copy_pairs := \
+  $(foreach f,$(target_script_files),\
+    test/vts/script/target/$(f):$(VTS_TESTCASES_OUT)/script/target/$(f))
+
+system_property_compatibility_test_res_copy_pairs := \
+  system/sepolicy/public/property_contexts:$(VTS_TESTCASES_OUT)/vts/testcases/security/system_property/data/property_contexts
+
+$(VTS_TESTCASES_OUT)/vts/testcases/vndk/golden/platform_vndk_version.txt:
+	@echo -n $(PLATFORM_VNDK_VERSION) > $@
+
+vts_test_core_copy_pairs := \
   $(call copy-many-files,$(host_framework_copy_pairs)) \
   $(call copy-many-files,$(host_testcase_copy_pairs)) \
+  $(call copy-many-files,$(host_additional_deps_copy_pairs)) \
+  $(call copy-many-files,$(target_spec_copy_pairs)) \
+  $(call copy-many-files,$(target_hal_hash_copy_pairs)) \
+  $(call copy-many-files,$(acts_framework_copy_pairs)) \
+
+vts_copy_pairs := \
+  $(vts_test_core_copy_pairs) \
+  $(call copy-many-files,$(target_native_copy_pairs)) \
+  $(call copy-many-files,$(target_trace_copy_pairs)) \
+  $(call copy-many-files,$(target_hostdriven_copy_pairs)) \
   $(call copy-many-files,$(host_kernel_config_copy_pairs)) \
   $(call copy-many-files,$(host_camera_its_copy_pairs)) \
   $(call copy-many-files,$(host_systrace_copy_pairs)) \
   $(call copy-many-files,$(media_test_res_copy_pairs)) \
+  $(call copy-many-files,$(nbu_p2p_apk_copy_pairs)) \
   $(call copy-many-files,$(performance_test_res_copy_pairs)) \
   $(call copy-many-files,$(audio_test_res_copy_pairs)) \
   $(call copy-many-files,$(kernel_rootdir_test_rc_copy_pairs)) \
-  $(call copy-many-files,$(acts_framework_copy_pairs)) \
   $(call copy-many-files,$(acts_testcases_copy_pairs)) \
+  $(call copy-many-files,$(target_script_copy_pairs)) \
+  $(call copy-many-files,$(system_property_compatibility_test_res_copy_pairs)) \
+  $(VTS_TESTCASES_OUT)/vts/testcases/vndk/golden/platform_vndk_version.txt \
+
+.PHONY: vts-test-core
+vts-test-core: $(vts_test_core_copy_pairs)
+
+$(compatibility_zip): $(vts_copy_pairs)
 
 -include vendor/google_vts/tools/build/vts_package_vendor.mk
diff --git a/tools/vts-hc/run b/tools/vts-hc/run
deleted file mode 100755
index 121a7b8..0000000
--- a/tools/vts-hc/run
+++ /dev/null
@@ -1,48 +0,0 @@
-#!/bin/bash
-# Copyright (C) 2017 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#       http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-# launcher script for vts-hc (host controller)
-# can be used from an Android build environment, or a standalone vts zip
-
-# 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
-      VTS_ROOT=${ANDROID_HOST_OUT}/vts
-    else
-      VTS_ROOT=${ANDROID_BUILD_TOP}/${OUT_DIR:-out}/host/${OS}/vts
-    fi
-    if [ ! -d ${VTS_ROOT} ]; then
-        echo "Could not find $VTS_ROOT in Android build environment. Try 'make vts'"
-        exit
-    fi;
-fi;
-
-if [ -z ${VTS_ROOT} ]; then
-    # assume we're in an extracted vts install
-    VTS_ROOT="$(dirname $(readlink -e $0))/../.."
-fi;
-
-cd ${VTS_ROOT}/android-vts/testcases/; python -m vts.harnesses.host_controller.main "$@"
diff --git a/tools/vts-tradefed/CtsDynamicConfig.xml b/tools/vts-tradefed/DynamicConfig.xml
similarity index 100%
rename from tools/vts-tradefed/CtsDynamicConfig.xml
rename to tools/vts-tradefed/DynamicConfig.xml
diff --git a/tools/vts-tradefed/etc/Android.mk b/tools/vts-tradefed/etc/Android.mk
index aa3cc16..7eaef2a 100644
--- a/tools/vts-tradefed/etc/Android.mk
+++ b/tools/vts-tradefed/etc/Android.mk
@@ -31,3 +31,5 @@
 LOCAL_MODULE_HOST_OS := windows
 include $(BUILD_PREBUILT)
 
+.PHONY: vts-tradefed-standalone
+vts-tradefed-standalone: vts-tradefed vts-tradefed-tests hosttestlib compatibility-host-util tradefed
diff --git a/tools/vts-tradefed/etc/vts-tradefed b/tools/vts-tradefed/etc/vts-tradefed
index 346f813..abb5a6c 100755
--- a/tools/vts-tradefed/etc/vts-tradefed
+++ b/tools/vts-tradefed/etc/vts-tradefed
@@ -16,6 +16,16 @@
 
 # launcher script for vts-tradefed harness
 # can be used from an Android build environment, or a standalone vts zip
+#
+# Usage:
+#   # to test a device with system.img = v9.0 and vendor.img = v9.0
+#       $ vts-tradefed
+#
+#   # to test a device with system.img = v9.0 and vendor.img = v8.1
+#       $ vts-tradefed -v=8.1
+#
+#   # all other cases are unsupported
+#
 
 checkFile() {
     if [ ! -f "$1" ]; then
@@ -78,16 +88,31 @@
     VTS_ROOT="$(dirname $(readlink -e $0))/../.."
 fi;
 
-JAR_DIR=${VTS_ROOT}/android-vts/tools
+VTS_JAR_DIR=${VTS_ROOT}/android-vts/tools
+STANDALONE_JAR_DIR=${ANDROID_HOST_OUT}/framework
+JAR_DIRS="$VTS_JAR_DIR
+  $STANDALONE_JAR_DIR"
 
+JAR_DIR=""
+# Wherever we find the tradefed jar is where we expect the other jars to be.
 TRADEFED_JAR="tradefed"
-if [ ! -f ${JAR_DIR}/${TRADEFED_JAR}.jar ]; then
+for CHECK_JAR_DIR in $JAR_DIRS; do
+  if [ -f ${CHECK_JAR_DIR}/${TRADEFED_JAR}.jar ]; then
+    JAR_DIR=$CHECK_JAR_DIR
+    break
+  fi;
+done
+
+# If we didn't find the TF jar, resort to tf prebuilt in VTS_JAR_DIR.
+if [ -z $JAR_DIR ]; then
+  JAR_DIR=$VTS_JAR_DIR
   TRADEFED_JAR="tradefed-prebuilt"
-fi;
+fi
 
 JARS="${TRADEFED_JAR}
   hosttestlib
   vts-tradefed
+  vts-tradefed-tests
   compatibility-host-util"
 
 for JAR in $JARS; do
@@ -100,13 +125,17 @@
   android-vts/tools/google-tradefed-vts-prebuilt
   google-tradefed-prebuilt
   google-tradefed-tests
-  google-tf-prod-tests"
+  google-tf-prod-tests
+  google-tradefed"
 
 for JAR in $OPTIONAL_JARS; do
-    if [ -f "${VTS_ROOT}/${JAR}.jar" ]; then
-        echo "Including optional JAR: $VTS_ROOT/$JAR.jar"
-        JAR_PATH=${JAR_PATH}:${VTS_ROOT}/${JAR}.jar
-    fi;
+    for OPT_JAR_DIR in $VTS_ROOT $JAR_DIR; do
+        if [ -f "${OPT_JAR_DIR}/${JAR}.jar" ]; then
+            echo "Including optional JAR: ${OPT_JAR_DIR}/${JAR}.jar"
+            JAR_PATH=${JAR_PATH}:${OPT_JAR_DIR}/${JAR}.jar
+            break
+        fi;
+    done
 done
 
 # load any shared libraries for host-side executables
@@ -124,4 +153,30 @@
     JAR_PATH=${JAR_PATH}:$j
 done
 
-cd ${VTS_ROOT}/android-vts/testcases/; java $RDBG_FLAG -cp ${JAR_PATH} -DVTS_ROOT=${VTS_ROOT} com.android.compatibility.common.tradefed.command.CompatibilityConsole "$@"
+ARGS=()
+for var in "$@"
+do
+case $var in
+    -v=*|--vendor-image=*)
+    VENDOR="${var#*=}"
+    if [ "${VENDOR}" == "9.0" ]; then
+        VENDOR=""
+    elif [ "${VENDOR}" != "8.1" ]; then
+        echo "Supports only --vendor-image=8.1."
+        echo "By default, 9.0 is the supported vendor.img version."
+        exit 1
+    fi
+    ;;
+    *)
+    ARGS+=("$var")
+    ;;
+esac
+done
+
+if [ -z "${VENDOR}" ]; then
+    VTS_TESTCASES=${VTS_ROOT}/android-vts/testcases/
+else
+    VTS_TESTCASES=${VTS_ROOT}/android-vts/${VENDOR}/testcases/
+fi
+
+cd ${VTS_TESTCASES}; VTS_TESTCASES=${VTS_TESTCASES} java $RDBG_FLAG -cp ${JAR_PATH} -DVTS_ROOT=${VTS_ROOT} com.android.compatibility.common.tradefed.command.CompatibilityConsole "${ARGS[@]}"
diff --git a/tools/vts-tradefed/etc/vts-tradefed_win.bat b/tools/vts-tradefed/etc/vts-tradefed_win.bat
index 10dca2e..e9055f4 100644
--- a/tools/vts-tradefed/etc/vts-tradefed_win.bat
+++ b/tools/vts-tradefed/etc/vts-tradefed_win.bat
@@ -70,6 +70,7 @@
 set JARS=^

   hosttestlib^

   vts-tradefed^

+  vts-tradefed-tests^

   compatibility-host-util

 for %%J in (%JARS%) do (

     set JAR=%JAR_DIR%\%%J.jar

diff --git a/tools/vts-tradefed/res/config/common-preparers.xml b/tools/vts-tradefed/res/config/common-preparers.xml
new file mode 100644
index 0000000..8030039
--- /dev/null
+++ b/tools/vts-tradefed/res/config/common-preparers.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="Common preparer for both vts and cts-on-gsi">
+  <multi_target_preparer class="com.android.tradefed.targetprep.VtsPythonVirtualenvPreparer">
+        <option name="dep-module" value="enum" />
+        <option name="dep-module" value="future" />
+        <option name="dep-module" value="futures" />
+        <option name="dep-module" value="google-api-python-client" />
+        <option name="dep-module" value="httplib2" />
+        <option name="dep-module" value="oauth2client" />
+        <option name="dep-module" value="protobuf" />
+        <option name="dep-module" value="requests" />
+  </multi_target_preparer>
+</configuration>
diff --git a/tools/vts-tradefed/res/config/cts-base.xml b/tools/vts-tradefed/res/config/cts-base.xml
new file mode 100644
index 0000000..7ff79c5
--- /dev/null
+++ b/tools/vts-tradefed/res/config/cts-base.xml
@@ -0,0 +1,108 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="Base test plan for running CTS test modules with VTS">
+
+    <option name="dynamic-sharding" value="false" />
+    <device_recovery class="com.android.tradefed.device.WaitDeviceRecovery" />
+    <build_provider class="com.android.compatibility.common.tradefed.build.CompatibilityBuildProvider">
+        <option name="url-suite-name-override" value="CTS" />
+    </build_provider>
+    <test class="com.android.compatibility.common.tradefed.testtype.CompatibilityTestMultiDevice" />
+    <option name="compatibility:test-arg" value="com.android.tradefed.testtype.AndroidJUnitTest:rerun-from-file:true" />
+    <option name="compatibility:test-arg" value="com.android.tradefed.testtype.AndroidJUnitTest:fallback-to-serial-rerun:false" />
+    <option name="compatibility:test-arg" value="com.android.compatibility.testtype.LibcoreTest:rerun-from-file:true" />
+    <option name="compatibility:test-arg" value="com.android.compatibility.testtype.LibcoreTest:fallback-to-serial-rerun:false" />
+
+    <logger class="com.android.tradefed.log.FileLogger">
+        <option name="log-level-display" value="WARN" />
+    </logger>
+    <result_reporter class="com.android.compatibility.common.tradefed.result.ConsoleReporter" />
+    <result_reporter class="com.android.compatibility.common.tradefed.result.VtsResultReporter" />
+
+    <include name="cts-preconditions" />
+    <option name="dynamic-config-pusher:dynamic-resource-name" value="vts-tradefed" />
+    <include name="cts-system-checkers" />
+    <include name="cts-known-failures" />
+
+    <option name="test-tag" value="cts" />
+
+    <option name="enable-root" value="false" />
+    <!-- retain 200MB of host log -->
+    <option name="max-log-size" value="200" />
+    <!--  retain 200MB of logcat -->
+    <option name="max-tmp-logcat-file" value="209715200" />
+
+    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+        <option name="run-command" value="settings put global package_verifier_enable 0" />
+        <option name="teardown-command" value="settings put global package_verifier_enable 1"/>
+    </target_preparer>
+
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.PropertyCheck">
+        <option name="property-name" value="ro.build.type" />
+        <option name="expected-value" value="user"/> <!-- Device should have user build -->
+        <option name="throw-error" value="false"/> <!-- Only print warning if not user build -->
+    </target_preparer>
+
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.PropertyCheck">
+        <option name="property-name" value="ro.product.locale" />
+        <option name="expected-value" value="en-US"/> <!-- Device locale should be US English -->
+        <option name="throw-error" value="false"/> <!-- Only print warning if not en-US -->
+    </target_preparer>
+    <template-include name="reporters" default="basic-reporters" />
+
+    <!-- Include additional test metadata output. -->
+    <template-include name="metadata-reporters" default="empty" />
+    <target_preparer class="com.android.tradefed.targetprep.VtsDeviceInfoCollector">
+         <option name="disable-framework" value="false"/>
+    </target_preparer>
+
+    <option name="compatibility:primary-abi-only" value="true" />
+
+    <!-- Explicitly include CTS components listed in CtsConfigLoadingTest.java -->
+    <option name="compatibility:module-metadata-include-filter" key="component" value="abuse" />
+    <option name="compatibility:module-metadata-include-filter" key="component" value="art" />
+    <option name="compatibility:module-metadata-include-filter" key="component" value="auth" />
+    <option name="compatibility:module-metadata-include-filter" key="component" value="auto" />
+    <option name="compatibility:module-metadata-include-filter" key="component" value="backup" />
+    <option name="compatibility:module-metadata-include-filter" key="component" value="bionic" />
+    <option name="compatibility:module-metadata-include-filter" key="component" value="bluetooth" />
+    <option name="compatibility:module-metadata-include-filter" key="component" value="camera" />
+    <option name="compatibility:module-metadata-include-filter" key="component" value="deqp" />
+    <option name="compatibility:module-metadata-include-filter" key="component" value="devtools" />
+    <option name="compatibility:module-metadata-include-filter" key="component" value="framework" />
+    <option name="compatibility:module-metadata-include-filter" key="component" value="graphics" />
+    <option name="compatibility:module-metadata-include-filter" key="component" value="inputmethod" />
+    <option name="compatibility:module-metadata-include-filter" key="component" value="libcore" />
+    <option name="compatibility:module-metadata-include-filter" key="component" value="location" />
+    <option name="compatibility:module-metadata-include-filter" key="component" value="media" />
+    <option name="compatibility:module-metadata-include-filter" key="component" value="metrics" />
+    <option name="compatibility:module-metadata-include-filter" key="component" value="misc" />
+    <option name="compatibility:module-metadata-include-filter" key="component" value="networking" />
+    <option name="compatibility:module-metadata-include-filter" key="component" value="neuralnetworks" />
+    <option name="compatibility:module-metadata-include-filter" key="component" value="renderscript" />
+    <option name="compatibility:module-metadata-include-filter" key="component" value="security" />
+    <option name="compatibility:module-metadata-include-filter" key="component" value="statsd" />
+    <option name="compatibility:module-metadata-include-filter" key="component" value="systems" />
+    <option name="compatibility:module-metadata-include-filter" key="component" value="sysui" />
+    <option name="compatibility:module-metadata-include-filter" key="component" value="telecom" />
+    <option name="compatibility:module-metadata-include-filter" key="component" value="tv" />
+    <option name="compatibility:module-metadata-include-filter" key="component" value="uitoolkit" />
+    <option name="compatibility:module-metadata-include-filter" key="component" value="vr" />
+    <option name="compatibility:module-metadata-include-filter" key="component" value="webview" />
+
+    <!-- Exclude vts-hal-adapter tests -->
+    <option name="compatibility:module-metadata-exclude-filter" key="plan" value="vts-hal-adapter" />
+</configuration>
diff --git a/tools/vts-tradefed/res/config/cts-on-gsi.xml b/tools/vts-tradefed/res/config/cts-on-gsi.xml
index 30010c2..3c8309a 100644
--- a/tools/vts-tradefed/res/config/cts-on-gsi.xml
+++ b/tools/vts-tradefed/res/config/cts-on-gsi.xml
@@ -14,12 +14,8 @@
      limitations under the License.
 -->
 <configuration description="Runs a subset of CTS tests using a general system image (GSI)">
-
-    <include name="cts-common" />
-
-    <option name="plan" value="cts-on-gsi" />
-
-    <option name="compatibility:primary-abi-only" value="true" />
+    <include name="common-preparers" />
+    <include name="cts-base" />
 
     <!-- Tell all AndroidJUnitTests to exclude certain annotations -->
     <option name="compatibility:test-arg" value="com.android.tradefed.testtype.AndroidJUnitTest:exclude-annotation:android.platform.test.annotations.RestrictedBuildTest" />
@@ -43,9 +39,9 @@
     <option name="compatibility:exclude-filter" value="CtsPermission2TestCases android.permission2.cts.NoReceiveSmsPermissionTest#testAppSpecificSmsToken" />
     <option name="compatibility:exclude-filter" value="CtsPermission2TestCases android.permission2.cts.NoReceiveSmsPermissionTest#testReceiveTextMessage" />
     <option name="compatibility:exclude-filter" value="CtsPermissionTestCases android.permission.cts.NoSystemFunctionPermissionTest#testSendSms" />
-    <option name="compatibility:exclude-filter" value="CtsSecurityHostTest android.security.cts.SELinuxHostTest#testNoExemptionsForBinderInVendorBan" />
-    <option name="compatibility:exclude-filter" value="CtsSecurityHostTest android.security.cts.SELinuxHostTest#testNoExemptionsForSocketsBetweenCoreAndVendorBan" />
-    <option name="compatibility:exclude-filter" value="CtsSecurityHostTest android.security.cts.SELinuxHostTest#testNoExemptionsForVendorExecutingCore" />
+    <option name="compatibility:exclude-filter" value="CtsSecurityHostTestCases android.security.cts.SELinuxHostTest#testNoExemptionsForBinderInVendorBan" />
+    <option name="compatibility:exclude-filter" value="CtsSecurityHostTestCases android.security.cts.SELinuxHostTest#testNoExemptionsForSocketsBetweenCoreAndVendorBan" />
+    <option name="compatibility:exclude-filter" value="CtsSecurityHostTestCases android.security.cts.SELinuxHostTest#testNoExemptionsForVendorExecutingCore" />
     <option name="compatibility:exclude-filter" value="CtsUsageStatsTestCases android.app.usage.cts.NetworkUsageStatsTest#testAppDetails" />
     <option name="compatibility:exclude-filter" value="CtsUsageStatsTestCases android.app.usage.cts.NetworkUsageStatsTest#testAppSummary" />
     <option name="compatibility:exclude-filter" value="CtsUsageStatsTestCases android.app.usage.cts.NetworkUsageStatsTest#testCallback" />
@@ -97,235 +93,6 @@
     <option name="compatibility:exclude-filter" value="CtsKeystoreTestCases android.keystore.cts.KeyAttestationTest#testEcAttestation" />
     <option name="compatibility:exclude-filter" value="CtsKeystoreTestCases android.keystore.cts.KeyAttestationTest#testRsaAttestation" />
 
-    <!-- Explicitly include CTS test modules  -->
-    <option name="compatibility:include-filter" value="CtsAadbHostTestCases" />
-    <option name="compatibility:include-filter" value="CtsAbiOverrideHostTestCases" />
-    <option name="compatibility:include-filter" value="CtsAccelerationTestCases" />
-    <option name="compatibility:include-filter" value="CtsAccessibilityServiceTestCases" />
-    <option name="compatibility:include-filter" value="CtsAccessibilityTestCases" />
-    <option name="compatibility:include-filter" value="CtsAccountManagerTestCases" />
-    <option name="compatibility:include-filter" value="CtsAdminPackageInstallerTestCases" />
-    <option name="compatibility:include-filter" value="CtsAdminTestCases" />
-    <option name="compatibility:include-filter" value="CtsAlarmClockTestCases" />
-    <option name="compatibility:include-filter" value="CtsAndroidAppTestCases" />
-    <option name="compatibility:include-filter" value="CtsAnimationTestCases" />
-    <option name="compatibility:include-filter" value="CtsAppSecurityHostTestCases" />
-    <option name="compatibility:include-filter" value="CtsAppTestCases" />
-    <option name="compatibility:include-filter" value="CtsAppUsageHostTestCases" />
-    <option name="compatibility:include-filter" value="CtsAppWidgetTestCases" />
-    <option name="compatibility:include-filter" value="CtsAslrMallocTestCases" />
-    <option name="compatibility:include-filter" value="CtsAssistTestCases" />
-    <option name="compatibility:include-filter" value="CtsAtraceHostTestCases" />
-    <option name="compatibility:include-filter" value="CtsAutoFillServiceTestCases" />
-    <option name="compatibility:include-filter" value="CtsBackgroundRestrictionsTestCases" />
-    <option name="compatibility:include-filter" value="CtsBackupHostTestCases" />
-    <option name="compatibility:include-filter" value="CtsBackupTestCases" />
-    <option name="compatibility:include-filter" value="CtsBionicTestCases" />
-    <option name="compatibility:include-filter" value="CtsBluetoothTestCases" />
-    <option name="compatibility:include-filter" value="CtsBootStatsTestCases" />
-    <option name="compatibility:include-filter" value="CtsCalendarcommon2TestCases" />
-    <option name="compatibility:include-filter" value="CtsCameraApi25TestCases" />
-    <option name="compatibility:include-filter" value="CtsCameraTestCases" />
-    <option name="compatibility:include-filter" value="CtsCarTestCases" />
-    <option name="compatibility:include-filter" value="CtsCarrierApiTestCases" />
-    <option name="compatibility:include-filter" value="CtsColorModeTestCases" />
-    <option name="compatibility:include-filter" value="CtsCompilationTestCases" />
-    <option name="compatibility:include-filter" value="CtsContactsProviderWipe" />
-    <option name="compatibility:include-filter" value="CtsContentTestCases" />
-    <option name="compatibility:include-filter" value="CtsCppToolsTestCases" />
-    <option name="compatibility:include-filter" value="CtsDatabaseTestCases" />
-    <option name="compatibility:include-filter" value="CtsDebugTestCases" />
-    <option name="compatibility:include-filter" value="CtsDeqpTestCases" />
-    <option name="compatibility:include-filter" value="CtsDevicePolicyManagerTestCases" />
-    <option name="compatibility:include-filter" value="CtsDisplayTestCases" />
-    <option name="compatibility:include-filter" value="CtsDpiTestCases" />
-    <option name="compatibility:include-filter" value="CtsDpiTestCases2" />
-    <option name="compatibility:include-filter" value="CtsDramTestCases" />
-    <option name="compatibility:include-filter" value="CtsDreamsTestCases" />
-    <option name="compatibility:include-filter" value="CtsDrmTestCases" />
-    <option name="compatibility:include-filter" value="CtsDumpsysHostTestCases" />
-    <option name="compatibility:include-filter" value="CtsEdiHostTestCases" />
-    <option name="compatibility:include-filter" value="CtsEffectTestCases" />
-    <option name="compatibility:include-filter" value="CtsExternalServiceTestCases" />
-    <option name="compatibility:include-filter" value="CtsExternalSourcesTestCases" />
-    <option name="compatibility:include-filter" value="CtsFileSystemTestCases" />
-    <option name="compatibility:include-filter" value="CtsFragmentTestCases" />
-    <option name="compatibility:include-filter" value="CtsGestureTestCases" />
-    <option name="compatibility:include-filter" value="CtsGraphicsTestCases" />
-    <option name="compatibility:include-filter" value="CtsHardwareTestCases" />
-    <option name="compatibility:include-filter" value="CtsHostTzDataTests" />
-    <option name="compatibility:include-filter" value="CtsHostsideNetworkTests" />
-    <option name="compatibility:include-filter" value="CtsHostsideNumberBlockingTestCases" />
-    <option name="compatibility:include-filter" value="CtsHostsideTvTests" />
-    <option name="compatibility:include-filter" value="CtsHostsideWebViewTests" />
-    <option name="compatibility:include-filter" value="CtsIcuTestCases" />
-    <option name="compatibility:include-filter" value="CtsIncidentHostTestCases" />
-    <option name="compatibility:include-filter" value="CtsIncidentTestCases" />
-    <option name="compatibility:include-filter" value="CtsInputMethodServiceHostTestCases" />
-    <option name="compatibility:include-filter" value="CtsInputMethodTestCases" />
-    <option name="compatibility:include-filter" value="CtsIntentSignatureTestCases" />
-    <option name="compatibility:include-filter" value="CtsJankDeviceTestCases" />
-    <option name="compatibility:include-filter" value="CtsJdwpSecurityHostTestCases" />
-    <option name="compatibility:include-filter" value="CtsJdwpTestCases" />
-    <option name="compatibility:include-filter" value="CtsJniTestCases" />
-    <option name="compatibility:include-filter" value="CtsJobSchedulerTestCases" />
-    <option name="compatibility:include-filter" value="CtsJvmtiAttachingHostTestCases" />
-    <option name="compatibility:include-filter" value="CtsJvmtiRedefineClassesHostTestCases" />
-    <option name="compatibility:include-filter" value="CtsJvmtiRunTest902HostTestCases" />
-    <option name="compatibility:include-filter" value="CtsJvmtiRunTest903HostTestCases" />
-    <option name="compatibility:include-filter" value="CtsJvmtiRunTest904HostTestCases" />
-    <option name="compatibility:include-filter" value="CtsJvmtiRunTest905HostTestCases" />
-    <option name="compatibility:include-filter" value="CtsJvmtiRunTest906HostTestCases" />
-    <option name="compatibility:include-filter" value="CtsJvmtiRunTest907HostTestCases" />
-    <option name="compatibility:include-filter" value="CtsJvmtiRunTest908HostTestCases" />
-    <option name="compatibility:include-filter" value="CtsJvmtiRunTest910HostTestCases" />
-    <option name="compatibility:include-filter" value="CtsJvmtiRunTest911HostTestCases" />
-    <option name="compatibility:include-filter" value="CtsJvmtiRunTest912HostTestCases" />
-    <option name="compatibility:include-filter" value="CtsJvmtiRunTest913HostTestCases" />
-    <option name="compatibility:include-filter" value="CtsJvmtiRunTest914HostTestCases" />
-    <option name="compatibility:include-filter" value="CtsJvmtiRunTest915HostTestCases" />
-    <option name="compatibility:include-filter" value="CtsJvmtiRunTest917HostTestCases" />
-    <option name="compatibility:include-filter" value="CtsJvmtiRunTest918HostTestCases" />
-    <option name="compatibility:include-filter" value="CtsJvmtiRunTest919HostTestCases" />
-    <option name="compatibility:include-filter" value="CtsJvmtiRunTest920HostTestCases" />
-    <option name="compatibility:include-filter" value="CtsJvmtiRunTest922HostTestCases" />
-    <option name="compatibility:include-filter" value="CtsJvmtiRunTest923HostTestCases" />
-    <option name="compatibility:include-filter" value="CtsJvmtiRunTest924HostTestCases" />
-    <option name="compatibility:include-filter" value="CtsJvmtiRunTest926HostTestCases" />
-    <option name="compatibility:include-filter" value="CtsJvmtiRunTest927HostTestCases" />
-    <option name="compatibility:include-filter" value="CtsJvmtiRunTest928HostTestCases" />
-    <option name="compatibility:include-filter" value="CtsJvmtiRunTest930HostTestCases" />
-    <option name="compatibility:include-filter" value="CtsJvmtiRunTest931HostTestCases" />
-    <option name="compatibility:include-filter" value="CtsJvmtiRunTest932HostTestCases" />
-    <option name="compatibility:include-filter" value="CtsJvmtiRunTest940HostTestCases" />
-    <option name="compatibility:include-filter" value="CtsJvmtiRunTest942HostTestCases" />
-    <option name="compatibility:include-filter" value="CtsJvmtiRunTest944HostTestCases" />
-    <option name="compatibility:include-filter" value="CtsJvmtiRunTest945HostTestCases" />
-    <option name="compatibility:include-filter" value="CtsJvmtiRunTest947HostTestCases" />
-    <option name="compatibility:include-filter" value="CtsJvmtiRunTest951HostTestCases" />
-    <option name="compatibility:include-filter" value="CtsJvmtiRunTest982HostTestCases" />
-    <option name="compatibility:include-filter" value="CtsJvmtiRunTest984HostTestCases" />
-    <option name="compatibility:include-filter" value="CtsJvmtiRunTest985HostTestCases" />
-    <option name="compatibility:include-filter" value="CtsJvmtiRunTest986HostTestCases" />
-    <option name="compatibility:include-filter" value="CtsJvmtiTaggingHostTestCases" />
-    <option name="compatibility:include-filter" value="CtsJvmtiTrackingHostTestCases" />
-    <option name="compatibility:include-filter" value="CtsKernelConfigTestCases" />
-    <option name="compatibility:include-filter" value="CtsKeystoreTestCases" />
-    <option name="compatibility:include-filter" value="CtsLeanbackJankTestCases" />
-    <option name="compatibility:include-filter" value="CtsLibcoreFileIOTestCases" />
-    <option name="compatibility:include-filter" value="CtsLibcoreJavaUtilCollectionsTestCases" />
-    <option name="compatibility:include-filter" value="CtsLibcoreJsr166TestCases" />
-    <option name="compatibility:include-filter" value="CtsLibcoreLegacy22TestCases" />
-    <option name="compatibility:include-filter" value="CtsLibcoreOjTestCases" />
-    <option name="compatibility:include-filter" value="CtsLibcoreOkHttpTestCases" />
-    <option name="compatibility:include-filter" value="CtsLibcoreTestCases" />
-    <option name="compatibility:include-filter" value="CtsLibcoreWycheproofBCTestCases" />
-    <option name="compatibility:include-filter" value="CtsLibcoreWycheproofConscryptTestCases" />
-    <option name="compatibility:include-filter" value="CtsLiblogTestCases" />
-    <option name="compatibility:include-filter" value="CtsLocation2TestCases" />
-    <option name="compatibility:include-filter" value="CtsLocationTestCases" />
-    <option name="compatibility:include-filter" value="CtsLogdTestCases" />
-    <option name="compatibility:include-filter" value="CtsMediaBitstreamsTestCases" />
-    <option name="compatibility:include-filter" value="CtsMediaHostTestCases" />
-    <option name="compatibility:include-filter" value="CtsMediaStressTestCases" />
-    <option name="compatibility:include-filter" value="CtsMediaTestCases" />
-    <option name="compatibility:include-filter" value="CtsMidiTestCases" />
-    <option name="compatibility:include-filter" value="CtsMonkeyTestCases" />
-    <option name="compatibility:include-filter" value="CtsMultiUserHostTestCases" />
-    <option name="compatibility:include-filter" value="CtsMultiUserTestCases" />
-    <option name="compatibility:include-filter" value="CtsNativeHardwareTestCases" />
-    <option name="compatibility:include-filter" value="CtsNativeMediaAAudioTestCases" />
-    <option name="compatibility:include-filter" value="CtsNativeMediaSlTestCases" />
-    <option name="compatibility:include-filter" value="CtsNativeMediaXaTestCases" />
-    <option name="compatibility:include-filter" value="CtsNativeNetTestCases" />
-    <option name="compatibility:include-filter" value="CtsNdefTestCases" />
-    <option name="compatibility:include-filter" value="CtsNetSecConfigAttributeTestCases" />
-    <option name="compatibility:include-filter" value="CtsNetSecConfigBasicDebugDisabledTestCases" />
-    <option name="compatibility:include-filter" value="CtsNetSecConfigBasicDebugEnabledTestCases" />
-    <option name="compatibility:include-filter" value="CtsNetSecConfigBasicDomainConfigTestCases" />
-    <option name="compatibility:include-filter" value="CtsNetSecConfigCleartextTrafficTestCases" />
-    <option name="compatibility:include-filter" value="CtsNetSecConfigDownloadManagerTestCases" />
-    <option name="compatibility:include-filter" value="CtsNetSecConfigInvalidPinTestCases" />
-    <option name="compatibility:include-filter" value="CtsNetSecConfigNestedDomainConfigTestCases" />
-    <option name="compatibility:include-filter" value="CtsNetSecConfigResourcesSrcTestCases" />
-    <option name="compatibility:include-filter" value="CtsNetSecPolicyUsesCleartextTrafficFalseTestCases" />
-    <option name="compatibility:include-filter" value="CtsNetSecPolicyUsesCleartextTrafficTrueTestCases" />
-    <option name="compatibility:include-filter" value="CtsNetSecPolicyUsesCleartextTrafficUnspecifiedTestCases" />
-    <option name="compatibility:include-filter" value="CtsNetTestCases" />
-    <option name="compatibility:include-filter" value="CtsNetTestCasesLegacyApi22" />
-    <option name="compatibility:include-filter" value="CtsNetTestCasesLegacyPermission22" />
-    <option name="compatibility:include-filter" value="CtsOpenGLTestCases" />
-    <option name="compatibility:include-filter" value="CtsOpenGlPerf2TestCases" />
-    <option name="compatibility:include-filter" value="CtsOpenGlPerfTestCases" />
-    <option name="compatibility:include-filter" value="CtsOsHostTestCases" />
-    <option name="compatibility:include-filter" value="CtsOsTestCases" />
-    <option name="compatibility:include-filter" value="CtsPdfTestCases" />
-    <option name="compatibility:include-filter" value="CtsPermission2TestCases" />
-    <option name="compatibility:include-filter" value="CtsPermissionTestCases" />
-    <option name="compatibility:include-filter" value="CtsPreference2TestCases" />
-    <option name="compatibility:include-filter" value="CtsPreferenceTestCases" />
-    <option name="compatibility:include-filter" value="CtsPrintTestCases" />
-    <option name="compatibility:include-filter" value="CtsProtoTestCases" />
-    <option name="compatibility:include-filter" value="CtsProviderTestCases" />
-    <option name="compatibility:include-filter" value="CtsRenderscriptLegacyTestCases" />
-    <option name="compatibility:include-filter" value="CtsRenderscriptTestCases" />
-    <option name="compatibility:include-filter" value="CtsRsBlasTestCases" />
-    <option name="compatibility:include-filter" value="CtsRsCppTestCases" />
-    <option name="compatibility:include-filter" value="CtsSampleDeviceTestCases" />
-    <option name="compatibility:include-filter" value="CtsSampleHostTestCases" />
-    <option name="compatibility:include-filter" value="CtsSaxTestCases" />
-    <option name="compatibility:include-filter" value="CtsSecurityHostTestCases" />
-    <option name="compatibility:include-filter" value="CtsSecurityTestCases" />
-    <option name="compatibility:include-filter" value="CtsSelinuxTargetSdk2TestCases" />
-    <option name="compatibility:include-filter" value="CtsSelinuxTargetSdkTestCases" />
-    <option name="compatibility:include-filter" value="CtsSensorTestCases" />
-    <option name="compatibility:include-filter" value="CtsServicesHostTestCases" />
-    <option name="compatibility:include-filter" value="CtsShortcutHostTestCases" />
-    <option name="compatibility:include-filter" value="CtsShortcutManagerTestCases" />
-    <option name="compatibility:include-filter" value="CtsSimpleCpuTestCases" />
-    <option name="compatibility:include-filter" value="CtsSimpleperfTestCases" />
-    <option name="compatibility:include-filter" value="CtsSpeechTestCases" />
-    <option name="compatibility:include-filter" value="CtsSustainedPerformanceHostTestCases" />
-    <option name="compatibility:include-filter" value="CtsSyncContentHostTestCases" />
-    <option name="compatibility:include-filter" value="CtsSystemIntentTestCases" />
-    <option name="compatibility:include-filter" value="CtsSystemUiHostTestCases" />
-    <option name="compatibility:include-filter" value="CtsSystemUiTestCases" />
-    <option name="compatibility:include-filter" value="CtsTelecomTestCases" />
-    <option name="compatibility:include-filter" value="CtsTelecomTestCases2" />
-    <option name="compatibility:include-filter" value="CtsTelecomTestCases3" />
-    <option name="compatibility:include-filter" value="CtsTelephony2TestCases" />
-    <option name="compatibility:include-filter" value="CtsTelephonyTestCases" />
-    <option name="compatibility:include-filter" value="CtsTextTestCases" />
-    <option name="compatibility:include-filter" value="CtsThemeDeviceTestCases" />
-    <option name="compatibility:include-filter" value="CtsThemeHostTestCases" />
-    <option name="compatibility:include-filter" value="CtsToastLegacyTestCases" />
-    <option name="compatibility:include-filter" value="CtsToastTestCases" />
-    <option name="compatibility:include-filter" value="CtsTransitionTestCases" />
-    <option name="compatibility:include-filter" value="CtsTrustedVoiceHostTestCases" />
-    <option name="compatibility:include-filter" value="CtsTvProviderTestCases" />
-    <option name="compatibility:include-filter" value="CtsTvTestCases" />
-    <option name="compatibility:include-filter" value="CtsUiAutomationTestCases" />
-    <option name="compatibility:include-filter" value="CtsUiDeviceTestCases" />
-    <option name="compatibility:include-filter" value="CtsUiHostTestCases" />
-    <option name="compatibility:include-filter" value="CtsUiRenderingTestCases" />
-    <option name="compatibility:include-filter" value="CtsUidIsolationTestCases" />
-    <option name="compatibility:include-filter" value="CtsUsageStatsTestCases" />
-    <option name="compatibility:include-filter" value="CtsUsbTests" />
-    <option name="compatibility:include-filter" value="CtsUtilTestCases" />
-    <option name="compatibility:include-filter" value="CtsVideoTestCases" />
-    <option name="compatibility:include-filter" value="CtsViewTestCases" />
-    <option name="compatibility:include-filter" value="CtsVmTestCases" />
-    <option name="compatibility:include-filter" value="CtsVoiceInteractionTestCases" />
-    <option name="compatibility:include-filter" value="CtsVoiceSettingsTestCases" />
-    <option name="compatibility:include-filter" value="CtsVrTestCases" />
-    <option name="compatibility:include-filter" value="CtsWebkitTestCases" />
-    <option name="compatibility:include-filter" value="CtsWidgetTestCases" />
-    <option name="compatibility:include-filter" value="CtsWindowManagerHostTestCases" />
-    <option name="compatibility:include-filter" value="CtsWrapNoWrapTestCases" />
-    <option name="compatibility:include-filter" value="CtsWrapWrapDebugMallocDebugTestCases" />
-    <option name="compatibility:include-filter" value="CtsWrapWrapDebugTestCases" />
-    <option name="compatibility:include-filter" value="CtsWrapWrapNoDebugTestCases" />
-
     <!-- Exclude test cases for b/64488375
     -->
 
@@ -359,4 +126,7 @@
     <option name="compatibility:exclude-filter" value="CtsWebkitTestCases android.webkit.cts.WebViewTest#testRequestFocusNodeHref" />
     <option name="compatibility:exclude-filter" value="CtsWebkitTestCases android.webkit.cts.WebViewTest#testRequestImageRef" />
 
+    <!-- b/65561379: Exclude android.media.cts.MediaPlayerFlakyNetworkTest -->
+    <option name="compatibility:exclude-filter" value="CtsMediaTestCases android.media.cts.MediaPlayerFlakyNetworkTest" />
+
 </configuration>
diff --git a/tools/vts-tradefed/res/config/cts-vendor-interface.xml b/tools/vts-tradefed/res/config/cts-vendor-interface.xml
index 73ec49b..049d6a1 100644
--- a/tools/vts-tradefed/res/config/cts-vendor-interface.xml
+++ b/tools/vts-tradefed/res/config/cts-vendor-interface.xml
@@ -16,6 +16,8 @@
 <configuration description="Runs a subset of CTS tests that can heavily exercise HALs">
 
     <include name="cts" />
+    <option name="compatibility-build-provider:url-suite-name-override" value="CTS" />
+    <option name="dynamic-config-pusher:dynamic-resource-name" value="vts-tradefed" />
 
     <option name="plan" value="cts-vendor-interface" />
 
@@ -35,7 +37,6 @@
     <option name="compatibility:include-filter" value="CtsHostsideNetworkTests" />
     <option name="compatibility:include-filter" value="CtsIcuTestCases" />
     <option name="compatibility:include-filter" value="CtsKeystoreTestCases" />
-    <option name="compatibility:include-filter" value="CtsLibcoreJavaUtilCollectionsTestCases" />
     <option name="compatibility:include-filter" value="CtsLibcoreOjTestCases" />
     <option name="compatibility:include-filter" value="CtsLibcoreTestCases" />
     <option name="compatibility:include-filter" value="CtsLocationTestCases" />
diff --git a/tools/vts-tradefed/res/config/plans.md b/tools/vts-tradefed/res/config/plans.md
index 85e16d2..197310e 100644
--- a/tools/vts-tradefed/res/config/plans.md
+++ b/tools/vts-tradefed/res/config/plans.md
@@ -11,6 +11,7 @@
 and partners.
 
  * __vts__: For all default VTS tests.
+ * __vts-firmware__: For all default VTS System Firmware tests.
  * __vts-fuzz__: For all default VTS fuzz tests.
  * __vts-hal__: For all default VTS HAL (hardware abstraction layer) module tests.
  * __vts-hal-profiling__: For all default VTS HAL performance profiling tests.
diff --git a/tools/vts-tradefed/res/config/vts-automated.xml b/tools/vts-tradefed/res/config/vts-automated.xml
index bc62afd..6018959 100644
--- a/tools/vts-tradefed/res/config/vts-automated.xml
+++ b/tools/vts-tradefed/res/config/vts-automated.xml
@@ -43,6 +43,7 @@
   <option name="compatibility:include-filter" value="VtsKernelNetdTest" />
   <option name="compatibility:include-filter" value="VtsKernelSelinuxFileApi" />
   <option name="compatibility:include-filter" value="VtsKernelTunTest" />
+  <option name="compatibility:include-filter" value="VtsKernelVersion" />
   <option name="compatibility:include-filter" value="VtsQtaguidTest" />
 
   <!--  From vts-vndk.xml -->
diff --git a/tools/vts-tradefed/res/config/vts-base-common.xml b/tools/vts-tradefed/res/config/vts-base-common.xml
new file mode 100644
index 0000000..5a9e22d
--- /dev/null
+++ b/tools/vts-tradefed/res/config/vts-base-common.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="VTS Main Test Plan">
+  <include name="common-preparers" />
+  <device_recovery class="com.android.tradefed.device.WaitDeviceRecovery" />
+
+  <option name="compatibility:test-arg" value="com.android.tradefed.testtype.AndroidJUnitTest:rerun-from-file:true" />
+  <option name="compatibility:test-arg" value="com.android.tradefed.testtype.AndroidJUnitTest:fallback-to-serial-rerun:false" />
+  <logger class="com.android.tradefed.log.FileLogger">
+    <option name="log-level-display" value="INFO" />
+  </logger>
+  <option name="compatibility:skip-all-system-status-check" value="true" />
+  <option name="max-log-size" value="200" />
+  <object type="vts-vendor-config" class="com.android.tradefed.util.VtsVendorConfigFileUtil" />
+  <result_reporter class="com.android.compatibility.common.tradefed.result.ConsoleReporter" />
+  <result_reporter class="com.android.compatibility.common.tradefed.result.ResultReporter" />
+  <template-include name="reporters" default="basic-reporters" />
+  <multi_target_preparer class="com.android.tradefed.targetprep.VtsTestPlanResultReporter" />
+  <test class="com.android.compatibility.common.tradefed.testtype.CompatibilityTestMultiDevice" />
+</configuration>
diff --git a/tools/vts-tradefed/res/config/vts-base.xml b/tools/vts-tradefed/res/config/vts-base.xml
index 506062f..27d66b0 100644
--- a/tools/vts-tradefed/res/config/vts-base.xml
+++ b/tools/vts-tradefed/res/config/vts-base.xml
@@ -14,22 +14,7 @@
      limitations under the License.
 -->
 <configuration description="VTS Main Test Plan">
-  <device_recovery class="com.android.tradefed.device.WaitDeviceRecovery" />
-
-  <option name="compatibility:test-arg" value="com.android.tradefed.testtype.AndroidJUnitTest:rerun-from-file:true" />
-  <option name="compatibility:test-arg" value="com.android.tradefed.testtype.AndroidJUnitTest:fallback-to-serial-rerun:false" />
-  <logger class="com.android.tradefed.log.FileLogger">
-    <option name="log-level-display" value="WARN" />
-  </logger>
-  <option name="compatibility:skip-all-system-status-check" value="true" />
-  <option name="max-log-size" value="200" />
-  <object type="vts-vendor-config" class="com.android.tradefed.util.VtsVendorConfigFileUtil" />
-  <result_reporter class="com.android.compatibility.common.tradefed.result.ConsoleReporter" />
-  <result_reporter class="com.android.compatibility.common.tradefed.result.VtsResultReporter" />
-
-  <template-include name="reporters" default="basic-reporters" />
-  <target_preparer class="com.android.tradefed.targetprep.VtsTestPlanResultReporter" />
-  <test class="com.android.compatibility.common.tradefed.testtype.CompatibilityTest" />
+  <include name="vts-base-common" />
 
   <build_provider class="com.android.compatibility.common.tradefed.build.CompatibilityBuildProvider" />
   <target_preparer class="com.android.tradefed.targetprep.VtsDeviceInfoCollector" />
diff --git a/tools/vts-tradefed/res/config/vts-codelab-multi-device.xml b/tools/vts-tradefed/res/config/vts-codelab-multi-device.xml
new file mode 100644
index 0000000..c43c939
--- /dev/null
+++ b/tools/vts-tradefed/res/config/vts-codelab-multi-device.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="VTS Codelab Plan">
+  <include name="vts-base-common" />
+  <option name="plan" value="vts" />
+  <option name="test-tag" value="vts" />
+  <option name="vts-plan-result:plan-name" value="vts" />
+
+  <device name="device1">
+    <build_provider class="com.android.compatibility.common.tradefed.build.CompatibilityBuildProvider" />
+    <target_preparer class="com.android.tradefed.targetprep.VtsDeviceInfoCollector" />
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.VtsFilePusher">
+        <option name="push-group" value="HostDrivenTest.push" />
+    </target_preparer>
+  </device>
+
+  <device name="device2" >
+    <build_provider class="com.android.compatibility.common.tradefed.build.CompatibilityBuildProvider" />
+    <target_preparer class="com.android.tradefed.targetprep.VtsDeviceInfoCollector" />
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.VtsFilePusher">
+        <option name="push-group" value="HostDrivenTest.push" />
+    </target_preparer>
+  </device>
+
+  <option name="compatibility:include-filter" value="VtsCodelabHelloWorldMultiDeviceTest" />
+
+</configuration>
diff --git a/tools/vts-tradefed/res/config/vts-codelab.xml b/tools/vts-tradefed/res/config/vts-codelab.xml
index a838a98..c683c5e 100644
--- a/tools/vts-tradefed/res/config/vts-codelab.xml
+++ b/tools/vts-tradefed/res/config/vts-codelab.xml
@@ -19,8 +19,6 @@
   <option name="test-tag" value="vts-star" />
   <option name="vts-plan-result:plan-name" value="vts-codelab" />
 
-  <option name="compatibility:include-filter" value="VtsCodelabHelloWorldTest" />
-  <option name="compatibility:include-filter" value="VtsCodelabHelloWorldStagingTest" />
-  <option name="compatibility:include-filter" value="VtsCodelabTargetBinary" />
+  <option name="compatibility:module-metadata-include-filter" key="plan" value="vts-codelab" />
 
 </configuration>
diff --git a/tools/vts-tradefed/res/config/vts-device-health.xml b/tools/vts-tradefed/res/config/vts-device-health.xml
new file mode 100644
index 0000000..cdabad9
--- /dev/null
+++ b/tools/vts-tradefed/res/config/vts-device-health.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="VTS Device Health Test Plan">
+  <include name="vts-base" />
+  <option name="plan" value="vts" />
+  <option name="test-tag" value="vts-star" />
+  <option name="vts-plan-result:plan-name" value="vts-device-health" />
+  <option name="compatibility:primary-abi-only" value="true" />
+
+  <option name="compatibility:include-filter" value="VtsDeviceHealth" />
+
+</configuration>
diff --git a/tools/vts-tradefed/res/config/vts-firmware.xml b/tools/vts-tradefed/res/config/vts-firmware.xml
new file mode 100644
index 0000000..5afb79d
--- /dev/null
+++ b/tools/vts-tradefed/res/config/vts-firmware.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="VTS System Firmware Test Plan">
+  <include name="vts-base" />
+  <option name="plan" value="vts" />
+  <option name="test-tag" value="vts-star" />
+  <option name="vts-plan-result:plan-name" value="vts-firmware" />
+
+  <option name="compatibility:module-metadata-include-filter" key="plan" value="vts-firmware" />
+
+</configuration>
diff --git a/tools/vts-tradefed/res/config/vts-fuzz.xml b/tools/vts-tradefed/res/config/vts-fuzz.xml
index ed62b85..4e3cfdf 100644
--- a/tools/vts-tradefed/res/config/vts-fuzz.xml
+++ b/tools/vts-tradefed/res/config/vts-fuzz.xml
@@ -20,22 +20,7 @@
   <option name="vts-plan-result:plan-name" value="vts-fuzz" />
 
   <option name="compatibility:primary-abi-only" value="true" />
-  <option name="compatibility:include-filter" value="VtsHalAudioV2_0IfaceFuzzer" />
-  <option name="compatibility:include-filter" value="VtsHalAudioEffectV2_0IfaceFuzzer" />
-  <option name="compatibility:include-filter" value="VtsHalConfigstoreV1_0IfaceFuzzer" />
-  <option name="compatibility:include-filter" value="VtsHalDumpstateV1_0IfaceFuzzer" />
-  <option name="compatibility:include-filter" value="VtsHalGatekeeperV1_0IfaceFuzzer" />
-  <option name="compatibility:include-filter" value="VtsHalGraphicsAllocatorV2_0IfaceFuzzer" />
-  <option name="compatibility:include-filter" value="VtsHalLightV2_0IfaceFuzzer" />
-  <option name="compatibility:include-filter" value="VtsHalMemtrackV1_0IfaceFuzzer" />
-  <option name="compatibility:include-filter" value="VtsHalPowerV1_0IfaceFuzzer" />
-  <option name="compatibility:include-filter" value="VtsHalPowerV1_1IfaceFuzzer" />
-  <option name="compatibility:include-filter" value="VtsHalRadioDeprecatedV1_0IfaceFuzzer" />
-  <option name="compatibility:include-filter" value="VtsHalThermalV1_0IfaceFuzzer" />
-  <option name="compatibility:include-filter" value="VtsHalThermalV1_1IfaceFuzzer" />
-  <option name="compatibility:include-filter" value="VtsHalVibratorV1_0IfaceFuzzer" />
-  <option name="compatibility:include-filter" value="VtsHalVibratorV1_1IfaceFuzzer" />
-  <option name="compatibility:include-filter" value="VtsHalVrV1_0IfaceFuzzer" />
-  <option name="compatibility:include-filter" value="VtsHalWifiV1_0IfaceFuzzer" />
-  <option name="compatibility:include-filter" value="VtsHalWifiV1_1IfaceFuzzer" />
+
+  <option name="compatibility:module-metadata-include-filter" key="plan" value="vts-fuzz" />
+
 </configuration>
diff --git a/tools/vts-tradefed/res/config/vts-hal-auto.xml b/tools/vts-tradefed/res/config/vts-hal-auto.xml
index fb45101..7bc6dcc 100644
--- a/tools/vts-tradefed/res/config/vts-hal-auto.xml
+++ b/tools/vts-tradefed/res/config/vts-hal-auto.xml
@@ -14,14 +14,11 @@
      limitations under the License.
 -->
 <configuration description="VTS Serving Plan for HALs - Auto">
-    <include name="vts-base" />
-    <option name="plan" value="vts" />
-    <option name="test-tag" value="vts" />
-    <option name="vts-plan-result:plan-name" value="vts-hal-auto" />
+  <include name="vts-base" />
+  <option name="plan" value="vts" />
+  <option name="test-tag" value="vts-star" />
+  <option name="vts-plan-result:plan-name" value="vts-hal-auto" />
 
-    <option name="compatibility:include-filter" value="VtsHalBroadcastradioV1_1TargetTest" />
-    <option name="compatibility:include-filter" value="VtsHalEvsV1_0Target" />
-    <option name="compatibility:include-filter" value="VtsHalAutomotiveVehicleV2_0Host" />
-    <option name="compatibility:include-filter" value="VtsHalAutomotiveVehicleV2_1Host" />
+  <option name="compatibility:module-metadata-include-filter" key="plan" value="vts-hal-auto" />
 
 </configuration>
diff --git a/tools/vts-tradefed/res/config/vts-hal-profiling.xml b/tools/vts-tradefed/res/config/vts-hal-profiling.xml
index 3fdd261..6eed201 100644
--- a/tools/vts-tradefed/res/config/vts-hal-profiling.xml
+++ b/tools/vts-tradefed/res/config/vts-hal-profiling.xml
@@ -14,50 +14,10 @@
      limitations under the License.
 -->
 <configuration description="VTS HAL Profiling Plan">
-  <include name="vts-base" />
+  <include name="vts-hal" />
   <option name="plan" value="vts" />
   <option name="test-tag" value="vts-star" />
   <option name="vts-plan-result:plan-name" value="vts-hal-profiling" />
-
-  <option name="compatibility:include-filter" value="VtsHalAudioV2_0TargetProfiling" />
-  <option name="compatibility:include-filter" value="VtsHalAudioEffectV2_0TargetProfiling" />
-  <option name="compatibility:include-filter" value="VtsHalBiometricsFingerprintV2_1TargetProfiling" />
-  <option name="compatibility:include-filter" value="VtsHalBootV1_0TargetProfiling" />
-  <option name="compatibility:include-filter" value="VtsHalBroadcastradioV1_0TargetProfiling" />
-  <option name="compatibility:include-filter" value="VtsHalBluetoothV1_0TargetProfiling" />
-  <option name="compatibility:include-filter" value="VtsHalCameraProviderV2_4TargetProfiling" />
-  <option name="compatibility:include-filter" value="VtsHalConfigstoreV1_0TargetProfiling" />
-  <option name="compatibility:include-filter" value="VtsHalConfigstoreV1_1TargetProfiling" />
-  <option name="compatibility:include-filter" value="VtsHalContexthubV1_0TargetProfiling" />
-  <option name="compatibility:include-filter" value="VtsHalDrmV1_0TargetProfiling" />
-  <option name="compatibility:include-filter" value="VtsHalGatekeeperV1_0TargetProfiling" />
-  <option name="compatibility:include-filter" value="VtsHalGnssV1_0TargetProfiling" />
-  <option name="compatibility:include-filter" value="VtsHalGraphicsComposerV2_1TargetProfiling" />
-  <option name="compatibility:include-filter" value="VtsHalGraphicsMapperV2_0TargetProfiling" />
-  <option name="compatibility:include-filter" value="VtsHalHealthV1_0TargetProfiling" />
-  <option name="compatibility:include-filter" value="VtsHalIrV1_0TargetProfiling" />
-  <option name="compatibility:include-filter" value="VtsHalKeymasterV3_0TargetProfiling" />
-  <option name="compatibility:include-filter" value="VtsHalLightV2_0TargetProfiling" />
-  <option name="compatibility:include-filter" value="VtsHalMemtrackV1_0TargetProfiling" />
-  <option name="compatibility:include-filter" value="VtsHalNfcV1_0HostProfiling" />
-  <option name="compatibility:include-filter" value="VtsHalNfcV1_0TargetProfiling" />
-  <option name="compatibility:include-filter" value="VtsHalOemLockV1_0TargetProfiling" />
-  <option name="compatibility:include-filter" value="VtsHalPowerV1_0TargetProfiling" />
-  <option name="compatibility:include-filter" value="VtsHalPowerV1_1TargetProfiling" />
-  <option name="compatibility:include-filter" value="VtsHalRadioV1_0TargetProfiling" />
-  <option name="compatibility:include-filter" value="VtsHalRenderscriptV1_0TargetProfiling" />
-  <option name="compatibility:include-filter" value="VtsHalSensorsV1_0HostProfiling" />
-  <option name="compatibility:include-filter" value="VtsHalSensorsV1_0TargetProfiling" />
-  <option name="compatibility:include-filter" value="VtsHalSoundtriggerV2_0TargetProfiling" />
-  <option name="compatibility:include-filter" value="VtsHalThermalV1_0TargetProfiling" />
-  <option name="compatibility:include-filter" value="VtsHalUsbV1_0TargetProfiling" />
-  <option name="compatibility:include-filter" value="VtsHalVibratorV1_0HostProfiling" />
-  <option name="compatibility:include-filter" value="VtsHalVibratorV1_0TargetProfiling" />
-  <option name="compatibility:include-filter" value="VtsHalVrV1_0TargetProfiling" />
-  <option name="compatibility:include-filter" value="VtsHalWeaverV1_0TargetProfiling" />
-  <option name="compatibility:include-filter" value="VtsHalWifiV1_0TargetProfiling" />
-  <option name="compatibility:include-filter" value="VtsHalWifiV1_1TargetProfiling" />
-  <option name="compatibility:include-filter" value="VtsHalWifiNanV1_0TargetProfiling" />
-  <option name="compatibility:include-filter" value="VtsHalWifiSupplicantV1_0TargetProfiling" />
-
+  <target_preparer class="com.android.tradefed.targetprep.VtsTraceCollectPreparer" />
+  <option name="compatibility:test-arg" value="com.android.tradefed.testtype.VtsMultiDeviceTest:enable-profiling:true" />
 </configuration>
diff --git a/tools/vts-tradefed/res/config/vts-hal-replay.xml b/tools/vts-tradefed/res/config/vts-hal-replay.xml
index 8ebc252..d7045f6 100644
--- a/tools/vts-tradefed/res/config/vts-hal-replay.xml
+++ b/tools/vts-tradefed/res/config/vts-hal-replay.xml
@@ -17,7 +17,7 @@
   <include name="vts-base" />
   <target_preparer class="com.android.tradefed.targetprep.VtsSancovPreparer" />
   <option name="plan" value="vts" />
-  <option name="test-tag" value="vts" />
+  <option name="test-tag" value="vts-star" />
   <option name="vts-plan-result:plan-name" value="vts-hal-replay" />
 
   <!-- For Hidl Hal replay tests -->
diff --git a/tools/vts-tradefed/res/config/vts-hal-tv.xml b/tools/vts-tradefed/res/config/vts-hal-tv.xml
index 88c0e25..6ebd9d5 100644
--- a/tools/vts-tradefed/res/config/vts-hal-tv.xml
+++ b/tools/vts-tradefed/res/config/vts-hal-tv.xml
@@ -16,12 +16,11 @@
 <configuration description="VTS Serving Plan for HALs - TV/x86">
   <include name="vts-base" />
   <option name="plan" value="vts" />
-  <option name="test-tag" value="vts" />
+  <option name="test-tag" value="vts-star" />
   <option name="vts-plan-result:plan-name" value="vts-hal-tv" />
 
+  <option name="compatibility:module-metadata-include-filter" key="plan" value="vts-hal-tv" />
+
   <option name="compatibility:test-arg" value="com.android.tradefed.testtype.VtsMultiDeviceTest:gtest-batch-mode:true" />
-  <option name="compatibility:include-filter" value="VtsHalTvCecV1_0Host" />
-  <option name="compatibility:include-filter" value="VtsHalTvInputV1_0Host" />
-  <option name="compatibility:include-filter" value="VtsHalTvInputV1_0Target" />
 
 </configuration>
diff --git a/tools/vts-tradefed/res/config/vts-hal.xml b/tools/vts-tradefed/res/config/vts-hal.xml
index ddc0baf..5ee4d3b 100644
--- a/tools/vts-tradefed/res/config/vts-hal.xml
+++ b/tools/vts-tradefed/res/config/vts-hal.xml
@@ -17,64 +17,9 @@
   <include name="vts-base" />
   <target_preparer class="com.android.tradefed.targetprep.VtsCoveragePreparer" />
   <option name="plan" value="vts" />
-  <option name="test-tag" value="vts" />
+  <option name="test-tag" value="vts-star" />
   <option name="vts-plan-result:plan-name" value="vts-hal" />
 
-  <option name="compatibility:include-filter" value="VtsHalBluetoothV1_0Target" />
-  <option name="compatibility:include-filter" value="VtsHalBootV1_0Target" />
-  <option name="compatibility:include-filter" value="VtsHalDumpstateV1_0Target" />
-  <option name="compatibility:include-filter" value="VtsHalIrV1_0Target" />
-  <option name="compatibility:include-filter" value="VtsHalNfcV1_0HostBinderize" />
-  <option name="compatibility:include-filter" value="VtsHalNfcV1_0HostPassthrough" />
-  <option name="compatibility:include-filter" value="VtsHalNfcV1_0Target" />
-  <option name="compatibility:include-filter" value="VtsHalRadioV1_0Host" />
-  <option name="compatibility:include-filter" value="VtsHalRadioV1_0Target" />
-  <option name="compatibility:include-filter" value="VtsHalRadioV1_1Target" />
-  <option name="compatibility:include-filter" value="VtsHalRenderscriptV1_0Target" />
-  <option name="compatibility:include-filter" value="VtsHalWifiV1_0Host" />
-  <option name="compatibility:include-filter" value="VtsHalWifiV1_1Target" />
-  <option name="compatibility:include-filter" value="VtsHalWifiNanV1_0Target" />
-  <option name="compatibility:include-filter" value="VtsHalWifiOffloadV1_0Target" />
-  <option name="compatibility:include-filter" value="VtsHalWifiSupplicantV1_0Target" />
-
-  <!-- Internal Only -->
-  <option name="compatibility:include-filter" value="VtsHalAudioV2_0Target" />
-  <option name="compatibility:include-filter" value="VtsHalAudioEffectV2_0Target" />
-  <option name="compatibility:include-filter" value="VtsHalBiometricsFingerprintV2_1Target" />
-  <option name="compatibility:include-filter" value="VtsHalBroadcastradioV1_0Target" />
-  <option name="compatibility:include-filter" value="VtsHalCameraProviderV2_4Target" />
-  <option name="compatibility:include-filter" value="VtsHalCasV1_0Target" />
-  <option name="compatibility:include-filter" value="VtsHalConfigstoreV1_0Target" />
-  <option name="compatibility:include-filter" value="VtsHalContexthubV1_0Target" />
-  <option name="compatibility:include-filter" value="VtsHalDrmV1_0Target" />
-  <option name="compatibility:include-filter" value="VtsHalGatekeeperV1_0Target" />
-  <option name="compatibility:include-filter" value="VtsHalGnssV1_0HostBinderize" />
-  <option name="compatibility:include-filter" value="VtsHalGnssV1_0Target" />
-  <option name="compatibility:include-filter" value="VtsHalGraphicsComposerV2_1Target" />
-  <option name="compatibility:include-filter" value="VtsHalGraphicsMapperV2_0Target" />
-  <option name="compatibility:include-filter" value="VtsHalHealthV1_0Target" />
-  <option name="compatibility:include-filter" value="VtsHalKeymasterV3_0Target" />
-  <option name="compatibility:include-filter" value="VtsHalLightV2_0Target" />
-  <option name="compatibility:include-filter" value="VtsHalMemtrackV1_0Target" />
-  <option name="compatibility:include-filter" value="VtsHalMediaOmxStoreV1_0Host" />
-  <option name="compatibility:include-filter" value="VtsHalMediaOmxV1_0Host" />
-  <option name="compatibility:include-filter" value="VtsHalOemLockV1_0Target" />
-  <option name="compatibility:include-filter" value="VtsHalPowerV1_0Target" />
-  <option name="compatibility:include-filter" value="VtsHalPowerV1_1Target" />
-  <option name="compatibility:include-filter" value="VtsHalSensorsV1_0Host" />
-  <option name="compatibility:include-filter" value="VtsHalSensorsV1_0Target" />
-  <option name="compatibility:include-filter" value="VtsHalSoundtriggerV2_0Target" />
-  <option name="compatibility:include-filter" value="VtsHalTetherOffloadConfigV1_0Target" />
-  <option name="compatibility:include-filter" value="VtsHalTetherOffloadControlV1_0Target" />
-  <option name="compatibility:include-filter" value="VtsHalThermalV1_0Target" />
-  <option name="compatibility:include-filter" value="VtsHalThermalV1_1Target" />
-  <option name="compatibility:include-filter" value="VtsHalUsbV1_0Target" />
-  <option name="compatibility:include-filter" value="VtsHalUsbV1_1Target" />
-  <option name="compatibility:include-filter" value="VtsHalVibratorV1_0Host" />
-  <option name="compatibility:include-filter" value="VtsHalVibratorV1_0Target" />
-  <option name="compatibility:include-filter" value="VtsHalVibratorV1_1Target" />
-  <option name="compatibility:include-filter" value="VtsHalVrV1_0Host" />
-  <option name="compatibility:include-filter" value="VtsHalVrV1_0Target" />
-  <option name="compatibility:include-filter" value="VtsHalWeaverV1_0Target" />
+  <option name="compatibility:module-metadata-include-filter" key="plan" value="vts-hal" />
 
 </configuration>
diff --git a/tools/vts-tradefed/res/config/vts-host.xml b/tools/vts-tradefed/res/config/vts-host.xml
index 266b1ad..599d274 100644
--- a/tools/vts-tradefed/res/config/vts-host.xml
+++ b/tools/vts-tradefed/res/config/vts-host.xml
@@ -19,20 +19,7 @@
   <option name="test-tag" value="vts-star" />
   <option name="vts-plan-result:plan-name" value="vts-host" />
 
-  <!-- For HIDL HALs -->
-  <option name="compatibility:include-filter" value="VtsHalContexthubV1_0Host" />
-  <option name="compatibility:include-filter" value="VtsHalGnssV1_0HostBinderize" />
-  <option name="compatibility:include-filter" value="VtsHalMediaOmxV1_0Host" />
-  <option name="compatibility:include-filter" value="VtsHalNfcV1_0HostBinderize" />
-  <option name="compatibility:include-filter" value="VtsHalNfcV1_0HostPassthrough" />
-  <option name="compatibility:include-filter" value="VtsHalRadioV1_0Host" />
-  <option name="compatibility:include-filter" value="VtsHalSensorsV1_0Host" />
-  <option name="compatibility:include-filter" value="VtsHalVibratorV1_0Host" />
-  <option name="compatibility:include-filter" value="VtsHalVrV1_0Host" />
-  <option name="compatibility:include-filter" value="VtsHalWifiV1_0Host" />
-
-  <!-- For shell-based tests -->
-  <option name="compatibility:include-filter" value="SampleShellTest" />
-  <option name="compatibility:include-filter" value="ShellBinaryCrashTest" />
+  <option name="compatibility:module-metadata-include-filter" key="plan" value="vts-hal-host" />
+  <option name="compatibility:module-metadata-include-filter" key="plan" value="vts-host" />
 
 </configuration>
diff --git a/tools/vts-tradefed/res/config/vts-kernel.xml b/tools/vts-tradefed/res/config/vts-kernel.xml
index a6272a5..be5188e 100644
--- a/tools/vts-tradefed/res/config/vts-kernel.xml
+++ b/tools/vts-tradefed/res/config/vts-kernel.xml
@@ -16,22 +16,9 @@
 <configuration description="VTS Kernel Test Plan">
   <include name="vts-base" />
   <option name="plan" value="vts" />
-  <option name="test-tag" value="vts" />
+  <option name="test-tag" value="vts-star" />
   <option name="vts-plan-result:plan-name" value="vts-kernel" />
 
-  <option name="compatibility:include-filter" value="VtsKernelLtp" />
-  <option name="compatibility:include-filter" value="KernelProcFileApiTest" />
-  <option name="compatibility:include-filter" value="VtsKernelLinuxKselftest" />
-  <option name="compatibility:include-filter" value="VtsKernelLinuxKselftestPresubmit" />
-  <option name="compatibility:include-filter" value="SyscallExistenceTest" />
-  <option name="compatibility:include-filter" value="VtsKernelApiSysfsTest" />
-  <option name="compatibility:include-filter" value="VtsKernelBinderTest" />
-  <option name="compatibility:include-filter" value="VtsKernelConfig" />
-  <option name="compatibility:include-filter" value="VtsKernelHwBinder" />
-  <option name="compatibility:include-filter" value="VtsKernelLibcutilsTest" />
-  <option name="compatibility:include-filter" value="VtsKernelNetdTest" />
-  <option name="compatibility:include-filter" value="VtsKernelSelinuxFileApi" />
-  <option name="compatibility:include-filter" value="VtsKernelTunTest" />
-  <option name="compatibility:include-filter" value="VtsQtaguidTest" />
+  <option name="compatibility:module-metadata-include-filter" key="plan" value="vts-kernel" />
 
 </configuration>
diff --git a/tools/vts-tradefed/res/config/vts-p2p.xml b/tools/vts-tradefed/res/config/vts-p2p.xml
new file mode 100644
index 0000000..3ac978a
--- /dev/null
+++ b/tools/vts-tradefed/res/config/vts-p2p.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="VTS Pair to Pair Test Plan">
+  <include name="vts-base-common" />
+  <option name="plan" value="vts" />
+  <option name="test-tag" value="vts-star" />
+  <option name="vts-plan-result:plan-name" value="vts-p2p" />
+  <option name="compatibility:primary-abi-only" value="true" />
+
+  <device name="device1">
+    <build_provider class="com.android.compatibility.common.tradefed.build.CompatibilityBuildProvider" />
+    <target_preparer class="com.android.tradefed.targetprep.VtsDeviceInfoCollector" />
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.VtsFilePusher">
+        <option name="push-group" value="HostDrivenTest.push" />
+    </target_preparer>
+    <!-- TODO(yuexima): The following preparer should be moved to module level when supported. -->
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.ApkInstaller">
+        <option name="test-file-name" value="DATA/app/nbu/android_snippet.apk" />
+        <option name="cleanup-apks" value="true" />
+        <option name="install-arg" value="-r" />
+        <option name="install-arg" value="-g" />
+    </target_preparer>
+  </device>
+
+  <device name="device2" >
+    <build_provider class="com.android.compatibility.common.tradefed.build.CompatibilityBuildProvider" />
+    <target_preparer class="com.android.tradefed.targetprep.VtsDeviceInfoCollector" />
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.VtsFilePusher">
+        <option name="push-group" value="HostDrivenTest.push" />
+    </target_preparer>
+    <!-- TODO(yuexima): The following preparer should be moved to module level when supported. -->
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.ApkInstaller">
+        <option name="test-file-name" value="DATA/app/nbu/android_snippet.apk" />
+        <option name="cleanup-apks" value="true" />
+        <option name="install-arg" value="-r" />
+        <option name="install-arg" value="-g" />
+    </target_preparer>
+  </device>
+
+  <option name="compatibility:module-metadata-include-filter" key="plan" value="vts-p2p" />
+
+</configuration>
diff --git a/tools/vts-tradefed/res/config/vts-security.xml b/tools/vts-tradefed/res/config/vts-security.xml
index 2fa4bff..674248a 100644
--- a/tools/vts-tradefed/res/config/vts-security.xml
+++ b/tools/vts-tradefed/res/config/vts-security.xml
@@ -21,5 +21,7 @@
 
   <!-- For Proof-of-Concept (PoC) Tests -->
   <option name="compatibility:include-filter" value="SecurityPoCKernelTest" />
+  <!-- For system property Tests -->
+  <option name="compatibility:include-filter" value="VtsTrebleSysProp" />
 
 </configuration>
diff --git a/tools/vts-tradefed/res/config/vts-selftest.xml b/tools/vts-tradefed/res/config/vts-selftest.xml
index de08e84..d6e3118 100644
--- a/tools/vts-tradefed/res/config/vts-selftest.xml
+++ b/tools/vts-tradefed/res/config/vts-selftest.xml
@@ -16,12 +16,15 @@
 <configuration description="VTS Self Test Plan">
   <include name="vts-base" />
   <option name="plan" value="vts" />
-  <option name="test-tag" value="vts" />
+  <option name="test-tag" value="vts-star" />
   <option name="vts-plan-result:plan-name" value="vts-selftest" />
   <option name="compatibility:primary-abi-only" value="true" />
 
   <!-- VTS self test modules -->
   <option name="compatibility:include-filter" value="VtsSelfTestBaseTest" />
+  <option name="compatibility:include-filter" value="VtsSelfTestPythonVirtualenvPreparerTestPart0" />
+  <option name="compatibility:include-filter" value="VtsSelfTestPythonVirtualenvPreparerTestPart1" />
+  <option name="compatibility:include-filter" value="VtsSelfTestPythonVirtualenvPreparerTestPart2" />
 
   <option name="compatibility:test-arg" value="com.android.tradefed.testtype.VtsMultiDeviceTest:run-as-vts-self-test:true" />
 
@@ -51,7 +54,7 @@
   <option name="compatibility:include-filter" value="VtsKernelNetdTest" />
   <option name="compatibility:include-filter" value="VtsKernelSelinuxFileApi" />
   <option name="compatibility:include-filter" value="VtsKernelTunTest" />
-  <option name="compatibility:include-filter" value="VtsQtaguidTest" />
+  <option name="compatibility:include-filter" value="VtsKernelQtaguidTest" />
 
   <!--  From vts-vndk.xml -->
   <option name="compatibility:include-filter" value="VtsVndkDependency" />
diff --git a/tools/vts-tradefed/res/config/vts-staging-default.xml b/tools/vts-tradefed/res/config/vts-staging-default.xml
index 4ac5ad5..38cebb0 100644
--- a/tools/vts-tradefed/res/config/vts-staging-default.xml
+++ b/tools/vts-tradefed/res/config/vts-staging-default.xml
@@ -27,5 +27,4 @@
     <!-- for VNDK -->
     <option name="compatibility:include-filter" value="VtsVndkAbi" />
     <option name="compatibility:include-filter" value="VtsVndkOpenLibraries" />
-
 </configuration>
diff --git a/tools/vts-tradefed/res/config/vts-staging-kernel.xml b/tools/vts-tradefed/res/config/vts-staging-kernel.xml
index bd91235..f28d005 100644
--- a/tools/vts-tradefed/res/config/vts-staging-kernel.xml
+++ b/tools/vts-tradefed/res/config/vts-staging-kernel.xml
@@ -16,7 +16,7 @@
 <configuration description="VTS Serving Plan for Kernel Staging Tests">
   <include name="vts-base" />
   <option name="plan" value="vts" />
-  <option name="test-tag" value="vts" />
+  <option name="test-tag" value="vts-star" />
   <option name="vts-plan-result:plan-name" value="vts-staging-kernel" />
 
   <option name="compatibility:include-filter" value="SecurityPoCKernelTestStaging" />
diff --git a/tools/vts-tradefed/res/config/vts-staging-presubmit.xml b/tools/vts-tradefed/res/config/vts-staging-presubmit.xml
index 2bf7d41..0f048f9 100644
--- a/tools/vts-tradefed/res/config/vts-staging-presubmit.xml
+++ b/tools/vts-tradefed/res/config/vts-staging-presubmit.xml
@@ -19,6 +19,7 @@
   <option name="plan" value="vts" />
   <option name="test-tag" value="vts-star" />
   <option name="vts-plan-result:plan-name" value="vts-staging-presubmit" />
+  <option name="vts-plan-result:default-type" value="staging" />
 
   <!-- For vts-hal-hidl -->
   <option name="compatibility:test-arg" value="com.android.tradefed.testtype.VtsMultiDeviceTest:gtest-batch-mode:true" />
diff --git a/tools/vts-tradefed/res/config/vts-staging-sancov.xml b/tools/vts-tradefed/res/config/vts-staging-sancov.xml
index d5f37e3..615e3e5 100644
--- a/tools/vts-tradefed/res/config/vts-staging-sancov.xml
+++ b/tools/vts-tradefed/res/config/vts-staging-sancov.xml
@@ -32,6 +32,7 @@
   <option name="compatibility:include-filter" value="VtsHalRadioV1_0Target" />
   <option name="compatibility:include-filter" value="VtsHalRadioV1_1Target" />
   <option name="compatibility:include-filter" value="VtsHalRenderscriptV1_0Target" />
+  <option name="compatibility:include-filter" value="VtsHalSapV1_0Target" />
   <option name="compatibility:include-filter" value="VtsHalWifiV1_0Host" />
   <option name="compatibility:include-filter" value="VtsHalWifiV1_1Target" />
   <option name="compatibility:include-filter" value="VtsHalWifiNanV1_0Target" />
diff --git a/tools/vts-tradefed/res/config/vts-staging-selftest.xml b/tools/vts-tradefed/res/config/vts-staging-selftest.xml
new file mode 100644
index 0000000..272ce93
--- /dev/null
+++ b/tools/vts-tradefed/res/config/vts-staging-selftest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 Google Inc.
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT 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="VTS Serving Plan for SelfTest (Staging)">
+  <include name="vts-base" />
+  <option name="compatibility:primary-abi-only" value="true" />
+  <option name="plan" value="vts" />
+  <option name="test-tag" value="vts-star" />
+
+  <option name="compatibility:module-metadata-include-filter" key="plan" value="vts-staging-selftest" />
+
+</configuration>
diff --git a/tools/vts-tradefed/res/config/vts-staging-web.xml b/tools/vts-tradefed/res/config/vts-staging-web.xml
index 891f3d2..655cc3a 100644
--- a/tools/vts-tradefed/res/config/vts-staging-web.xml
+++ b/tools/vts-tradefed/res/config/vts-staging-web.xml
@@ -16,7 +16,7 @@
 <configuration description="VTS Staging Plan for VTS Dashboard">
   <include name="vts-base" />
   <option name="plan" value="vts" />
-  <option name="test-tag" value="vts" />
+  <option name="test-tag" value="vts-star" />
   <option name="vts-plan-result:plan-name" value="vts-staging-web" />
   <option name="vts-plan-result:default-type" value="staging" />
 
diff --git a/tools/vts-tradefed/res/config/vts-star.xml b/tools/vts-tradefed/res/config/vts-star.xml
new file mode 100644
index 0000000..a41b029
--- /dev/null
+++ b/tools/vts-tradefed/res/config/vts-star.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="VTS-* Main Test Plan Excluding VTS">
+  <include name="vts-base" />
+  <option name="plan" value="vts" />
+  <option name="test-tag" value="vts-star" />
+  <option name="vts-plan-result:plan-name" value="vts-star" />
+
+  <option name="compatibility:module-metadata-include-filter" key="plan" value="vts-app" />
+  <option name="compatibility:module-metadata-include-filter" key="plan" value="vts-codelab" />
+  <option name="compatibility:module-metadata-include-filter" key="plan" value="vts-device-health" />
+  <option name="compatibility:module-metadata-include-filter" key="plan" value="vts-fuzz" />
+  <option name="compatibility:module-metadata-include-filter" key="plan" value="vts-hal-adapter" />
+  <option name="compatibility:module-metadata-include-filter" key="plan" value="vts-hal-profiling" />
+  <option name="compatibility:module-metadata-include-filter" key="plan" value="vts-host" />
+  <option name="compatibility:module-metadata-include-filter" key="plan" value="vts-library" />
+  <option name="compatibility:module-metadata-include-filter" key="plan" value="vts-performance" />
+  <option name="compatibility:module-metadata-include-filter" key="plan" value="vts-selftest" />
+
+</configuration>
diff --git a/tools/vts-tradefed/res/config/vts-unit-tests.xml b/tools/vts-tradefed/res/config/vts-unit-tests.xml
new file mode 100644
index 0000000..22c09d8
--- /dev/null
+++ b/tools/vts-tradefed/res/config/vts-unit-tests.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="Executes the VTS unit tests">
+    <option name="null-device" value="true" />
+    <build_provider class="com.android.tradefed.build.StubBuildProvider" />
+    <test class="com.android.tradefed.testtype.HostTest" >
+        <option name="class" value="com.android.tradefed.VtsUnitTests" />
+        <option name="class" value="com.android.compatibility.common.tradefed.VtsUnitTests" />
+    </test>
+    <logger class="com.android.tradefed.log.FileLogger" />
+
+    <result_reporter class="com.android.tradefed.result.ConsoleResultReporter">
+        <option name="suppress-passed-tests" value="true" />
+    </result_reporter>
+    <template-include name="reporters" default="empty" />
+</configuration>
diff --git a/tools/vts-tradefed/res/config/vts-vndk.xml b/tools/vts-tradefed/res/config/vts-vndk.xml
index 57f2453..dc31f12 100644
--- a/tools/vts-tradefed/res/config/vts-vndk.xml
+++ b/tools/vts-tradefed/res/config/vts-vndk.xml
@@ -16,9 +16,9 @@
 <configuration description="VTS VNDK (Vendor NDK) Test Plan">
   <include name="vts-base" />
   <option name="plan" value="vts" />
-  <option name="test-tag" value="vts" />
+  <option name="test-tag" value="vts-star" />
   <option name="vts-plan-result:plan-name" value="vts-vndk" />
 
-  <option name="compatibility:include-filter" value="VtsVndkDependency" />
+  <option name="compatibility:module-metadata-include-filter" key="plan" value="vts-vndk" />
 
 </configuration>
diff --git a/tools/vts-tradefed/res/config/vts.xml b/tools/vts-tradefed/res/config/vts.xml
index c98f434..0da9002 100644
--- a/tools/vts-tradefed/res/config/vts.xml
+++ b/tools/vts-tradefed/res/config/vts.xml
@@ -19,92 +19,16 @@
   <option name="test-tag" value="vts" />
   <option name="vts-plan-result:plan-name" value="vts" />
 
-  <!-- For Treble-specific validations -->
-  <option name="compatibility:include-filter" value="VtsTreblePlatformVersionTest" />
-  <option name="compatibility:include-filter" value="VtsTrebleVintfTest" />
+  <option name="compatibility:module-metadata-include-filter" key="plan" value="vts-firmware" />
+  <option name="compatibility:module-metadata-include-filter" key="plan" value="vts-hal" />
+  <option name="compatibility:module-metadata-include-filter" key="plan" value="vts-hal-replay" />
+  <option name="compatibility:module-metadata-include-filter" key="plan" value="vts-kernel" />
+  <option name="compatibility:module-metadata-include-filter" key="plan" value="vts-security" />
+  <option name="compatibility:module-metadata-include-filter" key="plan" value="vts-treble" />
+  <option name="compatibility:module-metadata-include-filter" key="plan" value="vts-vndk" />
 
-  <!-- From vts-hal-hidl.xml -->
-  <option name="compatibility:include-filter" value="VtsHalBluetoothV1_0Target" />
-  <option name="compatibility:include-filter" value="VtsHalBootV1_0Target" />
-  <option name="compatibility:include-filter" value="VtsHalDumpstateV1_0Target" />
-  <option name="compatibility:include-filter" value="VtsHalIrV1_0Target" />
-  <option name="compatibility:include-filter" value="VtsHalNfcV1_0Target" />
-  <option name="compatibility:include-filter" value="VtsHalRadioV1_0Target" />
-  <option name="compatibility:include-filter" value="VtsHalRadioV1_1Target" />
-  <option name="compatibility:include-filter" value="VtsHalRenderscriptV1_0Target" />
-  <option name="compatibility:include-filter" value="VtsHalWifiV1_0Host" />
-  <option name="compatibility:include-filter" value="VtsHalWifiV1_1Target" />
-  <option name="compatibility:include-filter" value="VtsHalWifiNanV1_0Target" />
-  <option name="compatibility:include-filter" value="VtsHalWifiOffloadV1_0Target" />
-  <option name="compatibility:include-filter" value="VtsHalWifiSupplicantV1_0Target" />
-
-  <!-- From vts-hal-hidl.xml: Internal Only -->
-  <option name="compatibility:include-filter" value="VtsHalAudioV2_0Target" />
-  <option name="compatibility:include-filter" value="VtsHalAudioEffectV2_0Target" />
-  <option name="compatibility:include-filter" value="VtsHalBiometricsFingerprintV2_1Target" />
-  <option name="compatibility:include-filter" value="VtsHalBroadcastradioV1_0Target" />
-  <option name="compatibility:include-filter" value="VtsHalCameraProviderV2_4Target" />
-  <option name="compatibility:include-filter" value="VtsHalCasV1_0Target" />
-  <option name="compatibility:include-filter" value="VtsHalConfigstoreV1_0Target" />
-  <option name="compatibility:include-filter" value="VtsHalContexthubV1_0Target" />
-  <option name="compatibility:include-filter" value="VtsHalDrmV1_0Target" />
-  <option name="compatibility:include-filter" value="VtsHalGatekeeperV1_0Target" />
-  <option name="compatibility:include-filter" value="VtsHalGnssV1_0Target" />
-  <option name="compatibility:include-filter" value="VtsHalGraphicsComposerV2_1Target" />
-  <option name="compatibility:include-filter" value="VtsHalGraphicsMapperV2_0Target" />
-  <option name="compatibility:include-filter" value="VtsHalHealthV1_0Target" />
-  <option name="compatibility:include-filter" value="VtsHalKeymasterV3_0Target" />
-  <option name="compatibility:include-filter" value="VtsHalLightV2_0Target" />
-  <option name="compatibility:include-filter" value="VtsHalMemtrackV1_0Target" />
-  <option name="compatibility:include-filter" value="VtsHalMediaOmxStoreV1_0Host" />
-  <option name="compatibility:include-filter" value="VtsHalMediaOmxV1_0Host" />
-  <option name="compatibility:include-filter" value="VtsHalOemLockV1_0Target" />
-  <option name="compatibility:include-filter" value="VtsHalPowerV1_0Target" />
-  <option name="compatibility:include-filter" value="VtsHalPowerV1_1Target" />
-  <option name="compatibility:include-filter" value="VtsHalSensorsV1_0Target" />
-  <option name="compatibility:include-filter" value="VtsHalSoundtriggerV2_0Target" />
-  <option name="compatibility:include-filter" value="VtsHalTetherOffloadConfigV1_0Target" />
-  <option name="compatibility:include-filter" value="VtsHalTetherOffloadControlV1_0Target" />
-  <option name="compatibility:include-filter" value="VtsHalThermalV1_0Target" />
-  <option name="compatibility:include-filter" value="VtsHalThermalV1_1Target" />
-  <option name="compatibility:include-filter" value="VtsHalUsbV1_0Target" />
-  <option name="compatibility:include-filter" value="VtsHalUsbV1_1Target" />
-  <option name="compatibility:include-filter" value="VtsHalVibratorV1_0Target" />
-  <option name="compatibility:include-filter" value="VtsHalVibratorV1_1Target" />
-  <option name="compatibility:include-filter" value="VtsHalVrV1_0Target" />
-  <option name="compatibility:include-filter" value="VtsHalWeaverV1_0Target" />
-
-  <!-- From vts-kernel.xml -->
-  <option name="compatibility:include-filter" value="VtsKernelLtp" />
-  <option name="compatibility:include-filter" value="KernelProcFileApiTest" />
-  <option name="compatibility:include-filter" value="VtsKernelLinuxKselftest" />
-  <option name="compatibility:include-filter" value="VtsKernelLinuxKselftestPresubmit" />
-  <option name="compatibility:include-filter" value="SyscallExistenceTest" />
-  <option name="compatibility:include-filter" value="VtsKernelApiSysfsTest" />
-  <option name="compatibility:include-filter" value="VtsKernelBinderTest" />
-  <option name="compatibility:include-filter" value="VtsKernelConfig" />
-  <option name="compatibility:include-filter" value="VtsKernelHwBinder" />
-  <option name="compatibility:include-filter" value="VtsKernelLibcutilsTest" />
-  <option name="compatibility:include-filter" value="VtsKernelNetdTest" />
-  <option name="compatibility:include-filter" value="VtsKernelSelinuxFileApi" />
-  <option name="compatibility:include-filter" value="VtsKernelTunTest" />
-  <option name="compatibility:include-filter" value="VtsQtaguidTest" />
-
-  <!--  From vts-vndk.xml -->
-  <option name="compatibility:include-filter" value="VtsVndkDependency" />
-
-  <!-- For Hidl Hal replay tests -->
-  <option name="compatibility:include-filter" value="VtsHalNfcV1_0TargetReplay" />
-
-  <!-- For Hidl Hal replay tests: Internal Only -->
-  <option name="compatibility:include-filter" value="VtsHalBiometricsFingerprintV2_1TargetReplay" />
-  <option name="compatibility:include-filter" value="VtsHalContexthubV1_0TargetReplay" />
-  <option name="compatibility:include-filter" value="VtsHalKeymasterV3_0TargetReplay" />
-  <option name="compatibility:include-filter" value="VtsHalLightV2_0TargetReplay" />
-  <option name="compatibility:include-filter" value="VtsHalMemtrackV1_0TargetReplay" />
-  <option name="compatibility:include-filter" value="VtsHalPowerV1_0TargetReplay" />
-  <option name="compatibility:include-filter" value="VtsHalUsbV1_0TargetReplay" />
-  <option name="compatibility:include-filter" value="VtsHalVibratorV1_0TargetReplay" />
-  <option name="compatibility:include-filter" value="VtsHalVrV1_0TargetReplay" />
+  <!-- Unapproved vts-security Tests -->
+  <option name="compatibility:exclude-filter" value="SecurityPoCKernelTest" />
+  <option name="compatibility:exclude-filter" value="VtsSecuritySelinuxPolicyHost" />
 
 </configuration>
diff --git a/tools/vts-tradefed/res/default/DefaultTestCase.config b/tools/vts-tradefed/res/default/DefaultTestCase.runner_conf
similarity index 100%
rename from tools/vts-tradefed/res/default/DefaultTestCase.config
rename to tools/vts-tradefed/res/default/DefaultTestCase.runner_conf
diff --git a/utils/Android.bp b/utils/Android.bp
index 3971d00..71743eb 100644
--- a/utils/Android.bp
+++ b/utils/Android.bp
@@ -14,4 +14,5 @@
 
 subdirs = [
      "native",
-]
\ No newline at end of file
+     "python",
+]
diff --git a/utils/native/libprofiling/Android.bp b/utils/native/libprofiling/Android.bp
index 070c645..3928457 100644
--- a/utils/native/libprofiling/Android.bp
+++ b/utils/native/libprofiling/Android.bp
@@ -53,6 +53,15 @@
         "-Wall",
     ],
 
+    multilib: {
+        lib32: {
+            ldflags: ["-Wl,--rpath,/data/local/tmp/32"],
+        },
+        lib64: {
+            ldflags: ["-Wl,--rpath,/data/local/tmp/64"],
+        },
+    },
+
     export_include_dirs: ["."],
 }
 
diff --git a/utils/native/libprofiling/VtsProfilingConfigureMain.cpp b/utils/native/libprofiling/VtsProfilingConfigureMain.cpp
index 8694b11..67cd7cc 100644
--- a/utils/native/libprofiling/VtsProfilingConfigureMain.cpp
+++ b/utils/native/libprofiling/VtsProfilingConfigureMain.cpp
@@ -85,36 +85,34 @@
   return true;
 }
 
-// Usage examples:
-//   To enable, <binary> enable <lib path>
-//   To disable, <binary> disable clear
-int main(int argc, char *argv[]) {
-  bool enable_profiling = false;
-  if (argc >= 2) {
-    if (!strcmp(argv[1], "enable")) {
-      enable_profiling = true;
-    }
-    if (argc == 3 && strlen(argv[2]) > 0) {
-      if (!strcmp(argv[2], "clear")) {
-        property_set("hal.instrumentation.lib.path", "");
-        printf("* setprop hal.instrumentation.lib.path \"\"\n");
-      } else {
-        property_set("hal.instrumentation.lib.path", argv[2]);
-        printf("* setprop hal.instrumentation.lib.path %s\n", argv[2]);
-      }
-    }
-  }
+void PrintUsage() {
+  printf(
+      "Usage: \n"
+      "To enable profiling: <binary> enable <lib path 32> <lib path 64>"
+      "To disable profiling <binary> disable");
+}
 
-  if (enable_profiling) {
+int main(int argc, char *argv[]) {
+  if (argc == 2 && !strcmp(argv[1], "disable")) {
+    printf("* disable profiling.\n");
+    property_set("hal.instrumentation.lib.path.32", "");
+    property_set("hal.instrumentation.lib.path.64", "");
+    if (!DisableHALProfiling()) {
+      printf("failed to disable profiling.\n");
+      return -1;
+    }
+  } else if (argc >= 2 && !strcmp(argv[1], "enable")) {
     printf("* enable profiling.\n");
+    if (argc == 4) {
+      property_set("hal.instrumentation.lib.path.32", argv[2]);
+      property_set("hal.instrumentation.lib.path.64", argv[3]);
+    }
     if (!EnableHALProfiling()) {
-      fprintf(stderr, "failed to enable profiling.\n");
+      printf("failed to enable profiling.\n");
+      return -1;
     }
   } else {
-    printf("* disable profiling.\n");
-    if (!DisableHALProfiling()) {
-      fprintf(stderr, "failed to disable profiling.\n");
-    }
+    PrintUsage();
   }
   return 0;
 }
diff --git a/utils/native/testability_checker/Android.bp b/utils/native/testability_checker/Android.bp
index ed24285..e37e9e8 100644
--- a/utils/native/testability_checker/Android.bp
+++ b/utils/native/testability_checker/Android.bp
@@ -19,13 +19,17 @@
     shared_libs: [
         "libbase",
         "libcutils",
+        "libhidlbase",
+        "libhidltransport",
         "liblog",
         "libselinux",
         "libtinyxml2",
+        "libutils",
         "libz",
     ],
     static_libs: [
         "libvintf",
+        "libhidl-gen-utils",
     ],
     cflags: [
         "-Wall",
@@ -37,14 +41,12 @@
     name: "libvts_testability_checker",
     defaults : ["VtsTestabilityCheckerDefaults"],
     srcs: ["VtsTestabilityChecker.cpp"],
-    host_supported: true,
 }
 
 cc_test {
     name: "libvts_testability_checker_test",
     defaults : ["VtsTestabilityCheckerDefaults"],
     srcs: ["VtsTestabilityCheckerTest.cpp"],
-    host_supported: true,
 
     static_libs: [
         "libgmock",
diff --git a/utils/native/testability_checker/VtsTestabilityChecker.cpp b/utils/native/testability_checker/VtsTestabilityChecker.cpp
index 19da321..206ed3d 100644
--- a/utils/native/testability_checker/VtsTestabilityChecker.cpp
+++ b/utils/native/testability_checker/VtsTestabilityChecker.cpp
@@ -21,16 +21,25 @@
 #include <iostream>
 #include <set>
 
+#include <android-base/strings.h>
+#include <vintf/parse_string.h>
+
+using android::base::Join;
 using android::vintf::Arch;
 using android::vintf::CompatibilityMatrix;
 using android::vintf::gArchStrings;
 using android::vintf::HalManifest;
 using android::vintf::ManifestHal;
+using android::vintf::ManifestInstance;
 using android::vintf::MatrixHal;
+using android::vintf::MatrixInstance;
+using android::vintf::toFQNameString;
 using android::vintf::Transport;
 using android::vintf::Version;
+using android::vintf::operator<<;
 using std::set;
 using std::string;
+using std::vector;
 
 namespace android {
 namespace vts {
@@ -45,13 +54,13 @@
   bool check_framework_hal = CheckFrameworkManifestHal(
       hal_package_name, hal_version, hal_interface_name, arch,
       &famework_hal_instances);
-  bool check_vendor_hal_compliance = CheckFrameworkCompatibleHal(
-      hal_package_name, hal_version, hal_interface_name, arch,
-      &vendor_hal_instances);
+  bool check_vendor_hal =
+      CheckVendorManifestHal(hal_package_name, hal_version, hal_interface_name,
+                             arch, &vendor_hal_instances);
   set_union(famework_hal_instances.begin(), famework_hal_instances.end(),
             vendor_hal_instances.begin(), vendor_hal_instances.end(),
             std::inserter(*instances, instances->begin()));
-  return check_framework_hal || check_vendor_hal_compliance;
+  return check_framework_hal || check_vendor_hal;
 }
 
 bool VtsTestabilityChecker::CheckHalForNonComplianceTest(
@@ -59,8 +68,43 @@
     const string& hal_interface_name, const Arch& arch,
     set<string>* instances) {
   CHECK(instances) << "instances set should not be NULL.";
-  return CheckVendorManifestHal(hal_package_name, hal_version,
-                                hal_interface_name, arch, instances);
+  set<string> vendor_hal_instances;
+  set<string> test_hal_instances;
+  bool check_vendor_hal =
+      CheckVendorManifestHal(hal_package_name, hal_version, hal_interface_name,
+                             arch, &vendor_hal_instances);
+
+  bool check_test_hal = CheckTestHalWithHwManager(
+      hal_package_name, hal_version, hal_interface_name, &test_hal_instances);
+
+  set_union(vendor_hal_instances.begin(), vendor_hal_instances.end(),
+            test_hal_instances.begin(), test_hal_instances.end(),
+            std::inserter(*instances, instances->begin()));
+  return check_vendor_hal || check_test_hal;
+}
+
+vector<const ManifestInstance*> VtsTestabilityChecker::FindInstance(
+    const vector<ManifestInstance>& manifest_instances,
+    const MatrixInstance& matrix_instance) {
+  vector<const ManifestInstance*> ret;
+  for (const auto& e : manifest_instances) {
+    if (matrix_instance.matchInstance(e.instance())) {
+      ret.push_back(&e);
+    }
+  }
+  return ret;
+}
+
+vector<const ManifestInstance*> VtsTestabilityChecker::FindInterface(
+    const vector<ManifestInstance>& manifest_instances,
+    const MatrixInstance& matrix_instance) {
+  vector<const ManifestInstance*> ret;
+  for (const auto& e : manifest_instances) {
+    if (e.interface() == matrix_instance.interface()) {
+      ret.push_back(&e);
+    }
+  }
+  return ret;
 }
 
 bool VtsTestabilityChecker::CheckFrameworkCompatibleHal(
@@ -68,107 +112,99 @@
     const string& hal_interface_name, const Arch& arch,
     set<string>* instances) {
   CHECK(instances) << "instances set should not be NULL.";
-  const MatrixHal* matrix_hal =
-      framework_comp_matrix_->getHal(hal_package_name, hal_version);
 
-  if (!matrix_hal) {
-    LOG(DEBUG) << "Does not find hal " << hal_package_name
-               << " in compatibility matrix";
-    return false;
-  }
+  auto matrix_instances = framework_comp_matrix_->getFqInstances(
+      hal_package_name, hal_version, hal_interface_name);
+  auto manifest_instances = device_hal_manifest_->getFqInstances(
+      hal_package_name, hal_version, hal_interface_name);
 
-  if (!hal_interface_name.empty() &&
-      !matrix_hal->hasInterface(hal_interface_name)) {
-    LOG(DEBUG) << "MaxtrixHal " << hal_package_name
-               << " does not support interface " << hal_interface_name;
-    return false;
-  }
+  bool testable = false;
 
-  const ManifestHal* manifest_hal =
-      device_hal_manifest_->getHal(hal_package_name, hal_version);
-
-  if (!manifest_hal || (!hal_interface_name.empty() &&
-                        !manifest_hal->hasInterface(hal_interface_name))) {
-    if (!matrix_hal->optional) {
+  for (const auto& matrix_instance : matrix_instances) {
+    const auto& matched_instances =
+        FindInstance(manifest_instances, matrix_instance);
+    if (!matrix_instance.optional() && matched_instances.empty()) {
+      // In matrix but not in manifest.
+      // The test should still run, but expect the test
+      // to fail (due to incompatible vendor and framework HAL).
       LOG(ERROR) << "Compatibility error. Hal " << hal_package_name
                  << " is required by framework but not supported by vendor";
-      // Return true here to indicate the test should run, but expect the test
-      // to fail (due to incompatible vendor and framework HAL).
-      GetTestInstances(matrix_hal, nullptr /*ManifestHal*/, hal_interface_name,
-                       instances);
-      return true;
+      if (!hal_interface_name.empty()) {
+        if (!matrix_instance.isRegex()) {
+          instances->insert(matrix_instance.exactInstance());
+        } else {
+          LOG(ERROR) << "Ignore regex-instance '"
+                     << matrix_instance.regexPattern();
+        }
+      }
+      testable |= true;
+      continue;
     }
-    return false;
-  }
 
-  if (manifest_hal->transport() == Transport::PASSTHROUGH &&
-      !CheckPassthroughManifestHal(manifest_hal, arch)) {
-    LOG(DEBUG) << "ManfestHal " << hal_package_name
-               << " is passthrough and does not support arch "
-               << gArchStrings.at(static_cast<int>(arch));
-    return false;
-  }
+    if (hal_interface_name.empty()) {
+      testable |= !matched_instances.empty();
+      continue;
+    }
 
-  GetTestInstances(matrix_hal, manifest_hal, hal_interface_name, instances);
-  return true;
+    auto get_testable_instances =
+        [&](const vector<const vintf::ManifestInstance*>& manifest_instances) {
+          vector<string> ret;
+          for (const auto& manifest_instance : manifest_instances) {
+            if ((manifest_instance->transport() == Transport::PASSTHROUGH &&
+                 CheckPassthroughManifestArch(manifest_instance->arch(),
+                                              arch)) ||
+                manifest_instance->transport() == Transport::HWBINDER) {
+              ret.push_back(manifest_instance->instance());
+            }
+          }
+          return ret;
+        };
+
+    auto testable_instances = get_testable_instances(matched_instances);
+    if (!testable_instances.empty()) {
+      instances->insert(testable_instances.begin(), testable_instances.end());
+      testable |= true;
+      continue;
+    }
+
+    // Special case: if a.h.foo@1.0::IFoo/default is in matrix but /custom
+    // is in manifest, the interface is still testable, but /default should
+    // not be added to instances.
+    const auto& matched_interface_instances =
+        FindInterface(manifest_instances, matrix_instance);
+    auto testable_interfaces =
+        get_testable_instances(matched_interface_instances);
+    if (!testable_interfaces.empty()) {
+      testable |= true;
+      continue;
+    }
+  }
+  if (instances->empty()) {
+    LOG(ERROR) << "Hal "
+               << toFQNameString(hal_package_name, hal_version,
+                                 hal_interface_name)
+               << " has no testable instance";
+  }
+  return testable;
 }
 
-void VtsTestabilityChecker::GetTestInstances(const MatrixHal* matrix_hal,
-                                             const ManifestHal* manifest_hal,
-                                             const string& interface_name,
-                                             set<string>* instances) {
-  CHECK(matrix_hal || manifest_hal)
-      << "MatrixHal and ManifestHal should not both be NULL.";
-  CHECK(instances) << "instances set should not be NULL.";
-  if (interface_name.empty()) {
-    LOG(INFO) << "No interface name provided, return empty instances set.";
-    return;
-  }
-
-  set<string> manifest_hal_instances;
-  if (!manifest_hal || (matrix_hal && !matrix_hal->optional)) {
-    // Only matrix_hal is provided or matrix_hal is required, get instances
-    // from matrix_hal.
-    set<string> matrix_hal_instances = matrix_hal->getInstances(interface_name);
-    instances->insert(matrix_hal_instances.begin(), matrix_hal_instances.end());
-  } else if (!matrix_hal) {
-    // Only manifest_hal is provided, get instances from manifest_hal.
-    set<string> manifest_hal_instances =
-        manifest_hal->getInstances(interface_name);
-    instances->insert(manifest_hal_instances.begin(),
-                      manifest_hal_instances.end());
-  } else {
-    // Both matrix_hal and manifest_hal not null && matrix_hal is optional,
-    // get intersection of instances from both matrix_hal and manifest_hal.
-    set<string> matrix_hal_instances = matrix_hal->getInstances(interface_name);
-    set<string> manifest_hal_instances =
-        manifest_hal->getInstances(interface_name);
-    set_intersection(matrix_hal_instances.begin(), matrix_hal_instances.end(),
-                     manifest_hal_instances.begin(),
-                     manifest_hal_instances.end(),
-                     std::inserter(*instances, instances->begin()));
-  }
-}
-
-bool VtsTestabilityChecker::CheckPassthroughManifestHal(
-    const ManifestHal* manifest_hal, const Arch& arch) {
-  CHECK(manifest_hal) << "ManifestHal should not be NULL.";
+bool VtsTestabilityChecker::CheckPassthroughManifestArch(
+    const Arch& manifest_arch, const Arch& arch) {
   switch (arch) {
     case Arch::ARCH_32: {
-      if (android::vintf::has32(manifest_hal->transportArch.arch)) {
+      if (android::vintf::has32(manifest_arch)) {
         return true;
       }
       break;
     }
     case Arch::ARCH_64: {
-      if (android::vintf::has64(manifest_hal->transportArch.arch)) {
+      if (android::vintf::has64(manifest_arch)) {
         return true;
       }
       break;
     }
     default: {
-      LOG(ERROR) << "Unexpected arch to check: "
-                 << gArchStrings.at(static_cast<int>(arch));
+      LOG(ERROR) << "Unexpected arch to check: " << arch;
       break;
     }
   }
@@ -179,49 +215,85 @@
     const string& hal_package_name, const Version& hal_version,
     const string& hal_interface_name, const Arch& arch,
     set<string>* instances) {
-  CHECK(instances) << "instances set should not be NULL.";
-  return CheckManifestHal(
-      framework_hal_manifest_->getHal(hal_package_name, hal_version),
-      hal_interface_name, arch, instances);
+  return CheckManifestHal(framework_hal_manifest_, hal_package_name,
+                          hal_version, hal_interface_name, arch, instances);
 }
 
 bool VtsTestabilityChecker::CheckVendorManifestHal(
     const string& hal_package_name, const Version& hal_version,
     const string& hal_interface_name, const Arch& arch,
     set<string>* instances) {
-  CHECK(instances) << "instances set should not be NULL.";
-  return CheckManifestHal(
-      device_hal_manifest_->getHal(hal_package_name, hal_version),
-      hal_interface_name, arch, instances);
+  return CheckManifestHal(device_hal_manifest_, hal_package_name, hal_version,
+                          hal_interface_name, arch, instances);
 }
 
-bool VtsTestabilityChecker::CheckManifestHal(const ManifestHal* manifest_hal,
+bool VtsTestabilityChecker::CheckManifestHal(const HalManifest* hal_manifest,
+                                             const string& hal_package_name,
+                                             const Version& hal_version,
                                              const string& hal_interface_name,
                                              const Arch& arch,
                                              set<string>* instances) {
   CHECK(instances) << "instances set should not be NULL.";
-  if (!manifest_hal) {
-    LOG(DEBUG) << "Does not find hal " << manifest_hal->name
+
+  const auto& manifest_instances = hal_manifest->getFqInstances(
+      hal_package_name, hal_version, hal_interface_name);
+
+  const auto& fq_instance_name =
+      toFQNameString(hal_package_name, hal_version, hal_interface_name);
+
+  if (manifest_instances.empty()) {
+    LOG(DEBUG) << "Does not find instances for " << fq_instance_name
                << " in manifest file";
     return false;
   }
-  if (!hal_interface_name.empty() &&
-      !manifest_hal->hasInterface(hal_interface_name)) {
-    LOG(DEBUG) << "ManifestHal " << manifest_hal->name
-               << " does not support interface " << hal_interface_name;
-    return false;
-  }
 
-  if (manifest_hal->transport() == Transport::PASSTHROUGH &&
-      !CheckPassthroughManifestHal(manifest_hal, arch)) {
-    LOG(DEBUG) << "ManfestHal " << manifest_hal->name
-               << " is passthrough and does not support arch "
-               << gArchStrings.at(static_cast<int>(arch));
+  bool testable = false;
+  for (const auto& manifest_instance : manifest_instances) {
+    if (manifest_instance.transport() == Transport::PASSTHROUGH &&
+        !CheckPassthroughManifestArch(manifest_instance.arch(), arch)) {
+      LOG(DEBUG) << "Manifest HAL " << fq_instance_name
+                 << " is passthrough and does not support arch " << arch;
+      continue;  // skip this instance
+    }
+    if (!hal_interface_name.empty()) {
+      instances->insert(manifest_instance.instance());
+    }
+    testable = true;
+  }
+  return testable;
+}
+
+bool VtsTestabilityChecker::CheckTestHalWithHwManager(
+    const string& hal_package_name, const Version& hal_version,
+    const string& hal_interface_name, set<string>* instances) {
+  CHECK(instances) << "instances set should not be NULL.";
+
+  string fqName =
+      toFQNameString(hal_package_name, hal_version, hal_interface_name);
+  bool registered = false;
+  hardware::Return<void> res;
+  if (!hal_interface_name.empty()) {
+    res = sm_->listByInterface(fqName, [&](const auto& registered_instances) {
+      for (const string& instance : registered_instances) {
+        registered = true;
+        instances->insert(instance);
+      }
+    });
+  } else {  // handle legacy data without interface info.
+    res = sm_->list([&](const auto& services) {
+      for (const string& service : services) {
+        if (service.find(fqName) == 0) {
+          registered = true;
+          break;
+        }
+      }
+    });
+  }
+  if (!res.isOk()) {
+    LOG(ERROR) << "failed to check services: " << res.description();
     return false;
   }
-  GetTestInstances(nullptr /*matrix_hal*/, manifest_hal, hal_interface_name,
-                   instances);
-  return true;
+  return registered;
 }
 
 }  // namespace vts
diff --git a/utils/native/testability_checker/VtsTestabilityChecker.h b/utils/native/testability_checker/VtsTestabilityChecker.h
index a3ff174..a7ebb83 100644
--- a/utils/native/testability_checker/VtsTestabilityChecker.h
+++ b/utils/native/testability_checker/VtsTestabilityChecker.h
@@ -20,9 +20,11 @@
 #include <set>
 
 #include <android-base/logging.h>
+#include <android/hidl/manager/1.0/IServiceManager.h>
 #include <vintf/CompatibilityMatrix.h>
 #include <vintf/HalManifest.h>
 
+using android::hidl::manager::V1_0::IServiceManager;
 using android::vintf::Arch;
 using android::vintf::CompatibilityMatrix;
 using android::vintf::HalManifest;
@@ -31,6 +33,7 @@
 using android::vintf::Version;
 using std::set;
 using std::string;
+using std::vector;
 
 namespace android {
 namespace vts {
@@ -42,10 +45,12 @@
  public:
   VtsTestabilityChecker(const CompatibilityMatrix* framework_comp_matrix,
                         const HalManifest* framework_hal_manifest,
-                        const HalManifest* device_hal_manifest)
+                        const HalManifest* device_hal_manifest,
+                        sp<IServiceManager> sm)
       : framework_comp_matrix_(framework_comp_matrix),
         framework_hal_manifest_(framework_hal_manifest),
-        device_hal_manifest_(device_hal_manifest) {
+        device_hal_manifest_(device_hal_manifest),
+        sm_(sm) {
     CHECK(framework_comp_matrix_) << "framework_comp_matrix null.";
     CHECK(framework_hal_manifest_) << "framework_hal_manifest null.";
     CHECK(device_hal_manifest_) << "device_hal_manifest null.";
@@ -72,7 +77,6 @@
                                     const Version& hal_version,
                                     const string& hal_interface_name,
                                     const Arch& arch, set<string>* instances);
-
  private:
   // Internal method to check the given hal against the framework compatibility
   // matrix and device manifest.
@@ -102,31 +106,50 @@
                                  const string& hal_interface_name,
                                  const Arch& arch, set<string>* instances);
 
-  // Helper method to check whether the mantifest_hal support the interface
-  // and arch (for passthrough hal). Store the corresponding
-  // instance names if supported.
-  bool CheckManifestHal(const ManifestHal* manifest_hal,
+  // Internal method to check whether the given hal is registered with
+  // hwservicemanager. Store the corresponding instance names if registered.
+  // This is used to check test hals that is not listed in manifest files.
+  // Note this could not check for passthrough hals.
+  bool CheckTestHalWithHwManager(const string& hal_package_name,
+                                 const Version& hal_version,
+                                 const string& hal_interface_name,
+                                 set<string>* instances);
+
+  // Helper method to check whether the hal_manifest support the
+  // package@version::interface and arch (for passthrough hal). Store the
+  // corresponding instance names if supported.
+  bool CheckManifestHal(const HalManifest* hal_manifest,
+                        const string& hal_package_name,
+                        const Version& hal_version,
                         const string& hal_interface_name, const Arch& arch,
                         set<string>* instances);
 
-  // Helper method to get the instance name for a given hal.
-  // If both matrix_hal and manifest_hal not null (i.e. the given hal exists
-  // in both compatibilty matrix and manifest), find the instance names in both
-  // hal and return the intersection.
-  // If either matrix_hal or manifest_hal not null, return the corresponding
-  // instance names
-  void GetTestInstances(const MatrixHal* matrix_hal,
-                        const ManifestHal* manifest_hal,
-                        const string& interface_name, set<string>* instances);
-
   // Helper method to check whether a passthrough hal support the given arch
   // (32 or 64).
-  bool CheckPassthroughManifestHal(const ManifestHal* manifest_hal,
-                                   const Arch& arch);
+  bool CheckPassthroughManifestArch(const Arch& manifest_arch,
+                                    const Arch& arch);
+
+  // Helper method to find matching instances from a list of
+  // manifest_instances.
+  vector<const vintf::ManifestInstance*> FindInstance(
+      const vector<vintf::ManifestInstance>& manifest_instances,
+      const vintf::MatrixInstance& matrix_instance);
+
+  // Helper method to find matching interfaces from a list of
+  // manifest_instances.
+  vector<const vintf::ManifestInstance*> FindInterface(
+      const vector<vintf::ManifestInstance>& manifest_instances,
+      const vintf::MatrixInstance& matrix_instance);
 
   const CompatibilityMatrix* framework_comp_matrix_;  // Do not own.
   const HalManifest* framework_hal_manifest_;         // Do not own.
   const HalManifest* device_hal_manifest_;            // Do not own.
+  sp<IServiceManager> sm_;
+
+  friend class
+      VtsTestabilityCheckerTest_CheckFrameworkCompatibleHalOptional_Test;
+  friend class
+      VtsTestabilityCheckerTest_CheckFrameworkCompatibleHalRequired_Test;
 };
 
 }  // namespace vts
diff --git a/utils/native/testability_checker/VtsTestabilityCheckerMain.cpp b/utils/native/testability_checker/VtsTestabilityCheckerMain.cpp
index b3e31d2..3bf0d15 100644
--- a/utils/native/testability_checker/VtsTestabilityCheckerMain.cpp
+++ b/utils/native/testability_checker/VtsTestabilityCheckerMain.cpp
@@ -18,10 +18,12 @@
 #include <iostream>
 
 #include <hidl-util/FQName.h>
+#include <hidl/ServiceManagement.h>
 #include <vintf/VintfObject.h>
 
 #include "VtsTestabilityChecker.h"
 
+using android::hidl::manager::V1_0::IServiceManager;
 using android::vintf::VintfObject;
 using std::cerr;
 using std::cout;
@@ -82,8 +84,8 @@
     return -1;
   }
 
-  android::FQName hal_fq_name = android::FQName(argv[optind]);
-  if (!hal_fq_name.isValid()) {
+  android::FQName hal_fq_name;
+  if (!android::FQName::parse(argv[optind], &hal_fq_name)) {
     cerr << "Invalid hal name: " << argv[optind] << endl;
     return -1;
   }
@@ -119,9 +121,16 @@
     return -1;
   }
 
+  android::sp<IServiceManager> sm =
+      ::android::hardware::defaultServiceManager();
+  if (sm == nullptr) {
+    cerr << "failed to get IServiceManager" << endl;
+    return -1;
+  }
+
   android::vts::VtsTestabilityChecker checker(framework_comp_matrix.get(),
                                               framework_hal_manifest.get(),
-                                              device_hal_manifest.get());
+                                              device_hal_manifest.get(), sm);
   set<string> instances;
   bool result = false;
   if (is_compliance_test) {
diff --git a/utils/native/testability_checker/VtsTestabilityCheckerTest.cpp b/utils/native/testability_checker/VtsTestabilityCheckerTest.cpp
index 2ce1a00..38822bc 100644
--- a/utils/native/testability_checker/VtsTestabilityCheckerTest.cpp
+++ b/utils/native/testability_checker/VtsTestabilityCheckerTest.cpp
@@ -22,6 +22,16 @@
 #include <vintf/HalManifest.h>
 #include <vintf/parse_xml.h>
 
+using namespace testing;
+
+using android::hidl::base::V1_0::IBase;
+using android::hidl::manager::V1_0::IServiceManager;
+using android::hidl::manager::V1_0::IServiceNotification;
+using android::hardware::hidl_array;
+using android::hardware::hidl_death_recipient;
+using android::hardware::hidl_handle;
+using android::hardware::hidl_string;
+using android::hardware::hidl_vec;
 using android::vintf::Arch;
 using android::vintf::CompatibilityMatrix;
 using android::vintf::HalManifest;
@@ -34,20 +44,51 @@
 using std::set;
 using std::string;
 
-extern const XmlConverter<HalManifest> &android::vintf::gHalManifestConverter;
-extern const XmlConverter<CompatibilityMatrix>
-    &android::vintf::gCompatibilityMatrixConverter;
-
 namespace android {
 namespace vts {
 
+class MockServiceManager : public IServiceManager {
+ public:
+  template <typename T>
+  using R = ::android::hardware::Return<T>;
+  using String = const hidl_string &;
+  ~MockServiceManager() = default;
+
+#define MOCK_METHOD_CB(name) \
+  MOCK_METHOD1(name, R<void>(IServiceManager::name##_cb))
+
+  MOCK_METHOD2(get, R<sp<IBase>>(String, String));
+  MOCK_METHOD2(add, R<bool>(String, const sp<IBase> &));
+  MOCK_METHOD2(getTransport, R<IServiceManager::Transport>(String, String));
+  MOCK_METHOD_CB(list);
+  MOCK_METHOD2(listByInterface, R<void>(String, listByInterface_cb));
+  MOCK_METHOD3(registerForNotifications,
+               R<bool>(String, String, const sp<IServiceNotification> &));
+  MOCK_METHOD_CB(debugDump);
+  MOCK_METHOD2(registerPassthroughClient, R<void>(String, String));
+  MOCK_METHOD_CB(interfaceChain);
+  MOCK_METHOD2(debug,
+               R<void>(const hidl_handle &, const hidl_vec<hidl_string> &));
+  MOCK_METHOD_CB(interfaceDescriptor);
+  MOCK_METHOD_CB(getHashChain);
+  MOCK_METHOD0(setHalInstrumentation, R<void>());
+  MOCK_METHOD2(linkToDeath,
+               R<bool>(const sp<hidl_death_recipient> &, uint64_t));
+  MOCK_METHOD0(ping, R<void>());
+  MOCK_METHOD_CB(getDebugInfo);
+  MOCK_METHOD0(notifySyspropsChanged, R<void>());
+  MOCK_METHOD1(unlinkToDeath, R<bool>(const sp<hidl_death_recipient> &));
+};
+
 class VtsTestabilityCheckerTest : public ::testing::Test {
  public:
   virtual void SetUp() override {
     test_cm_ = testFrameworkCompMatrix();
     test_fm_ = testFrameworkManfiest();
     test_vm_ = testDeviceManifest();
-    checker_.reset(new VtsTestabilityChecker(&test_cm_, &test_fm_, &test_vm_));
+    sm_ = new NiceMock<MockServiceManager>();
+    checker_.reset(
+        new VtsTestabilityChecker(&test_cm_, &test_fm_, &test_vm_, sm_));
   }
   virtual void TearDown() override {}
 
@@ -223,161 +264,180 @@
   CompatibilityMatrix test_cm_;
   HalManifest test_fm_;
   HalManifest test_vm_;
+  sp<MockServiceManager> sm_;
   std::unique_ptr<VtsTestabilityChecker> checker_;
 };
 
-TEST_F(VtsTestabilityCheckerTest, CheckComplianceTestForOptionalHal) {
+TEST_F(VtsTestabilityCheckerTest, CheckComplianceTest) {
   set<string> instances;
   // Check non-existent hal.
   EXPECT_FALSE(checker_->CheckHalForComplianceTest(
-      "non-existent", {1, 0}, "None", Arch::ARCH_EMPTY, &instances));
+      "nonexistent", {1, 0}, "None", Arch::ARCH_32, &instances));
   EXPECT_TRUE(instances.empty());
-
-  // Check hal not required by framework
+  // Check hal with unsupported version by vendor.
   EXPECT_FALSE(checker_->CheckHalForComplianceTest(
-      "android.hardware.renderscript", {1, 0}, "IRenderscript",
-      Arch::ARCH_EMPTY, &instances));
+      "android.hardware.nfc", {2, 0}, "INfc", Arch::ARCH_32, &instances));
   EXPECT_TRUE(instances.empty());
-
-  // Check hal with unsupported version.
+  // Check hal with unsupported interface by vendor.
   EXPECT_FALSE(checker_->CheckHalForComplianceTest(
-      "android.hardware.camera", {1, 0}, "ICamera", Arch::ARCH_EMPTY,
-      &instances));
+      "android.hardware.nfc", {1, 0}, "INfcTest", Arch::ARCH_32, &instances));
   EXPECT_TRUE(instances.empty());
-
-  // Check hal with non-existent interface.
+  // Check hal with unsupported arch by vendor.
   EXPECT_FALSE(checker_->CheckHalForComplianceTest(
-      "android.hardware.camera", {1, 2}, "None", Arch::ARCH_EMPTY, &instances));
+      "android.hardware.audio", {1, 0}, "IAudio", Arch::ARCH_64, &instances));
   EXPECT_TRUE(instances.empty());
-
-  // Check an optional hal not supported by vendor.
+  // Check hal claimed by framework but not supported by vendor (error case).
   EXPECT_FALSE(checker_->CheckHalForComplianceTest(
-      "android.hardware.radio", {1, 0}, "IRadio", Arch::ARCH_EMPTY,
-      &instances));
-  EXPECT_TRUE(instances.empty());
-
-  // Check an optional hal with version not supported by vendor.
-  EXPECT_FALSE(checker_->CheckHalForComplianceTest(
-      "android.hardware.camera", {4, 5}, "ICamera", Arch::ARCH_EMPTY,
-      &instances));
-  EXPECT_TRUE(instances.empty());
-
-  // Check an optional hal with interface not supported by vendor.
-  EXPECT_FALSE(checker_->CheckHalForComplianceTest(
-      "android.hardware.nfc", {4, 5}, "INfcTest", Arch::ARCH_EMPTY,
-      &instances));
-  EXPECT_TRUE(instances.empty());
-
-  // Check an option passthrough hal with unsupported arch.
-  EXPECT_FALSE(checker_->CheckHalForComplianceTest(
-      "android.hardware.audio", {2, 0}, "IAudio", Arch::ARCH_64, &instances));
-  EXPECT_TRUE(instances.empty());
-
-  // Check an optional hal supported by vendor but with no compatible
-  // instance.
-  EXPECT_TRUE(checker_->CheckHalForComplianceTest(
-      "android.hardware.camera", {2, 2}, "ICamera", Arch::ARCH_EMPTY,
-      &instances));
-  EXPECT_TRUE(instances.empty());
-
-  // Check an optional hal supported by vendor.
-  instances.clear();
-  EXPECT_TRUE(checker_->CheckHalForComplianceTest(
-      "android.hardware.camera", {2, 2}, "IBetterCamera", Arch::ARCH_EMPTY,
-      &instances));
-  EXPECT_THAT(instances, ::testing::ContainerEq(set<string>({"camera"})));
-
-  // Check an optional hal supported by vendor and framework.
-  instances.clear();
-  EXPECT_TRUE(checker_->CheckHalForComplianceTest(
-      "android.hardware.nfc", {1, 0}, "INfc", Arch::ARCH_EMPTY, &instances));
-  EXPECT_THAT(instances,
-              ::testing::ContainerEq(set<string>({"default", "fnfc"})));
-
-  // Check an optional passthrough hal supported by vendor.
-  instances.clear();
-  EXPECT_TRUE(checker_->CheckHalForComplianceTest(
-      "android.hardware.audio", {2, 0}, "IAudio", Arch::ARCH_32, &instances));
-  EXPECT_THAT(instances, ::testing::ContainerEq(set<string>({"default"})));
-
-  // Check an optional hal with empty interface (legacy input).
-  instances.clear();
-  EXPECT_TRUE(checker_->CheckHalForComplianceTest(
-      "android.hardware.camera", {2, 2}, "" /*interface*/, Arch::ARCH_EMPTY,
-      &instances));
-  EXPECT_TRUE(instances.empty());
-}
-
-TEST_F(VtsTestabilityCheckerTest, CheckComplianceTestForRequiredHal) {
-  set<string> instances;
-  // Check a required hal not supported by vendor.
-  EXPECT_TRUE(checker_->CheckHalForComplianceTest(
       "android.hardware.light", {2, 0}, "ILight", Arch::ARCH_32, &instances));
-  EXPECT_THAT(instances, ::testing::ContainerEq(set<string>({"default"})));
+  EXPECT_TRUE(instances.empty());
+  // Check hal interface claimed by framework but not supported by vendor (error
+  // case).
+  EXPECT_FALSE(checker_->CheckHalForComplianceTest(
+      "android.hardware.drm", {1, 0}, "IDrmTest", Arch::ARCH_32, &instances));
+  EXPECT_TRUE(instances.empty());
 
-  // Check a required hal with version not supported by vendor.
+  // Check hal claimed by framework and supported by vendor.
   instances.clear();
   EXPECT_TRUE(checker_->CheckHalForComplianceTest("android.hardware.vibrator",
-                                                  {2, 0}, "IVibrator",
+                                                  {1, 0}, "IVibrator",
                                                   Arch::ARCH_32, &instances));
-  EXPECT_THAT(instances, ::testing::ContainerEq(set<string>({"default"})));
+  EXPECT_THAT(instances, ContainerEq(set<string>({"default"})));
 
-  // Check a required hal with interface not supported by vendor.
+  // Check hal not claimed by framework but supported by vendor.
   instances.clear();
   EXPECT_TRUE(checker_->CheckHalForComplianceTest(
-      "android.hardware.drm", {1, 0}, "IDrmTest", Arch::ARCH_32, &instances));
-  EXPECT_THAT(instances, ::testing::ContainerEq(set<string>({"default"})));
+      "android.hardware.renderscript", {1, 0}, "IRenderscript", Arch::ARCH_32,
+      &instances));
+  EXPECT_THAT(instances, ContainerEq(set<string>({"default"})));
 
-  // Check a required hal supported by vendor.
+  // Check hal with version not claimed by framework by supported by vendor.
+  instances.clear();
+  EXPECT_TRUE(checker_->CheckHalForComplianceTest("android.hardware.vibrator",
+                                                  {1, 0}, "IVibrator",
+                                                  Arch::ARCH_32, &instances));
+  EXPECT_THAT(instances, ContainerEq(set<string>({"default"})));
+
+  // Check hal with instance not claimed by framework but supported by vendor.
   instances.clear();
   EXPECT_TRUE(checker_->CheckHalForComplianceTest(
-      "android.hardware.drm", {1, 0}, "IDrm", Arch::ARCH_32, &instances));
-  EXPECT_THAT(instances,
-              ::testing::ContainerEq(set<string>({"default", "drm"})));
+      "android.hardware.camera", {2, 2}, "ICamera", Arch::ARCH_32, &instances));
+  EXPECT_THAT(instances, ContainerEq(set<string>({"legacy/0"})));
+
+  // Check hal with additional vendor instance not claimed by framework.
+  instances.clear();
+  EXPECT_TRUE(checker_->CheckHalForComplianceTest("android.hardware.camera",
+                                                  {1, 2}, "IBetterCamera",
+                                                  Arch::ARCH_32, &instances));
+  EXPECT_THAT(instances, ContainerEq(set<string>({"default", "camera"})));
+
+  // Check hal supported by both framework and vendor.
+  instances.clear();
+  EXPECT_TRUE(checker_->CheckHalForComplianceTest(
+      "android.hardware.nfc", {1, 0}, "INfc", Arch::ARCH_32, &instances));
+  EXPECT_THAT(instances, ContainerEq(set<string>({"default", "fnfc"})));
+
+  // Check hal instance claimed by framework but not supported by vendor.
+  instances.clear();
+  EXPECT_TRUE(checker_->CheckHalForComplianceTest(
+      "android.hardware.drm", {2, 0}, "IDrm", Arch::ARCH_32, &instances));
+  EXPECT_THAT(instances, ContainerEq(set<string>({"default"})));
 
   // Check an optional hal with empty interface (legacy input).
   instances.clear();
   EXPECT_TRUE(checker_->CheckHalForComplianceTest(
-      "android.hardware.vibrator", {2, 0}, "" /*interface*/, Arch::ARCH_EMPTY,
+      "android.hardware.vibrator", {1, 0}, "" /*interface*/, Arch::ARCH_EMPTY,
       &instances));
   EXPECT_TRUE(instances.empty());
 }
 
 TEST_F(VtsTestabilityCheckerTest, CheckNonComplianceTest) {
   set<string> instances;
+  ON_CALL(*sm_, listByInterface(_, _))
+      .WillByDefault(
+          Invoke([](hidl_string str, IServiceManager::listByInterface_cb cb) {
+            if (str == "android.hardware.foo@1.0::IFoo") {
+              cb({"default", "footest"});
+            } else if (str == "android.hardware.nfc@3.0::INfc") {
+              cb({"default"});
+            } else if (str == "android.hardware.drm@2.0::IDrm") {
+              cb({"drmtest"});
+            }
+            return hardware::Void();
+          }));
+
+  ON_CALL(*sm_, list(_)).WillByDefault(Invoke([](IServiceManager::list_cb cb) {
+    cb({"android.hardware.foo@1.0::IFoo/default",
+        "android.hardware.foo@1.0::IFoo/footest",
+        "android.hardware.nfc@3.0::INfc/default",
+        "android.hardware.drm@2.0::IDrm/drmtest"});
+    return hardware::Void();
+  }));
+
   // Check non-existent hal.
   EXPECT_FALSE(checker_->CheckHalForNonComplianceTest(
       "non-existent", {1, 0}, "None", Arch::ARCH_32, &instances));
   EXPECT_TRUE(instances.empty());
-  // Check hal with unsupported version by vendor
+  // Check hal with unsupported version by vendor.
   EXPECT_FALSE(checker_->CheckHalForNonComplianceTest(
       "android.hardware.nfc", {2, 0}, "INfc", Arch::ARCH_32, &instances));
   EXPECT_TRUE(instances.empty());
-  // Check hal with unsupported interface by vendor
+  // Check hal with unsupported interface by vendor.
   EXPECT_FALSE(checker_->CheckHalForNonComplianceTest(
       "android.hardware.nfc", {1, 0}, "INfcTest", Arch::ARCH_32, &instances));
   EXPECT_TRUE(instances.empty());
+  // Check hal with unsupported arch by vendor.
+  EXPECT_FALSE(checker_->CheckHalForNonComplianceTest(
+      "android.hardware.audio", {1, 0}, "IAudio", Arch::ARCH_64, &instances));
+  EXPECT_TRUE(instances.empty());
+  // Check hal claimed by framework but not supported by vendor (error case).
+  EXPECT_FALSE(checker_->CheckHalForNonComplianceTest(
+      "android.hardware.light", {2, 0}, "ILight", Arch::ARCH_32, &instances));
+  EXPECT_TRUE(instances.empty());
+  // Check hal interface claimed by framework but not supported by vendor (error
+  // case).
+  EXPECT_FALSE(checker_->CheckHalForNonComplianceTest(
+      "android.hardware.drm", {1, 0}, "IDrmTest", Arch::ARCH_32, &instances));
+  EXPECT_TRUE(instances.empty());
 
-  // Check hal only supported by vendor
-  EXPECT_TRUE(checker_->CheckHalForNonComplianceTest(
-      "android.hardware.renderscript", {1, 0}, "IRenderscript", Arch::ARCH_32,
-      &instances));
-  EXPECT_THAT(instances, ::testing::ContainerEq(set<string>({"default"})));
-
-  // Check a required hal with version only supported by vendor.
+  // Check hal claimed by framework and supported by vendor.
   instances.clear();
   EXPECT_TRUE(checker_->CheckHalForNonComplianceTest(
       "android.hardware.vibrator", {1, 0}, "IVibrator", Arch::ARCH_32,
       &instances));
-  EXPECT_THAT(instances, ::testing::ContainerEq(set<string>({"default"})));
+  EXPECT_THAT(instances, ContainerEq(set<string>({"default"})));
 
-  // Check hal with additional vendor instance
+  // Check hal not claimed by framework but supported by vendor.
+  instances.clear();
+  EXPECT_TRUE(checker_->CheckHalForNonComplianceTest(
+      "android.hardware.renderscript", {1, 0}, "IRenderscript", Arch::ARCH_32,
+      &instances));
+  EXPECT_THAT(instances, ContainerEq(set<string>({"default"})));
+
+  // Check hal with version not claimed by framework by supported by vendor.
+  instances.clear();
+  EXPECT_TRUE(checker_->CheckHalForNonComplianceTest(
+      "android.hardware.vibrator", {1, 0}, "IVibrator", Arch::ARCH_32,
+      &instances));
+  EXPECT_THAT(instances, ContainerEq(set<string>({"default"})));
+
+  // Check hal with instance not claimed by framework but supported by vendor.
+  instances.clear();
+  EXPECT_TRUE(checker_->CheckHalForNonComplianceTest(
+      "android.hardware.camera", {2, 2}, "ICamera", Arch::ARCH_32, &instances));
+  EXPECT_THAT(instances, ContainerEq(set<string>({"legacy/0"})));
+
+  // Check hal with additional vendor instance not claimed by framework.
   instances.clear();
   EXPECT_TRUE(checker_->CheckHalForNonComplianceTest(
       "android.hardware.camera", {1, 2}, "IBetterCamera", Arch::ARCH_32,
       &instances));
-  EXPECT_THAT(instances,
-              ::testing::ContainerEq(set<string>({"default", "camera"})));
+  EXPECT_THAT(instances, ContainerEq(set<string>({"default", "camera"})));
+
+  // Check hal supported by both framework and vendor.
+  instances.clear();
+  EXPECT_TRUE(checker_->CheckHalForNonComplianceTest(
+      "android.hardware.nfc", {1, 0}, "INfc", Arch::ARCH_32, &instances));
+  EXPECT_THAT(instances, ContainerEq(set<string>({"default"})));
 
   // Check an optional hal with empty interface (legacy input).
   instances.clear();
@@ -385,6 +445,138 @@
       "android.hardware.vibrator", {1, 0}, "" /*interface*/, Arch::ARCH_EMPTY,
       &instances));
   EXPECT_TRUE(instances.empty());
+
+  // Check hal only registered with hwmanger.
+  instances.clear();
+  EXPECT_TRUE(checker_->CheckHalForNonComplianceTest(
+      "android.hardware.foo", {1, 0}, "IFoo", Arch::ARCH_EMPTY, &instances));
+  EXPECT_THAT(instances, ContainerEq(set<string>({"default", "footest"})));
+
+  // Check hal with version only registered with hwmanger.
+  instances.clear();
+  EXPECT_TRUE(checker_->CheckHalForNonComplianceTest(
+      "android.hardware.nfc", {3, 0}, "INfc", Arch::ARCH_EMPTY, &instances));
+  EXPECT_THAT(instances, ContainerEq(set<string>({"default"})));
+
+  // Check hal with additional instance registered with hwmanger.
+  instances.clear();
+  EXPECT_TRUE(checker_->CheckHalForNonComplianceTest(
+      "android.hardware.drm", {2, 0}, "IDrm", Arch::ARCH_EMPTY, &instances));
+  EXPECT_THAT(instances, ContainerEq(set<string>({"default", "drmtest"})));
+
+  instances.clear();
+  EXPECT_TRUE(checker_->CheckHalForNonComplianceTest(
+      "android.hardware.foo", {1, 0}, "", Arch::ARCH_EMPTY, &instances));
+  EXPECT_TRUE(instances.empty());
+}
+
+TEST_F(VtsTestabilityCheckerTest, CheckFrameworkCompatibleHalOptional) {
+  set<string> instances;
+  // Check non-existent hal.
+  EXPECT_FALSE(checker_->CheckFrameworkCompatibleHal(
+      "nonexistent", {1, 0}, "None", Arch::ARCH_EMPTY, &instances));
+  EXPECT_TRUE(instances.empty());
+
+  // Check hal not required by framework
+  EXPECT_FALSE(checker_->CheckFrameworkCompatibleHal(
+      "android.hardware.renderscript", {1, 0}, "IRenderscript",
+      Arch::ARCH_EMPTY, &instances));
+  EXPECT_TRUE(instances.empty());
+
+  // Check hal with unsupported version.
+  EXPECT_FALSE(checker_->CheckFrameworkCompatibleHal(
+      "android.hardware.camera", {1, 0}, "ICamera", Arch::ARCH_EMPTY,
+      &instances));
+  EXPECT_TRUE(instances.empty());
+
+  // Check hal with non-existent interface.
+  EXPECT_FALSE(checker_->CheckFrameworkCompatibleHal(
+      "android.hardware.camera", {1, 2}, "None", Arch::ARCH_EMPTY, &instances));
+  EXPECT_TRUE(instances.empty());
+
+  // Check an optional hal not supported by vendor.
+  EXPECT_FALSE(checker_->CheckFrameworkCompatibleHal(
+      "android.hardware.radio", {1, 0}, "IRadio", Arch::ARCH_EMPTY,
+      &instances));
+  EXPECT_TRUE(instances.empty());
+
+  // Check an optional hal with version not supported by vendor.
+  EXPECT_FALSE(checker_->CheckFrameworkCompatibleHal(
+      "android.hardware.camera", {4, 5}, "ICamera", Arch::ARCH_EMPTY,
+      &instances));
+  EXPECT_TRUE(instances.empty());
+
+  // Check an optional hal with interface not supported by vendor.
+  EXPECT_FALSE(checker_->CheckFrameworkCompatibleHal(
+      "android.hardware.nfc", {4, 5}, "INfcTest", Arch::ARCH_EMPTY,
+      &instances));
+  EXPECT_TRUE(instances.empty());
+
+  // Check an option passthrough hal with unsupported arch.
+  EXPECT_FALSE(checker_->CheckFrameworkCompatibleHal(
+      "android.hardware.audio", {2, 0}, "IAudio", Arch::ARCH_64, &instances));
+  EXPECT_TRUE(instances.empty());
+
+  // Check an optional hal supported by vendor but with no compatible
+  // instance.
+  EXPECT_TRUE(checker_->CheckFrameworkCompatibleHal(
+      "android.hardware.camera", {2, 2}, "ICamera", Arch::ARCH_EMPTY,
+      &instances));
+  EXPECT_TRUE(instances.empty());
+
+  // Check an optional hal supported by vendor.
+  instances.clear();
+  EXPECT_TRUE(checker_->CheckFrameworkCompatibleHal(
+      "android.hardware.camera", {2, 2}, "IBetterCamera", Arch::ARCH_EMPTY,
+      &instances));
+  EXPECT_THAT(instances, ContainerEq(set<string>({"camera"})));
+
+  // Check an optional passthrough hal supported by vendor.
+  instances.clear();
+  EXPECT_TRUE(checker_->CheckFrameworkCompatibleHal(
+      "android.hardware.audio", {2, 0}, "IAudio", Arch::ARCH_32, &instances));
+  EXPECT_THAT(instances, ContainerEq(set<string>({"default"})));
+
+  // Check an optional hal with empty interface (legacy input).
+  instances.clear();
+  EXPECT_TRUE(checker_->CheckFrameworkCompatibleHal(
+      "android.hardware.camera", {2, 2}, "" /*interface*/, Arch::ARCH_EMPTY,
+      &instances));
+  EXPECT_TRUE(instances.empty());
+}
+
+TEST_F(VtsTestabilityCheckerTest, CheckFrameworkCompatibleHalRequired) {
+  set<string> instances;
+  // Check a required hal not supported by vendor.
+  EXPECT_TRUE(checker_->CheckFrameworkCompatibleHal(
+      "android.hardware.light", {2, 0}, "ILight", Arch::ARCH_32, &instances));
+  EXPECT_THAT(instances, ContainerEq(set<string>({"default"})));
+
+  // Check a required hal with version not supported by vendor.
+  instances.clear();
+  EXPECT_TRUE(checker_->CheckFrameworkCompatibleHal("android.hardware.vibrator",
+                                                    {2, 0}, "IVibrator",
+                                                    Arch::ARCH_32, &instances));
+  EXPECT_THAT(instances, ContainerEq(set<string>({"default"})));
+
+  // Check a required hal with interface not supported by vendor.
+  instances.clear();
+  EXPECT_TRUE(checker_->CheckFrameworkCompatibleHal(
+      "android.hardware.drm", {1, 0}, "IDrmTest", Arch::ARCH_32, &instances));
+  EXPECT_THAT(instances, ContainerEq(set<string>({"default"})));
+
+  // Check a required hal supported by vendor.
+  instances.clear();
+  EXPECT_TRUE(checker_->CheckFrameworkCompatibleHal(
+      "android.hardware.drm", {1, 0}, "IDrm", Arch::ARCH_32, &instances));
+  EXPECT_THAT(instances, ContainerEq(set<string>({"default", "drm"})));
+
+  // Check an optional hal with empty interface (legacy input).
+  instances.clear();
+  EXPECT_TRUE(checker_->CheckFrameworkCompatibleHal(
+      "android.hardware.vibrator", {2, 0}, "" /*interface*/, Arch::ARCH_EMPTY,
+      &instances));
+  EXPECT_TRUE(instances.empty());
 }
 
 }  // namespace vts
diff --git a/utils/native/trace_processor/Android.bp b/utils/native/trace_processor/Android.bp
index f051f78..fc8d8d0 100644
--- a/utils/native/trace_processor/Android.bp
+++ b/utils/native/trace_processor/Android.bp
@@ -17,7 +17,7 @@
 cc_library_host_shared {
     name: "libvts_traceprocessor",
 
-    srcs: ["VtsTraceProcessor.cpp"],
+    srcs: ["VtsTraceProcessor.cpp", "VtsCoverageProcessor.cpp"],
 
     shared_libs: [
         "libbase",
@@ -26,6 +26,9 @@
         "libvts_multidevice_proto",
         "libvts_profiling_utils",
     ],
+    static_libs: [
+        "libjsoncpp",
+    ],
     cflags: [
         "-Wall",
         "-Werror",
diff --git a/utils/native/trace_processor/TraceProcessorMain.cpp b/utils/native/trace_processor/TraceProcessorMain.cpp
index 3b45fa0..8eb821c 100644
--- a/utils/native/trace_processor/TraceProcessorMain.cpp
+++ b/utils/native/trace_processor/TraceProcessorMain.cpp
@@ -13,61 +13,192 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+#include <getopt.h>
+
+#include "VtsCoverageProcessor.h"
 #include "VtsTraceProcessor.h"
-// Usage examples:
-//   To cleanup trace, <binary> --cleanup <trace file>/<trace file directory>
-//   To profile trace, <binary> --profiling <trace file>
-//   To dedup traces, <binary> --dedup <trace file directory>
-//   To select traces based on coverage data,
-//       <binary> --trace_selection <covreage file directory>
-//   To parse trace, <binary> --parse <trace file>
-// Cleanup trace is used to generate trace for replay test, it will replace the
-// old trace file with a new one of the same format (VtsProfilingRecord).
-//
-// Profile trace will calculate the latency of each API recorded in the trace
-// and print them out with the format api:latency. e.g.
-//   open:150231474
-//   write:842604
-//   coreInitialized:30466722
-//
-// Dedup trace is used to remove all duplicate traces under the given directory.
-// A trace is considered duplicated if there exists a trace that contains the
-// same API call sequence as the given trace and the input parameters for each
-// API call are all the same.
-//
-// Select trace is used to select a subset of trace files from a give trace set
-// based on their corresponding coverage data, the goal is to pick up the
-// minimal num of trace files that to maximize the total coverage.
-//
-// Parse trace is used to parse a binary trace file and print the text format of
-// the proto (used of for debug).
-int main(int argc, char* argv[]) {
-  android::vts::VtsTraceProcessor trace_processor;
-  if (argc == 3) {
-    if (!strcmp(argv[1], "--cleanup")) {
-      trace_processor.CleanupTraces(argv[2]);
-    } else if (!strcmp(argv[1], "--profiling")) {
-      trace_processor.ProcessTraceForLatencyProfiling(argv[2]);
-    } else if (!strcmp(argv[1], "--dedup")) {
-      trace_processor.DedupTraces(argv[2]);
-    } else if (!strcmp(argv[1], "--parse")) {
-      trace_processor.ParseTrace(argv[2]);
-    } else if (!strcmp(argv[1], "--convert")) {
-      trace_processor.ConvertTrace(argv[2]);
-    } else {
-      fprintf(stderr, "Invalid argument.\n");
-      return -1;
+
+static constexpr const char* kDefaultMode = "noop";
+static constexpr const char* kDefaultOutputFile =
+    "/temp/vts_trace_processor_output";
+
+using namespace std;
+
+enum mode_code {
+  // Trace related operations.
+  CLEANUP_TRACE,
+  CONVERT_TRACE,
+  DEDUPE_TRACE,
+  GET_TEST_LIST_FROM_TRACE,
+  PARSE_TRACE,
+  PROFILING_TRACE,
+  SELECT_TRACE,
+  // Coverage related operations.
+  COMPARE_COVERAGE,
+  GET_COVERGAGE_SUMMARY,
+  GET_SUBSET_COVERAGE,
+  MERGE_COVERAGE,
+};
+
+mode_code getModeCode(const std::string& str) {
+  if (str == "cleanup_trace") return mode_code::CLEANUP_TRACE;
+  if (str == "convert_trace") return mode_code::CONVERT_TRACE;
+  if (str == "dedup_trace") return mode_code::DEDUPE_TRACE;
+  if (str == "get_test_list_from_trace")
+    return mode_code::GET_TEST_LIST_FROM_TRACE;
+  if (str == "parse_trace") return mode_code::PARSE_TRACE;
+  if (str == "profiling_trace") return mode_code::PROFILING_TRACE;
+  if (str == "select_trace") return mode_code::SELECT_TRACE;
+  if (str == "compare_coverage") return mode_code::COMPARE_COVERAGE;
+  if (str == "get_coverage_summary") return mode_code::GET_COVERGAGE_SUMMARY;
+  if (str == "get_subset_coverage") return mode_code::GET_SUBSET_COVERAGE;
+  if (str == "merge_coverage") return mode_code::MERGE_COVERAGE;
+  printf("Unknown operation mode: %s\n", str.c_str());
+  exit(-1);
+}
+
+void ShowUsage() {
+  printf(
+      "Usage:   trace_processor [options] <input>\n"
+      "--mode:  The operation applied to the trace file.\n"
+      "\t cleanup_trace: cleanup trace for replay (remove duplicate events "
+      "etc.).\n"
+      "\t convert_trace: convert a text format trace file into a binary format "
+      "trace.\n"
+      "\t dedup_trace: remove duplicate trace file in the given directory. A "
+      "trace is considered duplicated if there exists a trace that contains "
+      "the "
+      "same API call sequence as the given trace and the input parameters for "
+      "each API call are all the same.\n"
+      "\t get_test_list_from_trace: parse all trace files under the given "
+      "directory and create a list of test modules for each hal@version that "
+      "access all apis covered by the whole test set. (i.e. such list should "
+      "be a subset of the whole test list that access the corresponding "
+      "hal@version)\n"
+      "\t parse_trace: parse the binary format trace file and print the text "
+      "format trace. \n"
+      "\t profiling_trace: parse the trace file to get the latency of each api "
+      "call.\n"
+      "\t select_trace: select a subset of trace files from a give trace set "
+      "based on their corresponding coverage data, the goal is to pick up the "
+      "minimal num of trace files that to maximize the total coverage.\n"
+      "\t compare_coverage: compare a coverage report with a reference "
+      "coverage report and print the additional file/lines covered.\n"
+      "\t get_coverage_summary: print the summary of the coverage file (e.g. "
+      "covered lines, total lines, coverage rate.) \n"
+      "\t get_subset_coverage: extract coverage measurement from coverage "
+      "report for files covered in the reference coverage report. It is used "
+      "in cases when we have an aggregated coverage report for all files but "
+      "are only interested in the coverage measurement of a subset of files in "
+      "that report.\n"
+      "\t merge_coverage: merge all coverage reports under the given directory "
+      "and generate a merged report.\n"
+      "--output: The file path to store the output results.\n"
+      "--help:   Show help\n");
+  exit(-1);
+}
+
+int main(int argc, char** argv) {
+  string mode = kDefaultMode;
+  string output = kDefaultOutputFile;
+  bool verbose_output = false;
+
+  android::vts::VtsCoverageProcessor coverage_processor;
+  android::vts::VtsTraceProcessor trace_processor(&coverage_processor);
+
+  const char* const short_opts = "hm:o:v:";
+  const option long_opts[] = {
+      {"help", no_argument, nullptr, 'h'},
+      {"mode", required_argument, nullptr, 'm'},
+      {"output", required_argument, nullptr, 'o'},
+      {"verbose", no_argument, nullptr, 'v'},
+      {nullptr, 0, nullptr, 0},
+  };
+
+  while (true) {
+    int opt = getopt_long(argc, argv, short_opts, long_opts, nullptr);
+    if (opt == -1) {
+      break;
     }
-  } else if (argc == 4) {
-    if (!strcmp(argv[1], "--trace_selection")) {
-      trace_processor.SelectTraces(argv[2], argv[3]);
-    } else {
-      fprintf(stderr, "Invalid argument.\n");
-      return -1;
+    switch (opt) {
+      case 'h':
+      case '?':
+        ShowUsage();
+        return 0;
+      case 'm': {
+        mode = string(optarg);
+        break;
+      }
+      case 'o': {
+        output = string(optarg);
+        break;
+      }
+      case 'v': {
+        verbose_output = true;
+        break;
+      }
+      default:
+        printf("getopt_long returned unexpected value: %d\n", opt);
+        return -1;
     }
-  } else {
-    fprintf(stderr, "Invalid argument.\n");
-    return -1;
+  }
+
+  if (optind == argc - 1) {
+    string trace_path = argv[optind];
+    switch (getModeCode(mode)) {
+      case mode_code::CLEANUP_TRACE:
+        trace_processor.CleanupTraces(trace_path);
+        break;
+      case mode_code::CONVERT_TRACE:
+        trace_processor.ConvertTrace(trace_path);
+        break;
+      case mode_code::DEDUPE_TRACE:
+        trace_processor.DedupTraces(trace_path);
+        break;
+      case mode_code::GET_TEST_LIST_FROM_TRACE:
+        trace_processor.GetTestListForHal(trace_path, output, verbose_output);
+        break;
+      case mode_code::PARSE_TRACE:
+        trace_processor.ParseTrace(trace_path);
+        break;
+      case mode_code::PROFILING_TRACE:
+        trace_processor.ProcessTraceForLatencyProfiling(trace_path);
+        break;
+      case mode_code::GET_COVERGAGE_SUMMARY:
+        coverage_processor.GetCoverageSummary(trace_path);
+        break;
+      case mode_code::MERGE_COVERAGE:
+        coverage_processor.MergeCoverage(trace_path, output);
+        break;
+      default:
+        printf("Invalid argument.");
+        return -1;
+    }
+  } else if (optind == argc - 2) {
+    switch (getModeCode(mode)) {
+      case mode_code::SELECT_TRACE: {
+        string coverage_dir = argv[optind];
+        string trace_dir = argv[optind + 1];
+        trace_processor.SelectTraces(coverage_dir, trace_dir);
+        break;
+      }
+      case mode_code::COMPARE_COVERAGE: {
+        string ref_coverage_path = argv[optind];
+        string coverage_path = argv[optind + 1];
+        coverage_processor.CompareCoverage(ref_coverage_path, coverage_path);
+        break;
+      }
+      case mode_code::GET_SUBSET_COVERAGE: {
+        string ref_coverage_path = argv[optind];
+        string coverage_path = argv[optind + 1];
+        coverage_processor.GetSubsetCoverage(ref_coverage_path, coverage_path,
+                                             output);
+        break;
+      }
+      default:
+        printf("Invalid argument.");
+        return -1;
+    }
   }
   return 0;
 }
diff --git a/utils/native/trace_processor/VtsCoverageProcessor.cpp b/utils/native/trace_processor/VtsCoverageProcessor.cpp
new file mode 100644
index 0000000..536ac0d
--- /dev/null
+++ b/utils/native/trace_processor/VtsCoverageProcessor.cpp
@@ -0,0 +1,274 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "VtsCoverageProcessor.h"
+
+#include <dirent.h>
+#include <fcntl.h>
+#include <fstream>
+#include <iostream>
+#include <map>
+#include <string>
+#include <vector>
+
+#include <google/protobuf/text_format.h>
+#include <test/vts/proto/VtsReportMessage.pb.h>
+
+using namespace std;
+using google::protobuf::TextFormat;
+
+namespace android {
+namespace vts {
+
+void VtsCoverageProcessor::ParseCoverageData(const string& coverage_file,
+                                             TestReportMessage* report_msg) {
+  ifstream in(coverage_file, std::ios::in);
+  string msg_str((istreambuf_iterator<char>(in)), istreambuf_iterator<char>());
+  if (!TextFormat::MergeFromString(msg_str, report_msg)) {
+    cerr << "Can't parse a given coverage report: " << msg_str << endl;
+    exit(-1);
+  }
+}
+
+void VtsCoverageProcessor::UpdateCoverageData(
+    const CoverageReportMessage& ref_msg,
+    CoverageReportMessage* msg_to_be_updated) {
+  if (ref_msg.file_path() == msg_to_be_updated->file_path()) {
+    for (int line = 0; line < ref_msg.line_coverage_vector_size(); line++) {
+      if (line < msg_to_be_updated->line_coverage_vector_size()) {
+        if (ref_msg.line_coverage_vector(line) > 0 &&
+            msg_to_be_updated->line_coverage_vector(line) > 0) {
+          msg_to_be_updated->set_line_coverage_vector(line, 0);
+          msg_to_be_updated->set_covered_line_count(
+              msg_to_be_updated->covered_line_count() - 1);
+        }
+      } else {
+        cout << "Reached the end of line_coverage_vector." << endl;
+        break;
+      }
+    }
+    // sanity check.
+    if (msg_to_be_updated->covered_line_count() < 0) {
+      cerr << __func__ << ": covered_line_count should not be negative."
+           << endl;
+      exit(-1);
+    }
+  }
+}
+
+void VtsCoverageProcessor::MergeCoverage(const string& coverage_file_dir,
+                                         const string& merged_coverage_file) {
+  DIR* coverage_dir = opendir(coverage_file_dir.c_str());
+  if (coverage_dir == 0) {
+    cerr << __func__ << ": " << coverage_file_dir << " does not exist." << endl;
+    return;
+  }
+  TestReportMessage merged_coverage_report;
+
+  struct dirent* file;
+  while ((file = readdir(coverage_dir)) != NULL) {
+    if (file->d_type == DT_REG) {
+      string coverage_file = coverage_file_dir;
+      if (coverage_file_dir.substr(coverage_file_dir.size() - 1) != "/") {
+        coverage_file += "/";
+      }
+      string coverage_file_base_name = file->d_name;
+      coverage_file += coverage_file_base_name;
+      TestReportMessage coverage_report;
+      ParseCoverageData(coverage_file, &coverage_report);
+
+      for (const auto cov : coverage_report.coverage()) {
+        bool seen_cov = false;
+        for (int i = 0; i < merged_coverage_report.coverage_size(); i++) {
+          if (merged_coverage_report.coverage(i).file_path() ==
+              cov.file_path()) {
+            MergeCoverageMsg(cov, merged_coverage_report.mutable_coverage(i));
+            seen_cov = true;
+            break;
+          }
+        }
+        if (!seen_cov) {
+          *merged_coverage_report.add_coverage() = cov;
+        }
+      }
+    }
+  }
+
+  PrintCoverageSummary(merged_coverage_report);
+  ofstream fout;
+  fout.open(merged_coverage_file);
+  fout << merged_coverage_report.DebugString();
+  fout.close();
+}
+
+void VtsCoverageProcessor::MergeCoverageMsg(
+    const CoverageReportMessage& ref_coverage_msg,
+    CoverageReportMessage* merged_coverage_msg) {
+  // sanity check.
+  if (ref_coverage_msg.file_path() != merged_coverage_msg->file_path()) {
+    cerr << "Trying to merge coverage data for different files." << endl;
+    exit(-1);
+  }
+  if (ref_coverage_msg.line_coverage_vector_size() !=
+      merged_coverage_msg->line_coverage_vector_size()) {
+    cerr << "Trying to merge coverage data with different lines."
+         << "ref_coverage_msg: " << ref_coverage_msg.DebugString()
+         << "merged_coverage_msg: " << merged_coverage_msg->DebugString()
+         << endl;
+    exit(-1);
+  }
+  for (int i = 0; i < ref_coverage_msg.line_coverage_vector_size(); i++) {
+    if (i > merged_coverage_msg->line_coverage_vector_size() - 1) {
+      cerr << "Reach the end of coverage vector" << endl;
+      break;
+    }
+    int ref_line_count = ref_coverage_msg.line_coverage_vector(i);
+    int merged_line_count = merged_coverage_msg->line_coverage_vector(i);
+    if (ref_line_count > 0) {
+      if (merged_line_count == 0) {
+        merged_coverage_msg->set_covered_line_count(
+            merged_coverage_msg->covered_line_count() + 1);
+      }
+      merged_coverage_msg->set_line_coverage_vector(
+          i, merged_line_count + ref_line_count);
+    }
+  }
+}
+
+void VtsCoverageProcessor::CompareCoverage(const string& ref_msg_file,
+                                           const string& new_msg_file) {
+  TestReportMessage ref_coverage_report;
+  TestReportMessage new_coverage_report;
+  ParseCoverageData(ref_msg_file, &ref_coverage_report);
+  ParseCoverageData(new_msg_file, &new_coverage_report);
+  map<string, vector<int>> new_coverage_map;
+
+  for (const auto& new_coverage : new_coverage_report.coverage()) {
+    bool seen_file = false;
+    for (const auto& ref_coverage : ref_coverage_report.coverage()) {
+      if (new_coverage.file_path() == ref_coverage.file_path()) {
+        int line = 0;
+        for (; line < new_coverage.line_coverage_vector_size(); line++) {
+          if (new_coverage.line_coverage_vector(line) > 0 &&
+              ref_coverage.line_coverage_vector(line) == 0) {
+            if (new_coverage_map.find(new_coverage.file_path()) !=
+                new_coverage_map.end()) {
+              new_coverage_map[new_coverage.file_path()].push_back(line);
+            } else {
+              new_coverage_map.insert(std::pair<string, vector<int>>(
+                  new_coverage.file_path(), vector<int>{line}));
+            }
+          }
+        }
+        seen_file = true;
+        break;
+      }
+    }
+    if (!seen_file) {
+      vector<int> new_line;
+      for (int line = 0; line < new_coverage.line_coverage_vector_size();
+           line++) {
+        if (new_coverage.line_coverage_vector(line) > 0) {
+          new_line.push_back(line);
+        }
+      }
+      new_coverage_map.insert(
+          std::pair<string, vector<int>>(new_coverage.file_path(), new_line));
+    }
+  }
+  for (auto it = new_coverage_map.begin(); it != new_coverage_map.end(); it++) {
+    cout << it->first << ": " << endl;
+    for (int covered_line : it->second) {
+      cout << covered_line << endl;
+    }
+  }
+}
+
+void VtsCoverageProcessor::GetSubsetCoverage(const string& ref_msg_file,
+                                             const string& full_msg_file,
+                                             const string& result_msg_file) {
+  TestReportMessage ref_coverage_report;
+  TestReportMessage full_coverage_report;
+  TestReportMessage result_coverage_report;
+  ParseCoverageData(ref_msg_file, &ref_coverage_report);
+  ParseCoverageData(full_msg_file, &full_coverage_report);
+
+  for (const auto& ref_coverage : ref_coverage_report.coverage()) {
+    bool seen_file = false;
+    for (const auto& coverage : full_coverage_report.coverage()) {
+      if (coverage.file_path() == ref_coverage.file_path()) {
+        *result_coverage_report.add_coverage() = coverage;
+        seen_file = true;
+        break;
+      }
+    }
+    if (!seen_file) {
+      cout << ": missing coverage for file " << ref_coverage.file_path()
+           << endl;
+      CoverageReportMessage* empty_coverage =
+          result_coverage_report.add_coverage();
+      *empty_coverage = ref_coverage;
+      for (int line = 0; line < empty_coverage->line_coverage_vector_size();
+           line++) {
+        if (empty_coverage->line_coverage_vector(line) > 0) {
+          empty_coverage->set_line_coverage_vector(line, 0);
+        }
+      }
+      empty_coverage->set_covered_line_count(0);
+    }
+  }
+  PrintCoverageSummary(result_coverage_report);
+  ofstream fout;
+  fout.open(result_msg_file);
+  fout << result_coverage_report.DebugString();
+  fout.close();
+}
+
+void VtsCoverageProcessor::GetCoverageSummary(const string& coverage_msg_file) {
+  TestReportMessage coverage_report;
+  ParseCoverageData(coverage_msg_file, &coverage_report);
+  PrintCoverageSummary(coverage_report);
+}
+
+void VtsCoverageProcessor::PrintCoverageSummary(
+    const TestReportMessage& coverage_report) {
+  long total_lines_covered = GetTotalCoverageLine(coverage_report);
+  long total_code_lines = GetTotalCodeLine(coverage_report);
+  double coverage_rate = (double)total_lines_covered / total_code_lines;
+  cout << "total lines covered: " << total_lines_covered << endl;
+  cout << "total lines: " << total_code_lines << endl;
+  cout << "coverage rate: " << coverage_rate << endl;
+}
+
+long VtsCoverageProcessor::GetTotalCoverageLine(
+    const TestReportMessage& msg) const {
+  long total_coverage_line = 0;
+  for (const auto coverage : msg.coverage()) {
+    total_coverage_line += coverage.covered_line_count();
+  }
+  return total_coverage_line;
+}
+
+long VtsCoverageProcessor::GetTotalCodeLine(
+    const TestReportMessage& msg) const {
+  long total_line = 0;
+  for (const auto coverage : msg.coverage()) {
+    total_line += coverage.total_line_count();
+  }
+  return total_line;
+}
+
+}  // namespace vts
+}  // namespace android
diff --git a/utils/native/trace_processor/VtsCoverageProcessor.h b/utils/native/trace_processor/VtsCoverageProcessor.h
new file mode 100644
index 0000000..e1eb70a
--- /dev/null
+++ b/utils/native/trace_processor/VtsCoverageProcessor.h
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef TOOLS_TRACE_PROCESSOR_VTSCOVERAGEPROCESSOR_H_
+#define TOOLS_TRACE_PROCESSOR_VTSCOVERAGEPROCESSOR_H_
+
+#include <android-base/macros.h>
+#include <test/vts/proto/VtsReportMessage.pb.h>
+
+namespace android {
+namespace vts {
+// A class used for processing coverage report data, such as parse the
+// coverage report file, merge multiple coverage reports, and compare
+// two coverage reports.
+class VtsCoverageProcessor {
+ public:
+  VtsCoverageProcessor(){};
+  virtual ~VtsCoverageProcessor(){};
+
+  // Merge the coverage files under coverage_file_dir and output the merged
+  // coverage data to merged_coverage_file.
+  void MergeCoverage(const std::string& coverage_file_dir,
+                     const std::string& merged_coverage_file);
+
+  // Compare coverage data contained in new_msg_file with ref_msg_file and
+  // print the additional file/lines covered by the new_msg_file.
+  void CompareCoverage(const std::string& ref_msg_file,
+                       const std::string& new_msg_file);
+
+  // Parse the given coverage_file into a coverage report.
+  void ParseCoverageData(const std::string& coverage_file,
+                         TestReportMessage* coverage_report);
+
+  // Updates msg_to_be_updated by removing all the covered lines in ref_msg
+  // and recalculates the count of covered lines accordingly.
+  void UpdateCoverageData(const CoverageReportMessage& ref_msg,
+                          CoverageReportMessage* msg_to_be_updated);
+
+  // Extract the files covered in ref_msg_file from full_msg_file and store
+  // the result in result_msg_file.
+  void GetSubsetCoverage(const std::string& ref_msg_file,
+                         const std::string& full_msg_file,
+                         const std::string& result_msg_file);
+
+  // Parse the coverage report and print the coverage summary.
+  void GetCoverageSummary(const std::string& coverage_msg_file);
+
+  // Calculate total coverage line in the given report message.
+  long GetTotalCoverageLine(const TestReportMessage& msg) const;
+  // Calculate total code line in the given report message.
+  long GetTotalCodeLine(const TestReportMessage& msg) const;
+
+ private:
+  // Internal method to merge the ref_coverage_msg into merged_covergae_msg.
+  void MergeCoverageMsg(const CoverageReportMessage& ref_coverage_msg,
+                        CoverageReportMessage* merged_covergae_msg);
+
+  // Help method to print the coverage summary.
+  void PrintCoverageSummary(const TestReportMessage& coverage_report);
+
+  DISALLOW_COPY_AND_ASSIGN(VtsCoverageProcessor);
+};
+
+}  // namespace vts
+}  // namespace android
+#endif  // TOOLS_TRACE_PROCESSOR_VTSCOVERAGEPROCESSOR_H_
diff --git a/utils/native/trace_processor/VtsTraceProcessor.cpp b/utils/native/trace_processor/VtsTraceProcessor.cpp
index aae654c..06f0c5d 100644
--- a/utils/native/trace_processor/VtsTraceProcessor.cpp
+++ b/utils/native/trace_processor/VtsTraceProcessor.cpp
@@ -17,9 +17,12 @@
 
 #include <dirent.h>
 #include <fcntl.h>
+#include <json/json.h>
 #include <fstream>
+#include <iomanip>
 #include <iostream>
 #include <map>
+#include <sstream>
 #include <string>
 #include <vector>
 
@@ -39,6 +42,7 @@
 
 bool VtsTraceProcessor::ParseBinaryTrace(const string& trace_file,
                                          bool ignore_timestamp, bool entry_only,
+                                         bool ignore_func_params,
                                          VtsProfilingMessage* profiling_msg) {
   int fd =
       open(trace_file.c_str(), O_RDONLY, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
@@ -53,6 +57,10 @@
     if (ignore_timestamp) {
       record.clear_timestamp();
     }
+    if (ignore_func_params) {
+      record.mutable_func_msg()->clear_arg();
+      record.mutable_func_msg()->clear_return_type_hidl();
+    }
     if (entry_only) {
       if (isEntryEvent(record.event())) {
         *profiling_msg->add_records() = record;
@@ -96,7 +104,7 @@
 
 void VtsTraceProcessor::ParseTrace(const string& trace_file) {
   VtsProfilingMessage profiling_msg;
-  if (!ParseBinaryTrace(trace_file, false, false, &profiling_msg)) {
+  if (!ParseBinaryTrace(trace_file, false, false, false, &profiling_msg)) {
     cerr << __func__ << ": Failed to parse trace file: " << trace_file << endl;
     return;
   }
@@ -140,7 +148,7 @@
 
 void VtsTraceProcessor::CleanupTraceFile(const string& trace_file) {
   VtsProfilingMessage profiling_msg;
-  if (!ParseBinaryTrace(trace_file, false, false, &profiling_msg)) {
+  if (!ParseBinaryTrace(trace_file, false, false, true, &profiling_msg)) {
     cerr << __func__ << ": Failed to parse trace file: " << trace_file << endl;
     return;
   }
@@ -240,15 +248,15 @@
 void VtsTraceProcessor::ProcessTraceForLatencyProfiling(
     const string& trace_file) {
   VtsProfilingMessage profiling_msg;
-  if (!ParseBinaryTrace(trace_file, false, false, &profiling_msg)) {
+  if (!ParseBinaryTrace(trace_file, false, false, true, &profiling_msg)) {
     cerr << __func__ << ": Failed to parse trace file: " << trace_file << endl;
     return;
   }
   if (!profiling_msg.records_size()) return;
-  if (profiling_msg.records(0).event()
-      == InstrumentationEventType::PASSTHROUGH_ENTRY
-      || profiling_msg.records(0).event()
-          == InstrumentationEventType::PASSTHROUGH_EXIT) {
+  if (profiling_msg.records(0).event() ==
+          InstrumentationEventType::PASSTHROUGH_ENTRY ||
+      profiling_msg.records(0).event() ==
+          InstrumentationEventType::PASSTHROUGH_EXIT) {
     cout << "hidl_hal_mode:passthrough" << endl;
   } else {
     cout << "hidl_hal_mode:binder" << endl;
@@ -295,14 +303,14 @@
 }
 
 void VtsTraceProcessor::DedupTraces(const string& trace_dir) {
-  DIR *dir = opendir(trace_dir.c_str());
+  DIR* dir = opendir(trace_dir.c_str());
   if (dir == 0) {
     cerr << trace_dir << "does not exist." << endl;
     return;
   }
   vector<VtsProfilingMessage> seen_msgs;
   vector<string> duplicate_trace_files;
-  struct dirent *file;
+  struct dirent* file;
   long total_trace_num = 0;
   long duplicat_trace_num = 0;
   while ((file = readdir(dir)) != NULL) {
@@ -314,7 +322,7 @@
       }
       trace_file += file->d_name;
       VtsProfilingMessage profiling_msg;
-      if (!ParseBinaryTrace(trace_file, true, true, &profiling_msg)) {
+      if (!ParseBinaryTrace(trace_file, true, true, false, &profiling_msg)) {
         cerr << "Failed to parse trace file: " << trace_file << endl;
         return;
       }
@@ -323,15 +331,15 @@
         duplicat_trace_num++;
         continue;
       }
-      auto found = find_if(
-          seen_msgs.begin(), seen_msgs.end(),
-          [&profiling_msg] (const VtsProfilingMessage& seen_msg) {
-            std::string str_profiling_msg;
-            std::string str_seen_msg;
-            profiling_msg.SerializeToString(&str_profiling_msg);
-            seen_msg.SerializeToString(&str_seen_msg);
-            return (str_profiling_msg == str_seen_msg);
-          });
+      auto found =
+          find_if(seen_msgs.begin(), seen_msgs.end(),
+                  [&profiling_msg](const VtsProfilingMessage& seen_msg) {
+                    std::string str_profiling_msg;
+                    std::string str_seen_msg;
+                    profiling_msg.SerializeToString(&str_profiling_msg);
+                    seen_msg.SerializeToString(&str_seen_msg);
+                    return (str_profiling_msg == str_seen_msg);
+                  });
       if (found == seen_msgs.end()) {
         seen_msgs.push_back(profiling_msg);
       } else {
@@ -350,43 +358,6 @@
        << float(duplicat_trace_num) / total_trace_num << endl;
 }
 
-bool VtsTraceProcessor::ParseCoverageData(const string& coverage_file,
-                                          TestReportMessage* report_msg) {
-  ifstream in(coverage_file, std::ios::in);
-  string msg_str((istreambuf_iterator<char>(in)), istreambuf_iterator<char>());
-  if (!TextFormat::MergeFromString(msg_str, report_msg)) {
-    cerr << __func__ << ": Can't parse a given record: " << msg_str << endl;
-    return false;
-  }
-  return true;
-}
-
-void VtsTraceProcessor::UpdateCoverageData(
-    const CoverageReportMessage& ref_msg,
-    CoverageReportMessage* msg_to_be_updated) {
-  if (ref_msg.file_path() == msg_to_be_updated->file_path()) {
-    for (int line = 0; line < ref_msg.line_coverage_vector_size(); line++) {
-      if (line < msg_to_be_updated->line_coverage_vector_size()) {
-        if (ref_msg.line_coverage_vector(line) > 0 &&
-            msg_to_be_updated->line_coverage_vector(line) > 0) {
-          msg_to_be_updated->set_line_coverage_vector(line, 0);
-          msg_to_be_updated->set_covered_line_count(
-              msg_to_be_updated->covered_line_count() - 1);
-        }
-      } else {
-        cout << "Reached the end of line_coverage_vector." << endl;
-        break;
-      }
-    }
-    // sanity check.
-    if (msg_to_be_updated->covered_line_count() < 0) {
-      cerr << __func__ << ": covered_line_count should not be negative."
-           << endl;
-      exit(-1);
-    }
-  }
-}
-
 void VtsTraceProcessor::SelectTraces(const string& coverage_file_dir,
                                      const string& trace_file_dir,
                                      TraceSelectionMetric metric) {
@@ -414,10 +385,7 @@
       string coverage_file_base_name = file->d_name;
       coverage_file += coverage_file_base_name;
       TestReportMessage coverage_msg;
-      if (!ParseCoverageData(coverage_file, &coverage_msg)) {
-        cerr << "Failed to parse coverage file: " << coverage_file << endl;
-        return;
-      }
+      coverage_processor_->ParseCoverageData(coverage_file, &coverage_msg);
 
       string trace_file = trace_file_dir;
       if (trace_file_dir.substr(trace_file_dir.size() - 1) != "/") {
@@ -440,11 +408,10 @@
       original_coverages[coverage_file] = coverage_info;
     }
   }
-  // Greedy algorithm that selects coverage files with the maximal code coverage
-  // delta at each iteration.
-  // Note: Not guaranteed to generate the optimal set.
-  // Example (*: covered, -: not_covered)
-  // line#\coverage_file   cov1 cov2 cov3
+  // Greedy algorithm that selects coverage files with the maximal code
+  // coverage delta at each iteration. Note: Not guaranteed to generate the
+  // optimal set. Example (*: covered, -: not_covered) line#\coverage_file
+  // cov1 cov2 cov3
   //          1              *   -    -
   //          2              *   *    -
   //          3              -   *    *
@@ -465,11 +432,13 @@
         for (int i = 0; i < cur_coverage_msg.coverage_size(); i++) {
           CoverageReportMessage* coverage_to_be_updated =
               cur_coverage_msg.mutable_coverage(i);
-          UpdateCoverageData(ref_coverage, coverage_to_be_updated);
+          coverage_processor_->UpdateCoverageData(ref_coverage,
+                                                  coverage_to_be_updated);
         }
       }
       it->second.coverage_msg = cur_coverage_msg;
-      long total_coverage_line = GetTotalCoverageLine(cur_coverage_msg);
+      long total_coverage_line =
+          coverage_processor_->GetTotalCoverageLine(cur_coverage_msg);
       long trace_file_size = it->second.trace_file_size;
       double coverage_size_ratio =
           (double)total_coverage_line / trace_file_size;
@@ -503,9 +472,10 @@
        ++it) {
     cout << "select trace file: " << it->second.trace_file_name << endl;
     TestReportMessage coverage_msg = it->second.coverage_msg;
-    total_lines_covered += GetTotalCoverageLine(coverage_msg);
-    if (GetTotalLine(coverage_msg) > total_lines) {
-      total_lines = GetTotalLine(coverage_msg);
+    total_lines_covered +=
+        coverage_processor_->GetTotalCoverageLine(coverage_msg);
+    if (coverage_processor_->GetTotalCodeLine(coverage_msg) > total_lines) {
+      total_lines = coverage_processor_->GetTotalCodeLine(coverage_msg);
     }
   }
   double coverage_rate = (double)total_lines_covered / total_lines;
@@ -514,22 +484,6 @@
   cout << "coverage rate: " << coverage_rate << endl;
 }
 
-long VtsTraceProcessor::GetTotalCoverageLine(const TestReportMessage& msg) {
-  long total_coverage_line = 0;
-  for (const auto coverage : msg.coverage()) {
-    total_coverage_line += coverage.covered_line_count();
-  }
-  return total_coverage_line;
-}
-
-long VtsTraceProcessor::GetTotalLine(const TestReportMessage& msg) {
-  long total_line = 0;
-  for (const auto coverage : msg.coverage()) {
-    total_line += coverage.total_line_count();
-  }
-  return total_line;
-}
-
 string VtsTraceProcessor::GetTraceFileName(const string& coverage_file_name) {
   std::size_t start = coverage_file_name.find("android.hardware");
   std::size_t end = coverage_file_name.find("vts.trace") + sizeof("vts.trace");
@@ -577,5 +531,158 @@
   return false;
 }
 
+void VtsTraceProcessor::GetTestListForHal(const string& test_trace_dir,
+                                          const string& output_file,
+                                          bool verbose_output) {
+  // Mapping from hal name to the list of test that access that hal.
+  map<string, vector<TraceSummary>> hal_trace_mapping;
+  GetHalTraceMapping(test_trace_dir, &hal_trace_mapping);
+
+  map<string, set<string>> test_list;
+  for (auto it = hal_trace_mapping.begin(); it != hal_trace_mapping.end();
+       it++) {
+    test_list[it->first] = set<string>();
+    vector<TraceSummary> trace_summaries = it->second;
+    vector<string> covered_apis;
+    for (auto summary : trace_summaries) {
+      for (auto const& api_stat_it : summary.api_stats) {
+        if (std::find(covered_apis.begin(), covered_apis.end(),
+                      api_stat_it.first) == covered_apis.end()) {
+          covered_apis.push_back(api_stat_it.first);
+          test_list[it->first].insert(summary.test_name);
+        }
+      }
+    }
+    for (auto api : covered_apis) {
+      cout << "covered api: " << api << endl;
+    }
+  }
+
+  ofstream fout;
+  fout.open(output_file);
+  for (auto it = hal_trace_mapping.begin(); it != hal_trace_mapping.end();
+       it++) {
+    if (verbose_output) {
+      Json::Value root(Json::objectValue);
+      root["Hal_name"] = Json::Value(it->first);
+      Json::Value arr(Json::arrayValue);
+      for (const TraceSummary& summary : it->second) {
+        Json::Value obj;
+        obj["Test_name"] = summary.test_name;
+        obj["Unique_Api_Count"] = std::to_string(summary.unique_api_count);
+        obj["Total_Api_Count"] = std::to_string(summary.total_api_count);
+        arr.append(obj);
+      }
+      root["Test_list"] = arr;
+      fout << root.toStyledString();
+    } else {
+      fout << it->first << ",";
+      for (auto test : test_list[it->first]) {
+        auto found = find_if(it->second.begin(), it->second.end(),
+                             [&](const TraceSummary& trace_summary) {
+                               return (trace_summary.test_name == test);
+                             });
+        if (found != it->second.end()) {
+          fout << found->test_name << "(" << found->unique_api_count << "/"
+               << found->total_api_count << "),";
+        }
+      }
+      fout << endl;
+    }
+  }
+  fout.close();
+}
+
+void VtsTraceProcessor::GetHalTraceMapping(
+    const string& test_trace_dir,
+    map<string, vector<TraceSummary>>* hal_trace_mapping) {
+  DIR* trace_dir = opendir(test_trace_dir.c_str());
+  if (trace_dir == 0) {
+    cerr << __func__ << ": " << trace_dir << " does not exist." << endl;
+    return;
+  }
+  vector<TraceSummary> trace_summaries;
+  struct dirent* test_dir;
+  while ((test_dir = readdir(trace_dir)) != NULL) {
+    if (test_dir->d_type == DT_DIR) {
+      string test_name = test_dir->d_name;
+      cout << "Processing test: " << test_name << endl;
+      string trace_file_dir_name = test_trace_dir;
+      if (test_trace_dir.substr(test_trace_dir.size() - 1) != "/") {
+        trace_file_dir_name += "/";
+      }
+      trace_file_dir_name += test_name;
+      DIR* trace_file_dir = opendir(trace_file_dir_name.c_str());
+      struct dirent* trace_file;
+      while ((trace_file = readdir(trace_file_dir)) != NULL) {
+        if (trace_file->d_type == DT_REG) {
+          string trace_file_name =
+              trace_file_dir_name + "/" + trace_file->d_name;
+          GetHalTraceSummary(trace_file_name, test_name, &trace_summaries);
+        }
+      }
+    }
+  }
+
+  // Generate hal_trace_mapping mappings.
+  for (const TraceSummary& trace_summary : trace_summaries) {
+    string test_name = trace_summary.test_name;
+    stringstream stream;
+    stream << fixed << setprecision(1) << trace_summary.version;
+    string hal_name = trace_summary.package + "@" + stream.str();
+    if (hal_trace_mapping->find(hal_name) != hal_trace_mapping->end()) {
+      (*hal_trace_mapping)[hal_name].push_back(trace_summary);
+    } else {
+      (*hal_trace_mapping)[hal_name] = vector<TraceSummary>{trace_summary};
+    }
+  }
+  for (auto it = hal_trace_mapping->begin(); it != hal_trace_mapping->end();
+       it++) {
+    // Sort the tests according to unique_api_count and break tie with
+    // total_api_count.
+    std::sort(it->second.begin(), it->second.end(),
+              [](const TraceSummary& lhs, const TraceSummary& rhs) {
+                return (lhs.unique_api_count > rhs.unique_api_count) ||
+                       (lhs.unique_api_count == rhs.unique_api_count &&
+                        lhs.total_api_count > rhs.total_api_count);
+              });
+  }
+}
+
+void VtsTraceProcessor::GetHalTraceSummary(
+    const string& trace_file, const string& test_name,
+    vector<TraceSummary>* trace_summaries) {
+  VtsProfilingMessage profiling_msg;
+  if (!ParseBinaryTrace(trace_file, true, true, true, &profiling_msg)) {
+    cerr << __func__ << ": Failed to parse trace file: " << trace_file << endl;
+    return;
+  }
+  for (const auto& record : profiling_msg.records()) {
+    string package = record.package();
+    float version = record.version();
+    string func_name = record.func_msg().name();
+    auto found = find_if(trace_summaries->begin(), trace_summaries->end(),
+                         [&](const TraceSummary& trace_summary) {
+                           return (test_name == trace_summary.test_name &&
+                                   package == trace_summary.package &&
+                                   version == trace_summary.version);
+                         });
+    if (found != trace_summaries->end()) {
+      found->total_api_count++;
+      if (found->api_stats.find(func_name) != found->api_stats.end()) {
+        found->api_stats[func_name]++;
+      } else {
+        found->unique_api_count++;
+        found->api_stats[func_name] = 1;
+      }
+    } else {
+      map<string, long> api_stats;
+      api_stats[func_name] = 1;
+      TraceSummary trace_summary(test_name, package, version, 1, 1, api_stats);
+      trace_summaries->push_back(trace_summary);
+    }
+  }
+}
+
 }  // namespace vts
 }  // namespace android
diff --git a/utils/native/trace_processor/VtsTraceProcessor.h b/utils/native/trace_processor/VtsTraceProcessor.h
index 1e8eddf..3d205e8 100644
--- a/utils/native/trace_processor/VtsTraceProcessor.h
+++ b/utils/native/trace_processor/VtsTraceProcessor.h
@@ -20,14 +20,16 @@
 #include <android-base/macros.h>
 #include <test/vts/proto/VtsProfilingMessage.pb.h>
 #include <test/vts/proto/VtsReportMessage.pb.h>
+#include "VtsCoverageProcessor.h"
 
 namespace android {
 namespace vts {
 
 class VtsTraceProcessor {
  public:
-  VtsTraceProcessor() {};
-  virtual ~VtsTraceProcessor() {};
+  VtsTraceProcessor(VtsCoverageProcessor* coverage_processor)
+      : coverage_processor_(coverage_processor){};
+  virtual ~VtsTraceProcessor(){};
 
   enum TraceSelectionMetric {
     MAX_COVERAGE,
@@ -60,12 +62,20 @@
   // Reads a text trace file, parse each trace event and convert it into a
   // binary trace file.
   void ConvertTrace(const std::string& trace_file);
+  // Parse all trace files under test_trace_dir and create a list of test
+  // modules for each hal@version that access all apis covered by the whole test
+  // set. (i.e. such list should be a subset of the whole test list that access
+  // the corresponding hal@version)
+  void GetTestListForHal(const std::string& test_trace_dir,
+                         const std::string& output_file,
+                         bool verbose_output = false);
 
  private:
   // Reads a binary trace file and parse each trace event into
   // VtsProfilingRecord.
   bool ParseBinaryTrace(const std::string& trace_file, bool ignore_timestamp,
-                        bool entry_only, VtsProfilingMessage* profiling_msg);
+                        bool entry_only, bool summary_only,
+                        VtsProfilingMessage* profiling_msg);
 
   // Reads a text trace file and parse each trace event into
   // VtsProfilingRecord.
@@ -90,8 +100,7 @@
   long GetTotalCoverageLine(const TestReportMessage& msg);
   // Helper method to calculate total code line in the given report message.
   long GetTotalLine(const TestReportMessage& msg);
-  // Helper method to extract the trace file name from the given coverage file
-  // name.
+  // Helper method to extract the trace file name from the given file name.
   std::string GetTraceFileName(const std::string& coverage_file_name);
   // Helper method to check whether the given event is an entry event.
   bool isEntryEvent(const InstrumentationEventType& event);
@@ -108,7 +117,48 @@
     long trace_file_size;
   };
 
-  DISALLOW_COPY_AND_ASSIGN (VtsTraceProcessor);
+  // Struct to store the trace summary data.
+  struct TraceSummary {
+    // Name of test module that generates the trace. e.g. CtsUsbTests.
+    std::string test_name;
+    // Hal package name. e.g. android.hardware.light
+    std::string package;
+    // Hal version e.g. 1.0
+    float version;
+    // Total number of API calls recorded in the trace.
+    long total_api_count;
+    // Total number of different APIs recorded in the trace.
+    long unique_api_count;
+    // Call statistics for each API: <API_name, number_called>
+    std::map<std::string, long> api_stats;
+
+    TraceSummary(std::string test_name, std::string package, float version,
+                 long total_api_count, long unique_api_count,
+                 std::map<std::string, long> api_stats)
+        : test_name(test_name),
+          package(package),
+          version(version),
+          total_api_count(total_api_count),
+          unique_api_count(unique_api_count),
+          api_stats(api_stats){};
+  };
+
+  // Internal method to parse all trace files under test_trace_dir and create
+  // the mapping from each hal@version to the list of test that access it.
+  void GetHalTraceMapping(
+      const std::string& test_trace_dir,
+      std::map<std::string, std::vector<TraceSummary>>* hal_trace_mapping);
+
+  // Internal method to parse a trace file and create the corresponding
+  // TraceSummary from it.
+  void GetHalTraceSummary(const std::string& trace_file,
+                          const std::string& test_name,
+                          std::vector<TraceSummary>* trace_summaries);
+
+  // A class to process coverage reports. Not owned.
+  VtsCoverageProcessor* coverage_processor_;
+
+  DISALLOW_COPY_AND_ASSIGN(VtsTraceProcessor);
 };
 
 }  // namespace vts
diff --git a/utils/python/Android.bp b/utils/python/Android.bp
new file mode 100644
index 0000000..7111b03
--- /dev/null
+++ b/utils/python/Android.bp
@@ -0,0 +1,22 @@
+// Copyright 2017 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+python_library_host {
+  name: "vts_runner_utils_python",
+  srcs: [
+    "**/*.py",
+  ],
+  defaults: ["py2_only"],
+}
+
diff --git a/utils/python/retry/__init__.py b/utils/python/android/__init__.py
similarity index 100%
copy from utils/python/retry/__init__.py
copy to utils/python/android/__init__.py
diff --git a/tools/vts-hc/Android.mk b/utils/python/android/api.py
similarity index 72%
copy from tools/vts-hc/Android.mk
copy to utils/python/android/api.py
index 7733142..f97a8cd 100644
--- a/tools/vts-hc/Android.mk
+++ b/utils/python/android/api.py
@@ -1,4 +1,5 @@
-# Copyright (C) 2017 The Android Open Source Project
+#
+# Copyright (C) 2018 The Android Open Source Project
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -12,11 +13,6 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := optional
-LOCAL_PREBUILT_EXECUTABLES := run
-include $(BUILD_HOST_PREBUILT)
-
+PLATFORM_API_LEVEL_O = 26
+PLATFORM_API_LEVEL_O_MR1 = 27
+PLATFORM_API_LEVEL_P = 28
diff --git a/utils/python/build/api/artifact_fetcher.py b/utils/python/build/api/artifact_fetcher.py
index c4769da..2224038 100644
--- a/utils/python/build/api/artifact_fetcher.py
+++ b/utils/python/build/api/artifact_fetcher.py
@@ -16,17 +16,16 @@
 """Class to fetch artifacts from internal build server.
 """
 
-import apiclient
+import googleapiclient
 import httplib2
 import io
 import json
 import logging
 import re
 import time
-from apiclient.discovery import build
+from googleapiclient.discovery import build
 from oauth2client import client as oauth2_client
 from oauth2client.service_account import ServiceAccountCredentials
-from vts.utils.python.retry import retry
 
 logger = logging.getLogger('artifact_fetcher')
 
@@ -48,15 +47,7 @@
         DEFAULT_ATTEMPT_ID: string, default attempt to request for the artifact.
         DEFAULT_CHUNK_SIZE: int, number of bytes to download at a time.
         RETRY_COUNT: int, max number of retries.
-        RETRY_BACKOFF_FACTOR: float, base of exponential determining sleep time.
-                              total_time = (backoff_factor^(attempt - 1))*sleep
-        RETRY_SLEEP_MULTIPLIER: float, multiplier for how long to sleep between
-                                attempts.
-        RETRY_HTTP_CODES: int array, HTTP codes for which a retry will be
-                          attempted.
-        RETRIABLE_AUTH_ERRORS: class tuple, list of error classes for which a
-                               retry will be attempted.
-
+        RETRY_DELAY_IN_SECS: int, time delays between retries in seconds.
     """
 
     API_NAME = "androidbuildinternal"
@@ -74,15 +65,7 @@
 
     # Defaults for retry.
     RETRY_COUNT = 5
-    RETRY_BACKOFF_FACTOR = 1.5
-    RETRY_SLEEP_MULTIPLIER = 1
-    RETRY_HTTP_CODES = [
-        500,  # Internal Server Error
-        502,  # Bad Gateway
-        503,  # Service Unavailable
-    ]
-
-    RETRIABLE_AUTH_ERRORS = (oauth2_client.AccessTokenRefreshError, )
+    RETRY_DELAY_IN_SECS = 3
 
     def __init__(self, oauth2_service_json):
         """Initialize.
@@ -93,15 +76,21 @@
         authToken = ServiceAccountCredentials.from_json_keyfile_name(
             oauth2_service_json, [self.SCOPE])
         http_auth = authToken.authorize(httplib2.Http())
-        self.service = retry.RetryException(
-            exc_retry=self.RETRIABLE_AUTH_ERRORS,
-            max_retry=self.RETRY_COUNT,
-            functor=build,
-            sleep=self.RETRY_SLEEP_MULTIPLIER,
-            backoff_factor=self.RETRY_BACKOFF_FACTOR,
-            serviceName=self.API_NAME,
-            version=self.API_VERSION,
-            http=http_auth)
+        for _ in xrange(self.RETRY_COUNT):
+            try:
+                self.service = build(
+                    serviceName=self.API_NAME,
+                    version=self.API_VERSION,
+                    http=http_auth)
+                break
+            except oauth2_client.AccessTokenRefreshError as e:
+                # The following HTTP code typically indicates transient errors:
+                #    500  (Internal Server Error)
+                #    502  (Bad Gateway)
+                #    503  (Service Unavailable)
+                logging.exception(e)
+                logging.info("Retrying to connect to %s", self.API_NAME)
+                time.sleep(self.RETRY_DELAY_IN_SECS)
 
     def DownloadArtifactToFile(self,
                                branch,
@@ -163,7 +152,7 @@
             else:
                 fh = io.BytesIO()
 
-            downloader = apiclient.http.MediaIoBaseDownload(
+            downloader = googleapiclient.http.MediaIoBaseDownload(
                 fh, api, chunksize=self.DEFAULT_CHUNK_SIZE)
             done = False
             while not done:
diff --git a/utils/python/common/cmd_utils.py b/utils/python/common/cmd_utils.py
index 8279994..d03e8c2 100644
--- a/utils/python/common/cmd_utils.py
+++ b/utils/python/common/cmd_utils.py
@@ -15,12 +15,55 @@
 
 import logging
 import subprocess
+import threading
+
+from vts.runners.host import utils
 
 STDOUT = 'stdouts'
 STDERR = 'stderrs'
 EXIT_CODE = 'return_codes'
 
 
+def _ExecuteOneShellCommandWithTimeout(cmd, timeout):
+    """Executes a command with timeout.
+
+    If the process times out, this function terminates it and continues
+    waiting.
+
+    Args:
+        proc: Popen object, the process to wait for.
+        timeout: float, timeout in seconds.
+
+    Returns:
+        tuple(string, string, int) which are stdout, stderr and return code.
+    """
+    # On Windows, subprocess.Popen(shell=True) starts two processes, cmd.exe
+    # and the command. The Popen object represents the cmd.exe process, so
+    # calling Popen.kill() does not terminate the command.
+    # This function uses process group to ensure command termination.
+    proc = utils.start_standing_subprocess(cmd)
+    result = []
+
+    def WaitForProcess():
+        out, err = proc.communicate()
+        result.append((out, err, proc.returncode))
+
+    wait_thread = threading.Thread(target=WaitForProcess)
+    wait_thread.daemon = True
+    wait_thread.start()
+    try:
+        wait_thread.join(timeout)
+    finally:
+        if proc.poll() is None:
+            utils.kill_process_group(proc)
+    wait_thread.join()
+
+    if len(result) != 1:
+        logging.error("Unexpected command result: %s", result)
+        return "", "", proc.returncode
+    return result[0]
+
+
 def RunCommand(command):
     """Runs a unix command and stashes the result.
 
@@ -40,19 +83,26 @@
     return proc.returncode
 
 
-def ExecuteOneShellCommand(cmd):
-    """Execute one shell command and return (stdout, stderr, exit_code).
+def ExecuteOneShellCommand(cmd, timeout=None):
+    """Executes one shell command and returns (stdout, stderr, exit_code).
 
     Args:
-        cmd: string, a shell command
+        cmd: string, a shell command.
+        timeout: float, timeout in seconds.
 
     Returns:
-        tuple(string, string, int), containing stdout, stderr, exit_code of the shell command
+        tuple(string, string, int), containing stdout, stderr, exit_code of
+        the shell command.
+        If timeout, exit_code is -15 on Unix; -1073741510 on Windows.
     """
-    p = subprocess.Popen(
-        str(cmd), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
-    stdout, stderr = p.communicate()
-    return (stdout, stderr, p.returncode)
+    if timeout is None:
+        p = subprocess.Popen(
+            str(cmd), shell=True,
+            stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+        stdout, stderr = p.communicate()
+        return (stdout, stderr, p.returncode)
+    else:
+        return _ExecuteOneShellCommandWithTimeout(str(cmd), timeout)
 
 
 def ExecuteShellCommand(cmd):
diff --git a/utils/python/common/filter_utils.py b/utils/python/common/filter_utils.py
index 750f7b2..df5121e 100644
--- a/utils/python/common/filter_utils.py
+++ b/utils/python/common/filter_utils.py
@@ -14,9 +14,11 @@
 # limitations under the License.
 #
 
+import copy
 import logging
 import re
 from sre_constants import error as regex_error
+import types
 
 from vts.runners.host import const
 from vts.utils.python.common import list_utils
@@ -36,8 +38,14 @@
 
     If a filter item contains bitness suffix, only test name with that tag
     will be included in output.
-    Otherwise, both 32bit and 64bit suffix will be paired to the test name
-    in output list.
+    Otherwise, 2 more item with 32bit and 64bit suffix will be added to the output list.
+
+    This method removes duplicated item while keeping item order before returning output.
+
+    Examples of input -> output are:
+        [a_32bit] -> [a_32bit]
+        [a] -> [a, a_32bit, a_64bit]
+        [a_32bit, a] -> [a_32bit, a, a_64bit]
 
     Args:
         input_list: list of string, the list to expand
@@ -55,6 +63,50 @@
     return list_utils.DeduplicateKeepOrder(result)
 
 
+def ExpandAppendix(input_list, appendix_list, filter_pattern):
+    '''Expand each item in input_list with appendix in the appendix_list
+
+    For each item in input_list, expand it to N items (N=size of appendix_list)
+    by attaching it with each appendix form appendix_list.
+    Note, for items end with bitness info (e.g 32bit/63bit/_32bit/_64bit),
+    attach the appendix before the bitness info. (This is to make sure the
+    bitness info is always at the end of each item since the system rely on this
+    assumption to check the bitness info)
+    There are two cases when an item will not be expanded: 1) it is a Regex
+    filter item and 2) it has the pattern described by filter_pattern.
+
+    Examples of input -> output are:
+        [a] [_default] -> [a_default]
+        [a, b] [_default] -> [a_default, b_default]
+        [a] [_default, _test] -> [a_default, a_test]
+        [a, b_32bit] [_default, _test]
+        -> [a_default, a_test, b_default_32bit, b_test_32bit]
+
+    Args:
+        input_list: list of string, the list to expand
+        appendix_list: list of string, the appendix to be append.
+        filter_pattern: string, a Regex pattern to filter out the items that
+                        should not expand.
+
+    Returns:
+        A list of string with expanded result.
+    '''
+    result = []
+    for item in input_list:
+        if IsRegexFilter(item) or re.compile(filter_pattern).match(item):
+            result.append(item)
+            continue
+        pos = len(item)
+        if (item.endswith(const.SUFFIX_32BIT) or
+                item.endswith(const.SUFFIX_64BIT)):
+            pos = len(item) - len(const.SUFFIX_32BIT)
+            if item[pos - 1] == "_":
+                pos = pos - 1
+        for appendix in appendix_list:
+            result.append(item[:pos] + appendix + item[pos:])
+    return result
+
+
 def SplitFilterList(input_list):
     '''Split filter items into exact and regex lists.
 
@@ -74,7 +126,7 @@
     exact = []
     regex = []
     for item in input_list:
-        if item.startswith(REGEX_PREFIX) and item.endswith(REGEX_SUFFIX):
+        if IsRegexFilter(item):
             regex_item = item[len(REGEX_PREFIX):-len(REGEX_SUFFIX)]
             try:
                 re.compile(regex_item)
@@ -134,6 +186,18 @@
     return False
 
 
+def IsRegexFilter(item):
+    '''Checks whether the given item is a regex filter.
+
+    Args:
+        item: string, given string
+
+    Returns:
+        bool: true if the given item is a regex filter.
+    '''
+    return item.startswith(REGEX_PREFIX) and item.endswith(REGEX_SUFFIX)
+
+
 class Filter(object):
     '''A class to hold test filter rules and filter test names.
 
@@ -168,6 +232,10 @@
                                             module name prefix matching
         module_name: string, test module name for auto module name prefix
                      matching
+        expand_bitness: bool, whether to append bitness to filter items.
+                        Default is False. When set to True, bitness will
+                        be added to test name for filtering process, but
+                        the original filter list will not be changed.
     '''
     include_filter_exact = []
     include_filter_regex = []
@@ -177,18 +245,16 @@
     def __init__(self,
                  include_filter=[],
                  exclude_filter=[],
-                 enable_regex=False,
+                 enable_regex=True,
                  exclude_over_include=None,
                  enable_negative_pattern=True,
                  enable_module_name_prefix_matching=False,
-                 module_name=None):
+                 module_name=None,
+                 expand_bitness=False):
         self.enable_regex = enable_regex
+        self.expand_bitness = expand_bitness
 
         self.enable_negative_pattern = enable_negative_pattern
-        if self.enable_negative_pattern:
-            include_filter, include_filter_negative = SplitNegativePattern(
-                include_filter)
-            exclude_filter.extend(include_filter_negative)
         self.include_filter = include_filter
         self.exclude_filter = exclude_filter
         if exclude_over_include is None:
@@ -197,6 +263,8 @@
         self.enable_module_name_prefix_matching = enable_module_name_prefix_matching
         self.module_name = module_name
 
+    # @Deprecated. Use expand_bitness parameter in construction method instead.
+    # This method will be removed after all legacy usage has been cleaned up
     def ExpandBitness(self):
         '''Expand bitness from filter.
 
@@ -206,14 +274,43 @@
         '''
         self.include_filter_exact = ExpandBitness(self.include_filter_exact)
         self.exclude_filter_exact = ExpandBitness(self.exclude_filter_exact)
+        self.expand_bitness = True
+
+    def IsIncludeFilterEmpty(self):
+        '''Check whether actual include filter is specified.
+
+        Since the input include filter may contain negative patterns,
+        checking self.include_filter is not always correct.
+
+        This method checks include_filter_exact and include_filter_regex.
+        '''
+        return not self.include_filter_exact and not self.include_filter_regex
+
+    def ExpandAppendix(self, appendix_list, filter_pattern):
+        '''Expand filter with appendix from appendix_list.
+
+        Reset both include_filter and exclude_filter by expanding the filters
+        with appendix in appendix_list.
+
+        Args:
+            appendix_list: list of string to be append to the filters.
+            filter_pattern: string, a Regex pattern to filter out the items that
+                            should not be expanded.
+        '''
+        self.include_filter = ExpandAppendix(self.include_filter,
+                                             appendix_list, filter_pattern)
+        self.exclude_filter = ExpandAppendix(self.exclude_filter,
+                                             appendix_list, filter_pattern)
 
     def Filter(self, item):
         '''Filter a given string using the internal filters.
 
         Rule:
-            If include_filter is empty, only exclude_filter is checked
-            for non-passing. Otherwise, only include_filter is checked
-            (include_filter overrides exclude_filter).
+            By default, include_filter overrides exclude_filter. This means:
+            If include_filter is empty, only exclude_filter is checked.
+            Otherwise, only include_filter is checked
+            If exclude_over_include is set to True, exclude filter will first
+            be checked.
 
         Args:
             item: string, the string for filter check
@@ -221,15 +318,19 @@
         Returns:
             bool. True if it passed the filter; False otherwise
         '''
-        if not self.exclude_over_include:
-            return (self.IsInIncludeFilter(item) if self.include_filter else
-                    not self.IsInExcludeFilter(item))
+        if self.exclude_over_include:
+            if self.IsInExcludeFilter(item):
+                return False
 
-        if self.IsInExcludeFilter(item):
-            return False
+            if not self.IsIncludeFilterEmpty():
+                return self.IsInIncludeFilter(item)
 
-        if self.include_filter:
-            return self.IsInIncludeFilter(item)
+            return True
+        else:
+            if not self.IsIncludeFilterEmpty():
+                return self.IsInIncludeFilter(item)
+
+            return not self.IsInExcludeFilter(item)
 
     def IsInIncludeFilter(self, item):
         '''Check if item is in include filter.
@@ -317,34 +418,131 @@
 
     @property
     def include_filter(self):
-        '''Getter method for include_filter'''
+        '''Getter method for include_filter.
+
+        Use this method to print include_filter only.
+
+        If the items needed to be added, use add_to_exclude_filter method.
+
+        E.g.
+            self.add_to_exclude_filter('pattern1')
+
+        If the filter needs to be modified without using add_to_exclude_filter,
+        call refresh_filter() after modification. Otherwise, the change will
+        not take effect.
+
+        E.g.
+            Get and modify filter:
+                filter = Filter()
+                filter.include_filter.append('pattern1')
+            Refresh the filter:
+                filter.refresh_filter()
+        '''
         return getattr(self, _INCLUDE_FILTER, [])
 
     @include_filter.setter
     def include_filter(self, include_filter):
         '''Setter method for include_filter'''
         setattr(self, _INCLUDE_FILTER, include_filter)
-        if self.enable_regex:
-            self.include_filter_exact, self.include_filter_regex = SplitFilterList(
-                include_filter)
-        else:
-            self.include_filter_exact = include_filter
+        self.refresh_filter()
 
     @property
     def exclude_filter(self):
-        '''Getter method for exclude_filter'''
+        '''Getter method for exclude_filter.
+
+        Use this method to print exclude_filter only.
+
+        If the items needed to be added, use add_to_exclude_filter method.
+
+        E.g.
+            self.add_to_exclude_filter('pattern1')
+
+        If the filter needs to be modified without using add_to_exclude_filter,
+        call refresh_filter() after modification. Otherwise, the change will
+        not take effect.
+
+        E.g.
+            Get and modify filter:
+                filter = Filter()
+                filter.exclude_filter.append('pattern1')
+            Refresh the filter:
+                filter.refresh_filter()
+        '''
         return getattr(self, _EXCLUDE_FILTER, [])
 
     @exclude_filter.setter
     def exclude_filter(self, exclude_filter):
         '''Setter method for exclude_filter'''
         setattr(self, _EXCLUDE_FILTER, exclude_filter)
+        self.refresh_filter()
+
+    def add_to_include_filter(self, pattern, auto_refresh=True):
+        '''Add an item to include_filter.
+
+        Args:
+            pattern: string or list of string. Item(s) to add
+            auto_refresh: bool, whether to automatically call refresh_filter().
+                          Default is True. Use False only if a large number of
+                          items are added one by one in a sequence call.
+                          In that case, call refresh_filter() at the end of the
+                          sequence.
+        '''
+        if not isinstance(pattern, types.ListType):
+            pattern = [pattern]
+
+        self.include_filter.extend(pattern)
+
+        if auto_refresh:
+            self.refresh_filter()
+
+    def add_to_exclude_filter(self, pattern, auto_refresh=True):
+        '''Add an item to exclude_filter.
+
+        Args:
+            pattern: string or list of string. Item(s) to add
+            auto_refresh: bool, whether to automatically call refresh_filter().
+                          Default is True. Use False only if a large number of
+                          items are added one by one in a sequence call.
+                          In that case, call refresh_filter() at the end of the
+                          sequence.
+        '''
+        if not isinstance(pattern, types.ListType):
+            pattern = [pattern]
+
+        self.exclude_filter.extend(pattern)
+
+        if auto_refresh:
+            self.refresh_filter()
+
+    def refresh_filter(self):
+        '''Process the filter patterns.
+
+        This method splits filter into exact and regex patterns.
+        Bitness will also be appended if expand_bitness is True.
+        '''
+        include_filter = copy.copy(self.include_filter)
+        exclude_filter = copy.copy(self.exclude_filter)
+
+        if self.enable_negative_pattern:
+            include_filter, include_filter_negative = SplitNegativePattern(
+                include_filter)
+            exclude_filter.extend(include_filter_negative)
+
         if self.enable_regex:
+            self.include_filter_exact, self.include_filter_regex = SplitFilterList(
+                include_filter)
             self.exclude_filter_exact, self.exclude_filter_regex = SplitFilterList(
                 exclude_filter)
         else:
+            self.include_filter_exact = include_filter
             self.exclude_filter_exact = exclude_filter
 
+        if self.expand_bitness:
+            self.include_filter_exact = ExpandBitness(
+                self.include_filter_exact)
+            self.exclude_filter_exact = ExpandBitness(
+                self.exclude_filter_exact)
+
     def __str__(self):
         return ('Filter:\nenable_regex: {enable_regex}\n'
                 'enable_negative_pattern: {enable_negative_pattern}\n'
@@ -356,7 +554,8 @@
                 'include_filter_exact: {include_filter_exact}\n'
                 'include_filter_regex: {include_filter_regex}\n'
                 'exclude_filter_exact: {exclude_filter_exact}\n'
-                'exclude_filter_regex: {exclude_filter_regex}'.format(
+                'exclude_filter_regex: {exclude_filter_regex}\n'
+                'expand_bitness: {expand_bitness}'.format(
                     enable_regex=self.enable_regex,
                     enable_negative_pattern=self.enable_negative_pattern,
                     enable_module_name_prefix_matching=
@@ -367,4 +566,5 @@
                     include_filter_exact=self.include_filter_exact,
                     include_filter_regex=self.include_filter_regex,
                     exclude_filter_exact=self.exclude_filter_exact,
-                    exclude_filter_regex=self.exclude_filter_regex))
+                    exclude_filter_regex=self.exclude_filter_regex,
+                    expand_bitness=self.expand_bitness))
diff --git a/utils/python/controllers/adb.py b/utils/python/controllers/adb.py
index aaa2936..faea786 100644
--- a/utils/python/controllers/adb.py
+++ b/utils/python/controllers/adb.py
@@ -22,6 +22,7 @@
 import time
 
 from vts.runners.host import const
+from vts.utils.python.common import cmd_utils
 
 
 class AdbError(Exception):
@@ -123,7 +124,7 @@
             self.adb_str = "adb"
         self.log = log
 
-    def _exec_cmd(self, cmd, no_except=False):
+    def _exec_cmd(self, cmd, no_except=False, timeout=None):
         """Executes adb commands in a new shell.
 
         This is specific to executing adb binary because stderr is not a good
@@ -132,6 +133,8 @@
         Args:
             cmd: string, the adb command to execute.
             no_except: bool, controls whether exception can be thrown.
+            timeout: float, timeout in seconds. If the command times out, the
+                     exit code is not 0.
 
         Returns:
             The output of the adb command run if the exit code is 0 and if
@@ -142,12 +145,7 @@
             AdbError if the adb command exit code is not 0 and exceptions are
             allowed.
         """
-        proc = subprocess.Popen(cmd,
-                                stdout=subprocess.PIPE,
-                                stderr=subprocess.PIPE,
-                                shell=True)
-        (out, err) = proc.communicate()
-        ret = proc.returncode
+        out, err, ret = cmd_utils.ExecuteOneShellCommand(cmd, timeout)
         logging.debug("cmd: %s, stdout: %s, stderr: %s, ret: %s", cmd, out,
                       err, ret)
         if no_except:
@@ -186,6 +184,6 @@
             clean_name = name.replace('_', '-')
             arg_str = ' '.join(str(elem) for elem in args)
             return self._exec_cmd(' '.join((self.adb_str, clean_name, arg_str)),
-                                  kwargs)
+                                  **kwargs)
 
         return adb_call
diff --git a/utils/python/controllers/android_device.py b/utils/python/controllers/android_device.py
index 1983bcc..fe797a9 100644
--- a/utils/python/controllers/android_device.py
+++ b/utils/python/controllers/android_device.py
@@ -24,6 +24,7 @@
 import time
 import traceback
 
+from vts.runners.host import asserts
 from vts.runners.host import errors
 from vts.runners.host import keys
 from vts.runners.host import logger as vts_logger
@@ -33,6 +34,7 @@
 from vts.utils.python.controllers import adb
 from vts.utils.python.controllers import event_dispatcher
 from vts.utils.python.controllers import fastboot
+from vts.utils.python.controllers import customflasher
 from vts.utils.python.controllers import sl4a_client
 from vts.utils.python.mirror import mirror_tracker
 
@@ -57,6 +59,10 @@
 # Max number of attempts that the client can make to connect to the agent
 MAX_AGENT_CONNECT_RETRIES = 10
 
+# The argument to fastboot getvar command to determine whether the device has
+# the slot for vbmeta.img
+_FASTBOOT_VAR_HAS_VBMETA = "has-slot:vbmeta"
+
 
 class AndroidDeviceError(signals.ControllerError):
     pass
@@ -339,6 +345,11 @@
         adb: An AdbProxy object used for interacting with the device via adb.
         fastboot: A FastbootProxy object used for interacting with the device
                   via fastboot.
+        customflasher: A CustomFlasherProxy object used for interacting with
+                       the device via user defined flashing binary.
+        enable_vts_agent: bool, whether VTS agent is used.
+        enable_sl4a: bool, whether SL4A is used.
+        enable_sl4a_ed: bool, whether SL4A Event Dispatcher is used.
         host_command_port: the host-side port for runner to agent sessions
                            (to send commands and receive responses).
         host_callback_port: the host-side port for agent to runner sessions
@@ -368,6 +379,7 @@
         self.vts_agent_process = None
         self.adb = adb.AdbProxy(serial)
         self.fastboot = fastboot.FastbootProxy(serial)
+        self.customflasher = customflasher.CustomFlasherProxy(serial)
         if not self.isBootloaderMode:
             self.rootAdb()
         self.host_command_port = None
@@ -386,6 +398,14 @@
     def __del__(self):
         self.cleanUp()
 
+    def SetCustomFlasherPath(self, customflasher_path):
+        """Sets customflasher path to use to flash the device.
+
+        Args:
+            customflasher_path: string, path to user-spcified flash binary.
+        """
+        self.customflasher.SetCustomBinaryPath(customflasher_path)
+
     def cleanUp(self):
         """Cleans up the AndroidDevice object and releases any resources it
         claimed.
@@ -399,6 +419,17 @@
             self.sl4a_host_port = None
 
     @property
+    def hasVbmetaSlot(self):
+        """True if the device has the slot for vbmeta."""
+        if not self.isBootloaderMode:
+            self.adb.reboot_bootloader()
+
+        out = self.fastboot.getvar(_FASTBOOT_VAR_HAS_VBMETA).strip()
+        if ("%s: yes" % _FASTBOOT_VAR_HAS_VBMETA) in out:
+            return True
+        return False
+
+    @property
     def isBootloaderMode(self):
         """True if the device is in bootloader mode."""
         return self.serial in list_fastboot_devices()
@@ -443,6 +474,58 @@
             return model
 
     @property
+    def first_api_level(self):
+        """Gets the API level that the device was initially launched with."""
+        return self.getProp("ro.product.first_api_level")
+
+    @property
+    def sdk_version(self):
+        """Gets the SDK version that the device is running with."""
+        return self.getProp("ro.build.version.sdk")
+
+    def getLaunchApiLevel(self, strict=True):
+        """Gets the API level that the device was initially launched with.
+
+        This method reads ro.product.first_api_level from the device. If the
+        value is 0, it then reads ro.build.version.sdk.
+
+        Args:
+            strict: A boolean, whether to fail the test if the property is
+                    not an integer or not defined.
+
+        Returns:
+            An integer, the API level.
+            0 if the property is not an integer or not defined.
+        """
+        level_str = self.first_api_level
+        try:
+            level = int(level_str)
+        except ValueError:
+            error_msg = "Cannot parse first_api_level: %s" % level_str
+            if strict:
+                asserts.fail(error_msg)
+            logging.error(error_msg)
+            return 0
+
+        if level != 0:
+            return level
+
+        level_str = self.sdk_version
+        try:
+            return int(level_str)
+        except ValueError:
+            error_msg = "Cannot parse version.sdk: %s" % level_str
+            if strict:
+                asserts.fail(error_msg)
+            logging.error(error_msg)
+            return 0
+
+    @property
+    def vndk_version(self):
+        """Gets the VNDK version that the vendor partition is using."""
+        return self.getProp("ro.vndk.version")
+
+    @property
     def cpu_abi(self):
         """CPU ABI (Application Binary Interface) of the device."""
         out = self.getProp("ro.product.cpu.abi")
@@ -630,7 +713,6 @@
         self.adb.bugreport(" > %s" % full_out_path)
         self.log.info("Bugreport for %s taken at %s", test_name, full_out_path)
 
-    @utils.timeout(15 * 60)
     def waitForBootCompletion(self, timeout=900):
         """Waits for Android framework to broadcast ACTION_BOOT_COMPLETED.
 
@@ -643,26 +725,35 @@
         """
         start = time.time()
         try:
-            self.adb.wait_for_device()
+            self.adb.wait_for_device(timeout=timeout)
         except adb.AdbError as e:
             # adb wait-for-device is not always possible in the lab
             logging.exception(e)
             return False
 
-        while not self.hasBooted():
+        while not self.isBootCompleted():
             if time.time() - start >= timeout:
                 logging.error("Timeout while waiting for boot completion.")
                 return False
-            time.sleep(3)
+            time.sleep(1)
 
         return True
 
+    # Deprecated. Use isBootCompleted instead
     def hasBooted(self):
         """Checks whether the device has booted.
 
         Returns:
             True if booted, False otherwise.
         """
+        return self.isBootCompleted()
+
+    def isBootCompleted(self):
+        """Checks whether the device has booted.
+
+        Returns:
+            True if booted, False otherwise.
+        """
         try:
             completed = self.getProp("sys.boot_completed")
             if completed == '1':
@@ -670,23 +761,117 @@
         except adb.AdbError:
             # adb shell calls may fail during certain period of booting
             # process, which is normal. Ignoring these errors.
+            pass
+
+        return False
+
+    def isFrameworkRunning(self, check_boot_completion=True):
+        """Checks whether Android framework is started.
+
+        This function will first check boot_completed prop. If boot_completed
+        is 0, then return False meaning framework not started.
+        Then this function will check whether system_server process is running.
+        If yes, then return True meaning framework is started.
+
+        The assumption here is if prop boot_completed is 0 then framework
+        is stopped.
+
+        There are still cases which can make this function return wrong
+        result. For example, boot_completed is set to 0 manually without
+        without stopping framework.
+
+        Args:
+            check_boot_completion: bool, whether to check boot completion
+                                   before checking framework status. This is an
+                                   important step for ensuring framework is
+                                   started. Under most circumstances this value
+                                   should be set to True.
+                                   Default True.
+
+        Returns:
+            True if started, False otherwise.
+        """
+        # First, check whether boot has completed.
+        if check_boot_completion and not self.isBootCompleted():
             return False
 
-    def start(self):
-        """Starts Android runtime and waits for ACTION_BOOT_COMPLETED."""
-        logging.info("starting Android Runtime")
-        self.adb.shell("start")
-        if self.waitForBootCompletion(60 * 2):
-            logging.info("Android Runtime started")
-        else:
-            logging.error("Failed to start Android Runtime.")
+        cmd = 'ps -g system | grep system_server'
+        res = self.adb.shell(cmd)
 
-    def stop(self):
-        """Stops Android runtime."""
-        logging.info("stopping Android Runtime")
+        return 'system_server' in res
+
+    def startFramework(self,
+                       wait_for_completion=True,
+                       wait_for_completion_timeout=120):
+        """Starts Android framework.
+
+        By default this function will wait for framework starting process to
+        finish before returning.
+
+        Args:
+            wait_for_completion: bool, whether to wait for framework to complete
+                                 starting. Default: True
+            wait_for_completion_timeout: timeout in seconds for waiting framework
+                                 to start. Default: 2 minutes
+
+        Returns:
+            bool, True if framework start success. False otherwise.
+        """
+        logging.info("starting Android framework")
+        self.adb.shell("start")
+
+        if wait_for_completion:
+            return self.waitForFrameworkStartComplete(wait_for_completion_timeout)
+
+        return True
+
+    def start(self):
+        """Starts Android framework and waits for ACTION_BOOT_COMPLETED.
+
+        Returns:
+            bool, True if framework start success. False otherwise.
+        """
+        return self.startFramework()
+
+    def stopFramework(self):
+        """Stops Android framework.
+
+        Method will block until stop is complete.
+        """
+        logging.info("stopping Android framework")
         self.adb.shell("stop")
         self.setProp("sys.boot_completed", 0)
-        logging.info("Android Runtime stopped")
+        logging.info("Android framework stopped")
+
+    def stop(self):
+        """Stops Android framework.
+
+        Method will block until stop is complete.
+        """
+        self.stopFramework()
+
+    def waitForFrameworkStartComplete(self, timeout_secs=120):
+        """Wait for Android framework to complete starting.
+
+        Args:
+            timeout_secs: int, seconds to wait for boot completion. Default is
+                          2 minutes.
+
+        Returns:
+            bool, True if framework is started. False otherwise or timeout
+        """
+        start = time.time()
+
+        # First, wait for boot completion and checks
+        self.waitForBootCompletion(timeout_secs)
+
+        while not self.isFrameworkRunning(check_boot_completion=False):
+            if time.time() - start >= timeout_secs:
+                logging.error("Timeout while waiting for framework to start.")
+                return False
+            time.sleep(1)
+
+        return True
 
     def setProp(self, name, value):
         """Calls setprop shell command.
@@ -775,15 +960,15 @@
         2. Start VtsAgent and create HalMirror unless disabled in config.
         3. If enabled in config, start sl4a service and create sl4a clients.
         """
-        enable_vts_agent = getattr(self, "enable_vts_agent", True)
-        enable_sl4a = getattr(self, "enable_sl4a", False)
-        enable_sl4a_ed = getattr(self, "enable_sl4a_ed", False)
+        self.enable_vts_agent = getattr(self, "enable_vts_agent", True)
+        self.enable_sl4a = getattr(self, "enable_sl4a", False)
+        self.enable_sl4a_ed = getattr(self, "enable_sl4a_ed", False)
         try:
             self.startAdbLogcat()
         except:
             self.log.exception("Failed to start adb logcat!")
             raise
-        if enable_vts_agent:
+        if self.enable_vts_agent:
             self.startVtsAgent()
             self.device_command_port = int(
                 self.adb.shell("cat /data/local/tmp/vts_tcp_server_port"))
@@ -792,14 +977,14 @@
                 self.host_command_port = adb.get_available_host_port()
             self.adb.tcp_forward(self.host_command_port,
                                  self.device_command_port)
-            self.hal = mirror_tracker.MirrorTracker(self.host_command_port,
-                                            self.host_callback_port, True)
+            self.hal = mirror_tracker.MirrorTracker(
+                self.host_command_port, self.host_callback_port, True)
             self.lib = mirror_tracker.MirrorTracker(self.host_command_port)
             self.shell = mirror_tracker.MirrorTracker(
                 host_command_port=self.host_command_port, adb=self.adb)
-        if enable_sl4a:
+        if self.enable_sl4a:
             try:
-                self.startSl4aClient(enable_sl4a_ed)
+                self.startSl4aClient(eself.enable_sl4a_ed)
             except Exception as e:
                 self.log.exception("Failed to start SL4A!")
                 self.log.exception(e)
@@ -810,9 +995,11 @@
         """
         if self.adb_logcat_process:
             self.stopAdbLogcat()
-        self._terminateAllSl4aSessions()
-        self.stopSl4a()
-        self.stopVtsAgent()
+        if getattr(self, "enable_sl4a", False):
+            self._terminateAllSl4aSessions()
+            self.stopSl4a()
+        if getattr(self, "enable_vts_agent", True):
+            self.stopVtsAgent()
         if self.hal:
             self.hal.CleanUp()
 
@@ -857,22 +1044,20 @@
         bits = ['64', '32'] if self.is64Bit else ['32']
         for bitness in bits:
             vts_agent_log_path = os.path.join(self.log_path,
-                                              "vts_agent_" + bitness + ".log")
-            cmd = (
-                'adb -s {s} shell LD_LIBRARY_PATH={path}/{bitness} '
-                '{path}/{bitness}/vts_hal_agent{bitness} '
-                '--hal_driver_path_32={path}/32/vts_hal_driver32 '
-                '--hal_driver_path_64={path}/64/vts_hal_driver64 '
-                '--spec_dir={path}/spec '
-                '--shell_driver_path_32={path}/32/vts_shell_driver32 '
-                '--shell_driver_path_64={path}/64/vts_shell_driver64 '
-                '-l {severity} >> {log} 2>&1'
-            ).format(
-                s=self.serial,
-                bitness=bitness,
-                path=DEFAULT_AGENT_BASE_DIR,
-                log=vts_agent_log_path,
-                severity=log_severity)
+                'vts_agent_%s_%s.log' % (bitness, self.serial))
+            cmd = ('adb -s {s} shell LD_LIBRARY_PATH={path}/{bitness} '
+                   '{path}/{bitness}/vts_hal_agent{bitness} '
+                   '--hal_driver_path_32={path}/32/vts_hal_driver32 '
+                   '--hal_driver_path_64={path}/64/vts_hal_driver64 '
+                   '--spec_dir={path}/spec '
+                   '--shell_driver_path_32={path}/32/vts_shell_driver32 '
+                   '--shell_driver_path_64={path}/64/vts_shell_driver64 '
+                   '-l {severity} >> {log} 2>&1').format(
+                       s=self.serial,
+                       bitness=bitness,
+                       path=DEFAULT_AGENT_BASE_DIR,
+                       log=vts_agent_log_path,
+                       severity=log_severity)
             try:
                 self.vts_agent_process = utils.start_standing_subprocess(
                     cmd, check_health_delay=1)
@@ -964,8 +1149,7 @@
     def stopSl4a(self):
         """Stops an SL4A apk on a target device."""
         try:
-            self.adb.shell(
-                "am force-stop %s" % SL4A_APK_NAME, ignore_status=True)
+            self.adb.shell("am force-stop %s" % SL4A_APK_NAME)
         except adb.AdbError as e:
             self.log.warn("Fail to stop package %s: %s", SL4A_APK_NAME, e)
 
@@ -985,9 +1169,7 @@
         """
         for cmd in ("ps -A", "ps"):
             try:
-                out = self.adb.shell(
-                    '%s | grep "S %s"' % (cmd, package_name),
-                    ignore_status=True)
+                out = self.adb.shell('%s | grep "S %s"' % (cmd, package_name))
                 if package_name not in out:
                     continue
                 try:
@@ -1235,3 +1417,7 @@
         """
         msg = "[AndroidDevice|%s] %s" % (self.extra["serial"], msg)
         return (msg, kwargs)
+
+    def warn(self, msg, *args, **kwargs):
+        """Function call warper for warn() to warning()."""
+        super(AndroidDeviceLoggerAdapter, self).warning(msg, *args, **kwargs)
diff --git a/utils/python/controllers/android_device_test.py b/utils/python/controllers/android_device_test.py
new file mode 100644
index 0000000..cbe6711
--- /dev/null
+++ b/utils/python/controllers/android_device_test.py
@@ -0,0 +1,58 @@
+#!/usr/bin/env python
+#
+# Copyright (C) 2018 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+import unittest
+import vts.utils.python.controllers.android_device as android_device
+
+
+class AndroidDeviceTest(unittest.TestCase):
+    '''Test methods inside android_device module.'''
+
+    def setUp(self):
+        """SetUp tasks"""
+        available_serials = android_device.list_adb_devices()
+        self.assertGreater(len(available_serials), 0, 'no device available.')
+        self.dut = android_device.AndroidDevice(available_serials[0])
+
+    def tearDown(self):
+        """TearDown tasks"""
+        pass
+
+    def testFrameworkStatusChange(self):
+        '''Test AndroidDevice class startRuntime related functions.'''
+        err_msg = 'Runtime status is wrong'
+        print('step 1 start runtime')
+        self.dut.start()
+
+        print('step 2 check runtime status')
+        self.assertTrue(self.dut.isFrameworkRunning(), err_msg)
+
+        print('step 3 stop runtime')
+        self.dut.stop()
+
+        print('step 4 check runtime status')
+        self.assertFalse(self.dut.isFrameworkRunning(), err_msg)
+
+        print('step 5 start runtime')
+        self.dut.start()
+
+        print('step 6 check runtime status')
+        self.assertTrue(self.dut.isFrameworkRunning(), err_msg)
+
+
+if __name__ == "__main__":
+    unittest.main()
diff --git a/utils/python/controllers/customflasher.py b/utils/python/controllers/customflasher.py
new file mode 100644
index 0000000..019c352
--- /dev/null
+++ b/utils/python/controllers/customflasher.py
@@ -0,0 +1,92 @@
+#
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+from vts.utils.python.common import cmd_utils
+
+
+class CustomFlasherError(Exception):
+    """Raised when there is an error in operations."""
+
+
+class CustomFlasherProxy():
+    """Proxy class for custom flasher tool.
+
+    Attributes:
+        serial: string containing devices serial.
+        customflasher_str: string containing path to the custom flasher.
+
+    For syntactic reasons, the '-' in  commands need to be replaced
+    with '_'. Can directly execute  commands on an object:
+    >> fb = Proxy(<serial>)
+    >> fb.devices() # will return the console output of "devices".
+    """
+
+    def __init__(self, serial=None):
+        """Initializes custom flasher proxy."""
+        self.serial = serial
+        self.customflasher_str = None
+
+    def SetCustomBinaryPath(self, customflasher_path=""):
+        """Sets path to flasher binary.
+
+        Args:
+            customflasher_path: string, path to custom flasher binary.
+        """
+        self.customflasher_str = customflasher_path
+
+    def ExecCustomFlasherCmd(self, name, arg_str):
+        """Passes joined shell command line to ExecuteOneShellCommand().
+
+        Args:
+            name: stirng, command to pass to custom flasher binary.
+            arg_str: string, contains additional argument(s).
+
+        Returns:
+            contents of stdout if command line returned without error;
+            stderr otherwise.
+        """
+        out, err, error_code = cmd_utils.ExecuteOneShellCommand(
+            ' '.join((self.customflasher_str, name, arg_str)))
+        if error_code:
+            return err
+        return out
+
+    def __getattr__(self, name):
+        """Passes invoked function's name to ExecCustomFlasherCmd().
+
+        Args:
+            name: stirng, 'name' of invoked method.
+
+        Returns:
+            output string from ExecCustomFlasherCmd().
+        """
+
+        def customflasher_call(*args):
+            """Joins *args into one string and passes the name and joined args
+            to ExecCustomFlasherCmd().
+
+            Replaces '_' characters in name, so if the invoked method name
+            was "__command", this function could pass "--command"
+            to custom flasher as a command.
+
+            Returns:
+                output string from ExecCustomFlasherCmd().
+            """
+            clean_name = name.replace('_', '-')
+            arg_str = ' '.join(str(elem) for elem in args)
+            return self.ExecCustomFlasherCmd(clean_name, arg_str)
+
+        return customflasher_call
diff --git a/utils/python/controllers/customflasher_test.py b/utils/python/controllers/customflasher_test.py
new file mode 100644
index 0000000..db3e789
--- /dev/null
+++ b/utils/python/controllers/customflasher_test.py
@@ -0,0 +1,40 @@
+#!/usr/bin/env python
+#
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+import unittest
+
+from vts.utils.python.controllers import customflasher
+
+
+class CustomFlasherTest(unittest.TestCase):
+    """Tests for CustomFlasher."""
+
+    def testExecCmd(self):
+        """Test for __getattr__().
+
+            Tests if CustomFlasherProxy gets binary path and commands
+            to the binary and passes to cmd_util
+            as one joined shell command line.
+        """
+        flashTool = customflasher.CustomFlasherProxy()
+        flashTool.SetCustomBinaryPath("myBinaryPath")
+        flashTool.__arg("aboutSomething")
+        flashTool.assert_called_with("myBinaryPath --arg", "aboutSomething")
+
+
+if __name__ == "__main__":
+    unittest.main()
diff --git a/utils/python/controllers/sl4a_client.py b/utils/python/controllers/sl4a_client.py
index f78086a..f4509af 100644
--- a/utils/python/controllers/sl4a_client.py
+++ b/utils/python/controllers/sl4a_client.py
@@ -101,7 +101,7 @@
         adb_proxy: adb.AdbProxy, The adb proxy to use for checking.
     """
     adb_proxy.shell(
-        'am force-stop com.googlecode.android_scripting', ignore_status=True)
+        'am force-stop com.googlecode.android_scripting', no_except=True)
 
 
 def is_sl4a_installed(adb_proxy):
diff --git a/utils/python/coverage/coverage_report.py b/utils/python/coverage/coverage_report.py
index e1f89ba..c176e5f 100644
--- a/utils/python/coverage/coverage_report.py
+++ b/utils/python/coverage/coverage_report.py
@@ -30,30 +30,37 @@
 from vts.utils.python.coverage import gcda_parser
 from vts.utils.python.coverage import gcno_parser
 
+GEN_TAG = "/gen/"
 
-def GenerateLineCoverageVector(src_file_name, gcno_file_summary):
-    """Returns a list of invocation counts for each line in the file.
+def GenerateLineCoverageVector(gcno_file_summary, exclude_paths, coverage_dict):
+    """Process the gcno_file_summary and update the coverage dictionary.
 
-    Parses the GCNO and GCDA file specified to calculate the number of times
-    each line in the source file specified by src_file_name was executed.
+    Create a coverage vector for each source file contained in gcno_file_summary
+    and update the corresponding item in coverage_dict.
 
     Args:
-        src_file_name: string, the source file name.
         gcno_file_summary: FileSummary object after gcno and gcda files have
                            been parsed.
-
-    Returns:
-        A list of non-negative integers or -1 representing the number of times
-        the i-th line was executed. -1 indicates a line that is not executable.
+        exclude_paths: a list of paths should be ignored in the coverage report.
+        coverage_dict: a dictionary for each source file and its corresponding
+                       coverage vector.
     """
-    src_lines_counts = []
-    covered_line_count = 0
     for ident in gcno_file_summary.functions:
         func = gcno_file_summary.functions[ident]
-        if not src_file_name == func.src_file_name:
-            logging.debug("GenerateLineCoverageVector: \"%s\" file is skipped \"%s\"",
-                          func.src_file_name, src_file_name)
+        file_name = func.src_file_name
+        if GEN_TAG in file_name:
+            logging.debug("Skip generated source file %s.", file_name)
             continue
+        skip_file = False
+        for path in exclude_paths:
+            if file_name.startswith(path):
+                skip_file = True
+                break
+        if skip_file:
+            logging.debug("Skip excluded source file %s.", file_name)
+            continue
+
+        src_lines_counts = coverage_dict[file_name] if file_name in coverage_dict else []
         for block in func.blocks:
             for line in block.lines:
                 if line > len(src_lines_counts):
@@ -62,11 +69,7 @@
                 if src_lines_counts[line - 1] < 0:
                     src_lines_counts[line - 1] = 0
                 src_lines_counts[line - 1] += block.count
-                if block.count > 0:
-                    covered_line_count += 1
-    logging.info("GenerateLineCoverageVector: file %s: %s lines covered",
-                 src_file_name, str(covered_line_count))
-    return src_lines_counts
+        coverage_dict[file_name] = src_lines_counts
 
 
 def GetCoverageStats(src_lines_counts):
diff --git a/utils/python/coverage/coverage_report_test.py b/utils/python/coverage/coverage_report_test.py
index 030a980..049d84c 100644
--- a/utils/python/coverage/coverage_report_test.py
+++ b/utils/python/coverage/coverage_report_test.py
@@ -48,13 +48,15 @@
         Runs GenerateLineCoverageVector on sample file and checks
         result.
         """
+        coverage_dict = dict()
+        exclude_paths = []
         src_lines_counts = coverage_report.GenerateLineCoverageVector(
-            'sample.c', self.gcno_summary)
-        expected = [-1, -1, -1, -1, 2, -1, -1, -1, -1, -1, 2,
+            self.gcno_summary, exclude_paths, coverage_dict)
+        expected = {'sample.c': [-1, -1, -1, -1, 2, -1, -1, -1, -1, -1, 2,
                     2, 2, -1, 2, -1, 2, 0, -1, 2, -1, -1, 2, 2, 502,
                     500, -1, -1, 2, -1, 2, -1, -1, -1, 2, -1,
-                    -1, -1, -1, 2, 2, 2]
-        self.assertEqual(src_lines_counts, expected)
+                    -1, -1, -1, 2, 2, 2]}
+        self.assertEqual(coverage_dict, expected)
 
 
 if __name__ == "__main__":
diff --git a/utils/python/coverage/coverage_utils.py b/utils/python/coverage/coverage_utils.py
index a95a10c..d4c3a13 100644
--- a/utils/python/coverage/coverage_utils.py
+++ b/utils/python/coverage/coverage_utils.py
@@ -12,17 +12,20 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
+import argparse
 import io
 import json
 import logging
 import os
 import shutil
+import sys
 import time
 import zipfile
 
 from vts.proto import VtsReportMessage_pb2 as ReportMsg
 from vts.runners.host import keys
 from vts.utils.python.archive import archive_parser
+from vts.utils.python.common import cmd_utils
 from vts.utils.python.controllers.adb import AdbError
 from vts.utils.python.coverage import coverage_report
 from vts.utils.python.coverage import gcda_parser
@@ -33,7 +36,7 @@
 
 FLUSH_PATH_VAR = "GCOV_PREFIX"  # environment variable for gcov flush path
 TARGET_COVERAGE_PATH = "/data/misc/trace/"  # location to flush coverage
-LOCAL_COVERAGE_PATH = "/tmp/vts-test-coverage"  # locatino to pull coverage to host
+LOCAL_COVERAGE_PATH = "/tmp/vts-test-coverage"  # location to pull coverage to host
 
 # Environment for test process
 COVERAGE_TEST_ENV = "GCOV_PREFIX_OVERRIDE=true GCOV_PREFIX=/data/misc/trace/self"
@@ -45,11 +48,13 @@
 MODULE_NAME = "module_name"
 NAME = "name"
 PATH = "path"
+GEN_TAG = "/gen/"
 
-_BUILD_INFO = 'BUILD_INFO'  # name of build info artifact
+_BUILD_INFO = "BUILD_INFO"  # name of build info artifact
 _GCOV_ZIP = "gcov.zip"  # name of gcov artifact zip
-_REPO_DICT = 'repo-dict'  # name of dictionary from project to revision in BUILD_INFO
+_REPO_DICT = "repo-dict"  # name of dictionary from project to revision in BUILD_INFO
 
+_CLEAN_TRACE_COMMAND = "rm -rf /data/misc/trace/*"
 _FLUSH_COMMAND = (
     "GCOV_PREFIX_OVERRIDE=true GCOV_PREFIX=/data/local/tmp/flusher "
     "/data/local/tmp/vts_coverage_configure flush")
@@ -77,10 +82,17 @@
     _OPTIONAL_PARAMS = [
         keys.ConfigKeys.IKEY_MODULES,
         keys.ConfigKeys.IKEY_OUTPUT_COVERAGE_REPORT,
-        keys.ConfigKeys.IKEY_GLOBAL_COVERAGE
+        keys.ConfigKeys.IKEY_GLOBAL_COVERAGE,
+        keys.ConfigKeys.IKEY_EXCLUDE_COVERAGE_PATH,
+        keys.ConfigKeys.IKEY_COVERAGE_REPORT_PATH,
     ]
 
-    def __init__(self, user_params, web=None, local_coverage_path=None):
+    _DEFAULT_EXCLUDE_PATHS = [
+        "bionic", "external/libcxx", "system/core", "system/libhidl",
+        "system/libfmq"
+    ]
+
+    def __init__(self, user_params, web=None):
         """Initializes the coverage feature.
 
         Args:
@@ -94,17 +106,18 @@
         self._device_resource_dict = {}
         self._hal_names = None
 
-        if local_coverage_path:
-            self.local_coverage_path = local_coverage_path
-        else:
-            timestamp_seconds = str(int(time.time() * 1000000))
-            self.local_coverage_path = os.path.join(LOCAL_COVERAGE_PATH,
-                                                    timestamp_seconds)
-            if os.path.exists(self.local_coverage_path):
-                logging.info("removing existing coverage path: %s",
-                             self.local_coverage_path)
-                shutil.rmtree(self.local_coverage_path)
-            os.makedirs(self.local_coverage_path)
+        timestamp_seconds = str(int(time.time() * 1000000))
+        self.local_coverage_path = os.path.join(LOCAL_COVERAGE_PATH,
+                                                timestamp_seconds)
+        if os.path.exists(self.local_coverage_path):
+            logging.info("removing existing coverage path: %s",
+                         self.local_coverage_path)
+            shutil.rmtree(self.local_coverage_path)
+        os.makedirs(self.local_coverage_path)
+
+        self._coverage_report_dir = getattr(
+            self, keys.ConfigKeys.IKEY_COVERAGE_REPORT_PATH, None)
+
         self._coverage_report_file_prefix = ""
 
         self.global_coverage = getattr(
@@ -113,51 +126,76 @@
             android_devices = getattr(self,
                                       keys.ConfigKeys.IKEY_ANDROID_DEVICE)
             if not isinstance(android_devices, list):
-                logging.warn('Android device information not available')
+                logging.warn("Android device information not available.")
                 self.enabled = False
             for device in android_devices:
                 serial = device.get(keys.ConfigKeys.IKEY_SERIAL)
-                coverage_resource_path = device.get(keys.ConfigKeys.IKEY_GCOV_RESOURCES_PATH)
-                if not serial or not coverage_resource_path:
-                    logging.warn('Missing coverage information in device: %s',
-                                 device)
+                coverage_resource_path = device.get(
+                    keys.ConfigKeys.IKEY_GCOV_RESOURCES_PATH)
+                if not serial:
+                    logging.error("Missing serial information in device: %s",
+                                  device)
                     continue
-                self._device_resource_dict[str(serial)] = str(coverage_resource_path)
+                if not coverage_resource_path:
+                    logging.error("Missing coverage resource path in device: %s",
+                                  device)
+                    continue
+                self._device_resource_dict[str(serial)] = str(
+                    coverage_resource_path)
         logging.info("Coverage enabled: %s", self.enabled)
 
-    def _ExtractSourceName(self, gcno_summary, file_name):
-        """Gets the source name from the GCNO summary object.
+    def _FindGcnoSummary(self, gcda_file_path, gcno_file_parsers):
+        """Find the corresponding gcno summary for given gcda file.
 
-        Gets the original source file name from the FileSummary object describing
-        a gcno file using the base filename of the gcno/gcda file.
+        Identify the corresponding gcno summary for given gcda file from a list
+        of gcno files with the same checksum as the gcda file by matching
+        the the gcda file path.
+        Note: if none of the gcno summary contains the source file same as the
+        given gcda_file_path (e.g. when the corresponding source file does not
+        contain any executable codes), just return the last gcno summary in the
+        list as a fall back solution.
 
         Args:
-            gcno_summary: a FileSummary object describing a gcno file
-            file_name: the base filename (without extensions) of the gcno or gcda file
+            gcda_file_path: the path of gcda file (without extensions).
+            gcno_file_parsers: a list of gcno file parser that has the same
+                               chechsum.
 
         Returns:
-            The relative path to the original source file corresponding to the
-            provided gcno summary. The path is relative to the root of the build.
+            The corresponding gcno summary for given gcda file.
         """
-        src_file_path = None
-        for key in gcno_summary.functions:
-            src_file_path = gcno_summary.functions[key].src_file_name
-            src_parts = src_file_path.rsplit(".", 1)
-            src_file_name = src_parts[0]
-            src_extension = src_parts[1] if len(src_parts) > 1 else None
-            if src_extension not in ["c", "cpp", "cc"]:
-                logging.debug("Found unsupported file type: %s", src_file_path)
-                continue
-            if src_file_name.endswith(file_name):
-                logging.info("Coverage source file: %s", src_file_path)
-                return src_file_path
-        return None
+        gcno_summary = None
+        # For each gcno files with the matched checksum, compare the
+        # gcda_file_path to find the corresponding gcno summary.
+        for gcno_file_parser in gcno_file_parsers:
+            try:
+                gcno_summary = gcno_file_parser.Parse()
+            except FileFormatError:
+                logging.error("Error parsing gcno for gcda %s", gcda_file_path)
+                break
+            legacy_build = "soong/.intermediates" not in gcda_file_path
+            for key in gcno_summary.functions:
+                src_file_path = gcno_summary.functions[key].src_file_name
+                src_file_name = src_file_path.rsplit(".", 1)[0]
+                # If build with legacy compile system, compare only the base
+                # source file name. Otherwise, compare the full source file name
+                # (with path info).
+                if legacy_build:
+                    base_src_file_name = os.path.basename(src_file_name)
+                    if gcda_file_path.endswith(base_src_file_name):
+                        return gcno_summary
+                else:
+                    if gcda_file_path.endswith(src_file_name):
+                        return gcno_summary
+        # If no gcno file matched with the gcda_file_name, return the last
+        # gcno summary as a fall back solution.
+        return gcno_summary
 
     def _GetChecksumGcnoDict(self, cov_zip):
         """Generates a dictionary from gcno checksum to GCNOParser object.
 
         Processes the gcnodir files in the zip file to produce a mapping from gcno
         checksum to the GCNOParser object wrapping the gcno content.
+        Note there might be multiple gcno files corresponds to the same checksum.
 
         Args:
             cov_zip: the zip file containing gcnodir files from the device build
@@ -181,15 +219,18 @@
                 continue
 
             for gcno_file_path in archive.files:
-                file_name_path = gcno_file_path.rsplit(".", 1)[0]
-                file_name = os.path.basename(file_name_path)
                 gcno_stream = io.BytesIO(archive.files[gcno_file_path])
                 gcno_file_parser = gcno_parser.GCNOParser(gcno_stream)
-                checksum_gcno_dict[
-                    gcno_file_parser.checksum] = gcno_file_parser
+                if gcno_file_parser.checksum in checksum_gcno_dict:
+                    checksum_gcno_dict[gcno_file_parser.checksum].append(
+                        gcno_file_parser)
+                else:
+                    checksum_gcno_dict[gcno_file_parser.checksum] = [
+                        gcno_file_parser
+                    ]
         return checksum_gcno_dict
 
-    def _ClearTargetGcov(self, dut, path_suffix=None):
+    def _ClearTargetGcov(self, dut, serial, path_suffix=None):
         """Removes gcov data from the device.
 
         Finds and removes all gcda files relative to TARGET_COVERAGE_PATH.
@@ -200,12 +241,9 @@
         path = TARGET_COVERAGE_PATH
         if path_suffix:
             path = path_utils.JoinTargetPath(path, path_suffix)
-        try:
-            dut.adb.shell("rm -rf %s/*" % TARGET_COVERAGE_PATH)
-        except AdbError as e:
-            logging.warn('Gcov cleanup error: \"%s\"', e)
+        self._ExecuteOneAdbShellCommand(dut, serial, _CLEAN_TRACE_COMMAND)
 
-    def InitializeDeviceCoverage(self, dut):
+    def InitializeDeviceCoverage(self, dut=None, serial=None):
         """Initializes the device for coverage before tests run.
 
         Flushes, then finds and removes all gcda files under
@@ -214,14 +252,11 @@
         Args:
             dut: the device under test.
         """
-        try:
-            dut.adb.shell(_FLUSH_COMMAND)
-        except AdbError as e:
-            logging.warn('Command failed: \"%s\"', _FLUSH_COMMAND)
+        self._ExecuteOneAdbShellCommand(dut, serial, _FLUSH_COMMAND)
         logging.info("Removing existing gcda files.")
-        self._ClearTargetGcov(dut)
+        self._ClearTargetGcov(dut, serial)
 
-    def GetGcdaDict(self, dut):
+    def _GetGcdaDict(self, dut, serial):
         """Retrieves GCDA files from device and creates a dictionary of files.
 
         Find all GCDA files on the target device, copy them to the host using
@@ -237,10 +272,8 @@
         logging.info("Creating gcda dictionary")
         gcda_dict = {}
         logging.info("Storing gcda tmp files to: %s", self.local_coverage_path)
-        try:
-            dut.adb.shell(_FLUSH_COMMAND)
-        except AdbError as e:
-            logging.warn('Command failed: \"%s\"', _FLUSH_COMMAND)
+
+        self._ExecuteOneAdbShellCommand(dut, serial, _FLUSH_COMMAND)
 
         gcda_files = set()
         if self._hal_names:
@@ -248,16 +281,14 @@
             entries = []
             try:
                 entries = dut.adb.shell(
-                    'lshal -itp 2> /dev/null | grep -E \"{0}\"'.format(
+                    "lshal -itp 2> /dev/null | grep -E \"{0}\"".format(
                         searchString)).splitlines()
             except AdbError as e:
-                logging.error('failed to get pid entries')
+                logging.error("failed to get pid entries")
 
-            pids = set([
-                pid.strip()
-                for pid in map(lambda entry: entry.split()[-1], entries)
-                if pid.isdigit()
-            ])
+            pids = set(pid.strip()
+                       for pid in map(lambda entry: entry.split()[-1], entries)
+                       if pid.isdigit())
             pids.add(_SP_COVERAGE_PATH)
             for pid in pids:
                 path = path_utils.JoinTargetPath(TARGET_COVERAGE_PATH, pid)
@@ -265,48 +296,62 @@
                     files = dut.adb.shell("find %s -name \"*.gcda\"" % path)
                     gcda_files.update(files.split("\n"))
                 except AdbError as e:
-                    logging.info('No gcda files found in path: \"%s\"', path)
-
+                    logging.info("No gcda files found in path: \"%s\"", path)
         else:
-            try:
-                gcda_files.update(
-                    dut.adb.shell("find %s -name \"*.gcda\"" %
-                                  TARGET_COVERAGE_PATH).split("\n"))
-            except AdbError as e:
-                logging.warn('No gcda files found in path: \"%s\"',
-                             TARGET_COVERAGE_PATH)
+            cmd = ("find %s -name \"*.gcda\"" % TARGET_COVERAGE_PATH)
+            result = self._ExecuteOneAdbShellCommand(dut, serial, cmd)
+            if result:
+                gcda_files.update(result.split("\n"))
 
         for gcda in gcda_files:
             if gcda:
                 basename = os.path.basename(gcda.strip())
                 file_name = os.path.join(self.local_coverage_path, basename)
-                dut.adb.pull("%s %s" % (gcda, file_name))
+                if dut is None:
+                    results = cmd_utils.ExecuteShellCommand(
+                        "adb -s %s pull %s %s " % (serial, gcda, file_name))
+                    if (results[cmd_utils.EXIT_CODE][0]):
+                        logging.error(
+                            "Fail to execute command: %s. error: %s" %
+                            (cmd, str(results[cmd_utils.STDERR][0])))
+                else:
+                    dut.adb.pull("%s %s" % (gcda, file_name))
                 gcda_content = open(file_name, "rb").read()
-                gcda_dict[basename] = gcda_content
-        self._ClearTargetGcov(dut)
+                gcda_dict[gcda.strip()] = gcda_content
+        self._ClearTargetGcov(dut, serial)
         return gcda_dict
 
-    def _OutputCoverageReport(self, isGlobal):
+    def _OutputCoverageReport(self, isGlobal, coverage_report_msg=None):
         logging.info("outputing coverage data")
         timestamp_seconds = str(int(time.time() * 1000000))
         coverage_report_file_name = "coverage_report_" + timestamp_seconds + ".txt"
         if self._coverage_report_file_prefix:
             coverage_report_file_name = "coverage_report_" + self._coverage_report_file_prefix + ".txt"
 
-        coverage_report_file = os.path.join(self.local_coverage_path,
+        coverage_report_file = None
+        if (self._coverage_report_dir):
+            if not os.path.exists(self._coverage_report_dir):
+                os.makedirs(self._coverage_report_dir)
+            coverage_report_file = os.path.join(self._coverage_report_dir,
                                             coverage_report_file_name)
-        logging.info("Storing coverage report to: %s", coverage_report_file)
-        coverage_report_msg = ReportMsg.TestReportMessage()
-        if isGlobal:
-            for c in self.web.report_msg.coverage:
-                coverage = coverage_report_msg.coverage.add()
-                coverage.CopyFrom(c)
         else:
-            for c in self.web.current_test_report_msg.coverage:
-                coverage = coverage_report_msg.coverage.add()
-                coverage.CopyFrom(c)
-        with open(coverage_report_file, 'w+') as f:
-            f.write(str(coverage_report_msg))
+            coverage_report_file = os.path.join(self.local_coverage_path,
+                                            coverage_report_file_name)
+
+        logging.info("Storing coverage report to: %s", coverage_report_file)
+        if self.web and self.web.enabled:
+            coverage_report_msg = ReportMsg.TestReportMessage()
+            if isGlobal:
+                for c in self.web.report_msg.coverage:
+                    coverage = coverage_report_msg.coverage.add()
+                    coverage.CopyFrom(c)
+            else:
+                for c in self.web.current_test_report_msg.coverage:
+                    coverage = coverage_report_msg.coverage.add()
+                    coverage.CopyFrom(c)
+        if coverage_report_msg is not None:
+            with open(coverage_report_file, "w+") as f:
+                f.write(str(coverage_report_msg))
 
     def _AutoProcess(self, cov_zip, revision_dict, gcda_dict, isGlobal):
         """Process coverage data and appends coverage reports to the report message.
@@ -335,27 +380,35 @@
         checksum_gcno_dict = self._GetChecksumGcnoDict(cov_zip)
         output_coverage_report = getattr(
             self, keys.ConfigKeys.IKEY_OUTPUT_COVERAGE_REPORT, False)
+        exclude_coverage_path = getattr(
+            self, keys.ConfigKeys.IKEY_EXCLUDE_COVERAGE_PATH, [])
+        for idx, path in enumerate(exclude_coverage_path):
+            base_name = os.path.basename(path)
+            if base_name and "." not in base_name:
+                path = path if path.endswith("/") else path + "/"
+                exclude_coverage_path[idx] = path
+        exclude_coverage_path.extend(self._DEFAULT_EXCLUDE_PATHS)
+
+        coverage_dict = dict()
+        coverage_report_message = ReportMsg.TestReportMessage()
 
         for gcda_name in gcda_dict:
+            if GEN_TAG in gcda_name:
+                # skip coverage measurement for intermediate code.
+                logging.warn("Skip for gcda file: %s", gcda_name)
+                continue
+
             gcda_stream = io.BytesIO(gcda_dict[gcda_name])
             gcda_file_parser = gcda_parser.GCDAParser(gcda_stream)
+            file_name = gcda_name.rsplit(".", 1)[0]
 
             if not gcda_file_parser.checksum in checksum_gcno_dict:
                 logging.info("No matching gcno file for gcda: %s", gcda_name)
                 continue
-            gcno_file_parser = checksum_gcno_dict[gcda_file_parser.checksum]
-
-            try:
-                gcno_summary = gcno_file_parser.Parse()
-            except FileFormatError:
-                logging.error("Error parsing gcno for gcda %s", gcda_name)
-                continue
-
-            file_name = gcda_name.rsplit(".", 1)[0]
-            src_file_path = self._ExtractSourceName(gcno_summary, file_name)
-
-            if not src_file_path:
-                logging.error("No source file found for gcda %s.", gcda_name)
+            gcno_file_parsers = checksum_gcno_dict[gcda_file_parser.checksum]
+            gcno_summary = self._FindGcnoSummary(file_name, gcno_file_parsers)
+            if gcno_summary is None:
+                logging.error("No gcno file found for gcda %s.", gcda_name)
                 continue
 
             # Process and merge gcno/gcda data
@@ -365,6 +418,10 @@
                 logging.error("Error parsing gcda file %s", gcda_name)
                 continue
 
+            coverage_report.GenerateLineCoverageVector(
+                gcno_summary, exclude_coverage_path, coverage_dict)
+
+        for src_file_path in coverage_dict:
             # Get the git project information
             # Assumes that the project name and path to the project root are similar
             revision = None
@@ -387,24 +444,36 @@
                     revision = str(revision_dict[project_name])
                     logging.info("Source file '%s' matched with project '%s'",
                                  src_file_path, git_project_name)
+                    break
 
             if not revision:
                 logging.info("Could not find git info for %s", src_file_path)
                 continue
 
+            coverage_vec = coverage_dict[src_file_path]
+            total_count, covered_count = coverage_report.GetCoverageStats(
+                coverage_vec)
             if self.web and self.web.enabled:
-                coverage_vec = coverage_report.GenerateLineCoverageVector(
-                    src_file_path, gcno_summary)
-                total_count, covered_count = coverage_report.GetCoverageStats(
-                    coverage_vec)
                 self.web.AddCoverageReport(coverage_vec, src_file_path,
                                            git_project_name, git_project_path,
                                            revision, covered_count,
                                            total_count, isGlobal)
+            else:
+                coverage = coverage_report_message.coverage.add()
+                coverage.total_line_count = total_count
+                coverage.covered_line_count = covered_count
+                coverage.line_coverage_vector.extend(coverage_vec)
+
+                src_file_path = os.path.relpath(src_file_path,
+                                                git_project_path)
+                coverage.file_path = src_file_path
+                coverage.revision = revision
+                coverage.project_name = git_project_name
 
         if output_coverage_report:
-            self._OutputCoverageReport(isGlobal)
+            self._OutputCoverageReport(isGlobal, coverage_report_message)
 
+    # TODO: consider to deprecate the manual process.
     def _ManualProcess(self, cov_zip, revision_dict, gcda_dict, isGlobal):
         """Process coverage data and appends coverage reports to the report message.
 
@@ -476,8 +545,8 @@
                     logging.error("No gcda file found %s.", gcda_name)
                     continue
 
-                src_file_path = self._ExtractSourceName(gcno_summary,
-                                                        file_name)
+                src_file_path = self._ExtractSourceName(
+                    gcno_summary, file_name)
 
                 if not src_file_path:
                     logging.error("No source file found for %s.",
@@ -506,7 +575,7 @@
         if output_coverage_report:
             self._OutputCoverageReport(isGlobal)
 
-    def SetCoverageData(self, dut, isGlobal=False):
+    def SetCoverageData(self, dut=None, serial=None, isGlobal=False):
         """Sets and processes coverage data.
 
         Organizes coverage data and processes it into a coverage report in the
@@ -522,19 +591,22 @@
         if not self.enabled:
             return
 
-        serial = dut.adb.shell('getprop ro.serialno').strip()
-        if not serial in self._device_resource_dict:
-            logging.error('Invalid device provided: %s', serial)
-            return
+        if serial is None:
+            serial = "default" if dut is None else dut.adb.shell(
+                "getprop ro.serialno").strip()
 
-        gcda_dict = self.GetGcdaDict(dut)
-        logging.info("coverage file paths %s", str([fp for fp in gcda_dict]))
+        if not serial in self._device_resource_dict:
+            logging.error("Invalid device provided: %s", serial)
+            return
 
         resource_path = self._device_resource_dict[serial]
         if not resource_path:
-            logging.error('coverage resource path not found.')
+            logging.error("coverage resource path not found.")
             return
 
+        gcda_dict = self._GetGcdaDict(dut, serial)
+        logging.info("coverage file paths %s", str([fp for fp in gcda_dict]))
+
         cov_zip = zipfile.ZipFile(os.path.join(resource_path, _GCOV_ZIP))
 
         revision_dict = json.load(
@@ -547,6 +619,13 @@
             # explicitly process coverage data for the specified modules
             self._ManualProcess(cov_zip, revision_dict, gcda_dict, isGlobal)
 
+        # cleanup the downloaded gcda files.
+        logging.info("cleanup gcda files.")
+        files = os.listdir(self.local_coverage_path)
+        for item in files:
+            if item.endswith(".gcda"):
+                os.remove(os.path.join(self.local_coverage_path, item))
+
     def SetHalNames(self, names=[]):
         """Sets the HAL names for which to process coverage.
 
@@ -562,3 +641,101 @@
             prefix: strings, prefix of the coverage report file.
         """
         self._coverage_report_file_prefix = prefix
+
+    def SetCoverageReportDirectory(self, corverage_report_dir):
+        """Sets the path for storing the coverage report file.
+
+        Args:
+            corverage_report_dir: strings, dir to store the coverage report file.
+        """
+        self._coverage_report_dir = corverage_report_dir
+
+    def _ExecuteOneAdbShellCommand(self, dut, serial, cmd):
+        """Helper method to execute a shell command and return results.
+
+        Args:
+            dut: the device under test.
+            cmd: string, command to execute.
+        Returns:
+            stdout result of the command, None if command fails.
+        """
+        if dut is None:
+            results = cmd_utils.ExecuteShellCommand("adb -s %s shell %s" %
+                                                    (serial, cmd))
+            if (results[cmd_utils.EXIT_CODE][0]):
+                logging.error("Fail to execute command: %s. error: %s" %
+                              (cmd, str(results[cmd_utils.STDERR][0])))
+                return None
+            else:
+                return results[cmd_utils.STDOUT][0]
+        else:
+            try:
+                return dut.adb.shell(cmd)
+            except AdbError as e:
+                logging.warn("Fail to execute command: %s. error: %s" %
+                             (cmd, str(e)))
+                return None
+
+
+if __name__ == '__main__':
+    """ Tools to process coverage data.
+
+    Usage:
+      python coverage_utils.py operation [--serial=device_serial_number]
+      [--report_prefix=prefix_of_coverage_report]
+
+    Example:
+      python coverage_utils.py init_coverage
+      python coverage_utils.py get_coverage --serial HT7821A00243
+      python coverage_utils.py get_coverage --serial HT7821A00243 --report_prefix=test
+    """
+    logging.basicConfig(level=logging.INFO)
+    parser = argparse.ArgumentParser(description="Coverage process tool.")
+    parser.add_argument(
+        "--report_prefix",
+        dest="report_prefix",
+        required=False,
+        help="Prefix of the coverage report.")
+    parser.add_argument(
+        "--report_path",
+        dest="report_path",
+        required=False,
+        help="directory to store the coverage reports.")
+    parser.add_argument(
+        "--serial", dest="serial", required=True, help="Device serial number.")
+    parser.add_argument(
+        "--gcov_rescource_path",
+        dest="gcov_rescource_path",
+        required=True,
+        help="Directory that stores gcov resource files.")
+    parser.add_argument(
+        "operation",
+        help=
+        "Operation for processing coverage data, e.g. 'init_coverage', get_coverage'"
+    )
+    args = parser.parse_args()
+
+    if args.operation != "init_coverage" and args.operation != "get_coverage":
+        print "Unsupported operation. Exiting..."
+        sys.exit(1)
+    user_params = {
+        keys.ConfigKeys.IKEY_ENABLE_COVERAGE:
+        True,
+        keys.ConfigKeys.IKEY_ANDROID_DEVICE: [{
+            keys.ConfigKeys.IKEY_SERIAL:args.serial,
+            keys.ConfigKeys.IKEY_GCOV_RESOURCES_PATH:args.gcov_rescource_path,
+        }],
+        keys.ConfigKeys.IKEY_OUTPUT_COVERAGE_REPORT:
+        True,
+        keys.ConfigKeys.IKEY_GLOBAL_COVERAGE:
+        True
+    }
+    coverage = CoverageFeature(user_params)
+    if args.operation == "init_coverage":
+        coverage.InitializeDeviceCoverage(serial=args.serial)
+    elif args.operation == "get_coverage":
+        if args.report_prefix:
+            coverage.SetCoverageReportFilePrefix(args.report_prefix)
+        if args.report_path:
+            coverage.SetCoverageReportDirectory(args.report_path)
+        coverage.SetCoverageData(serial=args.serial, isGlobal=True)
diff --git a/utils/python/coverage/gcda_parser.py b/utils/python/coverage/gcda_parser.py
index 99e7820..b2dbc3c 100644
--- a/utils/python/coverage/gcda_parser.py
+++ b/utils/python/coverage/gcda_parser.py
@@ -29,7 +29,7 @@
 import sys
 
 from vts.utils.python.coverage import parser
-
+from vts.utils.python.coverage import gcno_parser
 
 class GCDAParser(parser.GcovStreamParserUtil):
     """Parser object class stores stateful information for parsing GCDA files.
@@ -182,3 +182,11 @@
 
     with open(file_name, 'rb') as stream:
         return GCDAParser(stream).Parse(file_summary)
+
+
+if __name__ == '__main__':
+    if len(sys.argv) != 2:
+        print('usage: gcda_parser.py [gcda file name] [gcno file name]')
+    else:
+        file_summary = gcno_parser.ParseGcnoFile(sys.argv[2])
+        print(str(ParseGcdaFile(sys.argv[1], file_summary)))
diff --git a/utils/python/coverage/gcno_parser.py b/utils/python/coverage/gcno_parser.py
index ed5addd..954cc3d 100644
--- a/utils/python/coverage/gcno_parser.py
+++ b/utils/python/coverage/gcno_parser.py
@@ -237,6 +237,6 @@
 
 if __name__ == '__main__':
     if len(sys.argv) < 3 or sys.argv[1] != '-f':
-        print('usage: GCNOparser.py -f [file name]')
+        print('usage: gcno_parser.py -f [file name]')
     else:
-        print(str(parse(sys.argv[2])))
+        print(str(ParseGcnoFile(sys.argv[2])))
diff --git a/utils/python/coverage/sancov_utils.py b/utils/python/coverage/sancov_utils.py
index ab15b47..5cc11db 100644
--- a/utils/python/coverage/sancov_utils.py
+++ b/utils/python/coverage/sancov_utils.py
@@ -94,62 +94,66 @@
                 self._device_resource_dict[serial] = sancov_resource_path
         logging.info('Sancov enabled: %s', self.enabled)
 
-    def InitializeDeviceCoverage(self, dut, hal_name):
+    def InitializeDeviceCoverage(self, dut, hals):
         """Initializes the sanitizer coverage on the device for the provided HAL.
 
         Args:
             dut: The device under test.
-            hal_name: The HAL name and version (string) for which to measure coverage
-                      (e.g. 'android.hardware.light@2.0-service')
+            hals: A list of the HAL name and version (string) for which to
+                  measure coverage (e.g. ['android.hardware.light@2.0'])
         """
         serial = dut.adb.shell('getprop ro.serialno').strip()
         if serial not in self._device_resource_dict:
             logging.error("Invalid device provided: %s", serial)
             return
-        entries = dut.adb.shell('lshal -itp 2> /dev/null | grep {0}'.format(
-            hal_name)).splitlines()
-        pids = set([pid.strip()
-                    for pid in map(lambda entry: entry.split()[-1], entries)
-                    if pid.isdigit()])
 
-        if len(pids) == 0:
-            logging.warn('No matching processes IDs found for HAL %s',
-                         hal_name)
-            return
-        processes = dut.adb.shell('ps -p {0} -o comm='.format(' '.join(
-            pids))).splitlines()
-        process_names = set(
-            [name.strip() for name in processes
-             if name.strip() and not name.endswith(' (deleted)')])
 
-        if len(process_names) == 0:
-            logging.warn('No matching processes names found for HAL %s',
-                         hal_name)
-            return
+        for hal in hals:
+            entries = dut.adb.shell('lshal -itp 2> /dev/null | grep {0}'.format(
+                hal)).splitlines()
+            pids = set([pid.strip()
+                        for pid in map(lambda entry: entry.split()[-1], entries)
+                        if pid.isdigit()])
 
-        for process_name in process_names:
-            cmd = self._PROCESS_INIT_COMMAND.format(
-                process_name, self._TARGET_SANCOV_PATH, hal_name)
-            try:
-                dut.adb.shell(cmd.format(process_name))
-            except AdbError as e:
-                logging.error('Command failed: \"%s\"', cmd)
-                continue
+            if len(pids) == 0:
+                logging.warn('No matching processes IDs found for HAL %s',
+                             hal)
+                return
+            processes = dut.adb.shell('ps -p {0} -o comm='.format(' '.join(
+                pids))).splitlines()
+            process_names = set(
+                [name.strip() for name in processes
+                 if name.strip() and not name.endswith(' (deleted)')])
 
-    def FlushDeviceCoverage(self, dut, hal_name=None):
+            if len(process_names) == 0:
+                logging.warn('No matching processes names found for HAL %s',
+                             hal)
+                return
+
+            for process_name in process_names:
+                cmd = self._PROCESS_INIT_COMMAND.format(
+                    process_name, self._TARGET_SANCOV_PATH, hal)
+                try:
+                    dut.adb.shell(cmd.format(process_name))
+                except AdbError as e:
+                    logging.error('Command failed: \"%s\"', cmd)
+                    continue
+
+    def FlushDeviceCoverage(self, dut, hals):
         """Flushes the sanitizer coverage on the device for the provided HAL.
 
         Args:
             dut: The device under test.
-            hal_name: The HAL name and version (string) for which to flush coverage
-                      (e.g. 'android.hardware.light@2.0-service')
+            hals: A list of HAL name and version (string) for which to flush
+                  coverage (e.g. ['android.hardware.light@2.0-service'])
         """
         serial = dut.adb.shell('getprop ro.serialno').strip()
         if serial not in self._device_resource_dict:
             logging.error('Invalid device provided: %s', serial)
             return
-        dut.adb.shell(
-            self._FLUSH_COMMAND.format('' if hal_name is None else hal_name))
+        for hal in hals:
+            dut.adb.shell(
+                self._FLUSH_COMMAND.format(hal))
 
     def _InitializeFileVectors(self, serial, binary_path):
         """Parse the binary and read the debugging information.
@@ -267,7 +271,7 @@
                     git_project_name, git_project_path, revision,
                     covered_count, total_count, True)
 
-    def ProcessDeviceCoverage(self, dut, hal_name):
+    def ProcessDeviceCoverage(self, dut, hals):
         """Process device coverage.
 
         Fetch sancov files from the target, parse the sancov files, symbolize the output,
@@ -275,8 +279,8 @@
 
         Args:
             dut: The device under test.
-            hal_name: The HAL name and version (string) for which to process coverage
-                      (e.g. 'android.hardware.light@2.0')
+            hals: A list of HAL name and version (string) for which to process
+                  coverage (e.g. ['android.hardware.light@2.0'])
         """
         serial = dut.adb.shell('getprop ro.serialno').strip()
         product = dut.adb.shell('getprop ro.build.product').strip()
@@ -292,8 +296,11 @@
             os.path.join(self._device_resource_dict[serial],
                          self._SYMBOLS_ZIP))
 
-        sancov_files = dut.adb.shell('find {0}/{1} -name \"*.sancov\"'.format(
-            self._TARGET_SANCOV_PATH, hal_name)).splitlines()
+
+        sancov_files = []
+        for hal in hals:
+            sancov_files.extend(dut.adb.shell('find {0}/{1} -name \"*.sancov\"'.format(
+                self._TARGET_SANCOV_PATH, hal)).splitlines())
         temp_dir = tempfile.mkdtemp()
 
         binary_to_sancov = {}
@@ -304,8 +311,9 @@
                 os.path.join(temp_dir, os.path.basename(file)))
             binary_to_sancov[binary] = (bitness, offsets)
 
-        dut.adb.shell('rm -rf {0}/{1}'.format(self._TARGET_SANCOV_PATH,
-                                              hal_name))
+        for hal in hals:
+            dut.adb.shell('rm -rf {0}/{1}'.format(self._TARGET_SANCOV_PATH,
+                                                  hal))
 
         search_root = os.path.join('out', 'target', 'product', product,
                                    'symbols')
diff --git a/utils/python/cpu/cpu_frequency_scaling.py b/utils/python/cpu/cpu_frequency_scaling.py
index b24721d..5ce6b0e 100644
--- a/utils/python/cpu/cpu_frequency_scaling.py
+++ b/utils/python/cpu/cpu_frequency_scaling.py
@@ -39,6 +39,11 @@
         _theoretical_max_frequency: a dict where its key is the CPU number and
                                     its value is an integer containing the
                                     theoretical max CPU frequency.
+        _perf_override: boolean, true if this module has switched the device from
+                        its normal cpufreq governor to the performance
+                        governor.
+        _saved_governors: list of strings, the saved cpufreq governor for each
+                          CPU on the device.
     """
 
     def __init__(self, dut):
@@ -49,10 +54,11 @@
         """Creates a shell mirror object and reads the configuration values."""
         if self._init:
             return
-        self._dut.shell.InvokeTerminal("cpu_frequency_scaling")
-        self._shell = self._dut.shell.cpu_frequency_scaling
+        self._shell = self._dut.shell
         self._min_cpu_number, self._max_cpu_number = self._LoadMinAndMaxCpuNo()
         self._theoretical_max_frequency = {}
+        self._perf_override = False
+        self._saved_governors = None
         self._init = True
 
     def _LoadMinAndMaxCpuNo(self):
@@ -110,30 +116,76 @@
                          " not set.", cpu_no)
             return None
 
-    def ChangeCpuGovernor(self, mode):
+    def ChangeCpuGovernor(self, modes):
         """Changes the CPU governor mode of all the CPUs on the device.
 
         Args:
-            mode: expected CPU governor mode, e.g., 'performance' or 'interactive'.
+            modes: list of expected CPU governor modes, e.g., 'performance'
+                   or 'schedutil'. The length of the list must be equal to
+                   the number of CPUs on the device.
+
+        Returns:
+            A list of the previous governor modes if successful, None otherwise.
         """
         self.Init()
+        asserts.assertEqual(self._max_cpu_number - self._min_cpu_number,
+                            len(modes))
+        # save current governor settings
+        target_cmd = []
+        prev_govs = []
         for cpu_no in range(self._min_cpu_number, self._max_cpu_number):
-            results = self._shell.Execute(
-                "echo %s > /sys/devices/system/cpu/cpu%s/"
-                "cpufreq/scaling_governor" % (mode, cpu_no))
-            asserts.assertEqual(1, len(results[const.EXIT_CODE]))
-            if results[const.EXIT_CODE][0]:
-                logging.warn("Can't change CPU governor.")
-                logging.warn("Stderr for scaling_governor: %s",
-                    results[const.STDERR][0])
+            target_cmd.append("cat /sys/devices/system/cpu/cpu%s/cpufreq/"
+                               "scaling_governor" % cpu_no)
+        results = self._shell.Execute(target_cmd)
+        asserts.assertEqual(self._max_cpu_number - self._min_cpu_number,
+                            len(results[const.STDOUT]))
+        if any(results[const.EXIT_CODE]):
+            logging.warn("Unable to save governors")
+            logging.warn("Stderr for saving scaling_governor: %s",
+                results[const.STDERR])
+            return
+        for cpu_no in range(self._min_cpu_number, self._max_cpu_number):
+            prev_govs.append(results[const.STDOUT][cpu_no].rstrip())
+        # set new governor
+        target_cmd = []
+        for cpu_no in range(self._min_cpu_number, self._max_cpu_number):
+            target_cmd.append("echo %s > /sys/devices/system/cpu/cpu%s/cpufreq/"
+                               "scaling_governor" % (modes[cpu_no], cpu_no))
+        results = self._shell.Execute(target_cmd)
+        asserts.assertEqual(self._max_cpu_number - self._min_cpu_number,
+                            len(results[const.STDOUT]))
+        if any(results[const.EXIT_CODE]):
+            logging.warn("Can't change CPU governor.")
+            logging.warn("Stderr for changing scaling_governor: %s",
+                results[const.STDERR])
+            return
+        return prev_govs
 
     def DisableCpuScaling(self):
         """Disable CPU frequency scaling on the device."""
-        self.ChangeCpuGovernor("performance")
+        self.Init()
+        if self._perf_override:
+            logging.warn("DisableCpuScaling called while scaling already disabled.")
+            return;
+        new_govs = []
+        for cpu_no in range(self._min_cpu_number, self._max_cpu_number):
+            new_govs.append("performance")
+        prev_govs = self.ChangeCpuGovernor(new_govs)
+        if prev_govs is not None:
+            self._saved_governors = prev_govs
+            self._perf_override = True
 
     def EnableCpuScaling(self):
         """Enable CPU frequency scaling on the device."""
-        self.ChangeCpuGovernor("interactive")
+        self.Init()
+        if not self._perf_override:
+            logging.warn("EnableCpuScaling called while scaling already enabled.")
+            return;
+        if self._saved_governors is None:
+            logging.warn("EnableCpuScaling called and _saved_governors is None.")
+            return;
+        self.ChangeCpuGovernor(self._saved_governors)
+        self._perf_override = False
 
     def IsUnderThermalThrottling(self):
         """Checks whether a target device is under thermal throttling.
@@ -155,16 +207,16 @@
                 return False
             configurable_max_frequency = results[const.STDOUT][0].strip()
             current_frequency = results[const.STDOUT][1].strip()
-            if configurable_max_frequency != current_frequency:
+            if configurable_max_frequency > current_frequency:
                 logging.error(
-                    "CPU%s: Configurable max frequency %s != current frequency %s",
+                    "CPU%s: Configurable max frequency %s > current frequency %s",
                     cpu_no, configurable_max_frequency, current_frequency)
                 return True
             theoretical_max_frequency = self._GetTheoreticalMaxFrequency(cpu_no)
             if (theoretical_max_frequency is not None and
-                theoretical_max_frequency != int(current_frequency)):
+                theoretical_max_frequency > int(current_frequency)):
                 logging.error(
-                    "CPU%s, Theoretical max frequency %d != scaling current frequency %s",
+                    "CPU%s, Theoretical max frequency %d > scaling current frequency %s",
                     cpu_no, theoretical_max_frequency, current_frequency)
                 return True
         return False
diff --git a/utils/python/file/target_file_utils.py b/utils/python/file/target_file_utils.py
index c744bc5..a24b3fe 100644
--- a/utils/python/file/target_file_utils.py
+++ b/utils/python/file/target_file_utils.py
@@ -23,25 +23,6 @@
 _EXECUTE_PERMISSION = 1
 
 
-def Exists(filepath, shell):
-    """Determines if a file exists.
-
-    Args:
-        filepath: string, path to file
-        shell: an instance of the VTS shell
-
-    Returns:
-        True if the file exists, False otherwise
-    """
-    cmd = "ls %s" % filepath
-    results = shell.Execute(cmd)
-    if results[const.EXIT_CODE][0] != 0:
-        return False
-
-    out_str = str(results[const.STDOUT][0]).strip()
-    return out_str.find(filepath) == 0
-
-
 def _Test(shell, *args):
     """Executes test command on device.
 
@@ -57,6 +38,19 @@
     return results[const.EXIT_CODE][0] == 0
 
 
+def Exists(filepath, shell):
+    """Determines if a file or directory exists.
+
+    Args:
+        filepath: string, the path to a file or a directory.
+        shell: an instance of the VTS shell.
+
+    Returns:
+        True if exists, False otherwise.
+    """
+    return _Test(shell, "-e", filepath)
+
+
 def IsDirectory(path, shell):
     """Determines if a path is a directory.
 
diff --git a/utils/python/hal/hal_service_name_utils.py b/utils/python/hal/hal_service_name_utils.py
index 0651a9b..ec17233 100644
--- a/utils/python/hal/hal_service_name_utils.py
+++ b/utils/python/hal/hal_service_name_utils.py
@@ -15,6 +15,7 @@
 #
 
 import json
+import logging
 
 from vts.runners.host import asserts
 from vts.runners.host import const
@@ -22,6 +23,7 @@
 VTS_TESTABILITY_CHECKER_32 = "/data/local/tmp/vts_testability_checker32"
 VTS_TESTABILITY_CHECKER_64 = "/data/local/tmp/vts_testability_checker64"
 
+
 def GetHalServiceName(shell, hal, bitness="64", run_as_compliance_test=False):
     """Determine whether to run a VTS test against a HAL and get the service
     names of the given hal if determine to run.
@@ -46,8 +48,8 @@
     cmd += " -b " + bitness + " " + hal
     cmd_results = shell.Execute(str(cmd))
     asserts.assertFalse(
-            any(cmd_results[const.EXIT_CODE]),
-            "Failed to run vts_testability_checker.")
+        any(cmd_results[const.EXIT_CODE]),
+        "Failed to run vts_testability_checker. Error: %s" % cmd_results[const.STDERR][0])
     result = json.loads(cmd_results[const.STDOUT][0])
     if str(result['Testable']).lower() == "true":
         return True, set(result['instances'])
@@ -55,9 +57,46 @@
         return False, ()
 
 
-def GetServiceInstancesCombinations(services, service_instances):
+class CombMode(object):
+    """Enum for service name combination mode"""
+    FULL_PERMUTATION = 0
+    NAME_MATCH = 1
+    NO_COMBINATION = 2
+
+
+def GetServiceInstancesCombinations(services,
+                                    service_instances,
+                                    mode=CombMode.FULL_PERMUTATION):
+    """Create combinations of instances for all services.
+
+    Args:
+        services: list, all services used in the test. e.g. [s1, s2]
+        service_instances: dictionary, mapping of each service and the
+                           corresponding service name(s).
+                           e.g. {"s1": ["n1"], "s2": ["n2", "n3"]}
+        mode: CombMode that determines the combination strategy.
+
+    Returns:
+        A list of service instance combinations.
+    """
+    if mode == CombMode.FULL_PERMUTATION:
+        return GetServiceInstancesFullCombinations(services, service_instances)
+    elif mode == CombMode.NAME_MATCH:
+        return GetServiceInstancesNameMatchCombinations(services,
+                                                        service_instances)
+    else:
+        logging.warning("Unknown comb mode, use default comb mode instead.")
+        return GetServiceInstancesFullCombinations(services, service_instances)
+
+
+def GetServiceInstancesFullCombinations(services, service_instances):
     """Create all combinations of instances for all services.
 
+    Create full permutation for registered service instances, e.g.
+    s1 have instances (n1, n2) and s2 have instances (n3, n4), return all
+    permutations of s1 and s2, i.e. (s1/n1, s2/n3), (s1/n1, s2/n4),
+    (s1/n2, s2/n3) and (s1/n2, s2/n4)
+
     Args:
         services: list, all services used in the test. e.g. [s1, s2]
         service_instances: dictionary, mapping of each service and the
@@ -87,3 +126,42 @@
                 service_instance_combinations.append(new_instance_comb)
 
     return service_instance_combinations
+
+
+def GetServiceInstancesNameMatchCombinations(services, service_instances):
+    """Create service instance combinations with same name for services.
+
+    Create combinations for services with the same instance name, e.g.
+    both s1 and s2 have two instances with name n1, and n2, return
+    the service instance combination of s1 and s2 with same instance name,
+    i.e. (s1/n1, s2/n1) and (s1/n2, s2/n2)
+
+    Args:
+        services: list, all services used in the test. e.g. [s1, s2]
+        service_instances: dictionary, mapping of each service and the
+                           corresponding service name(s).
+                           e.g. {"s1": ["n1", "n2"], "s2": ["n1", "n2"]}
+
+    Returns:
+        A list of service instance combinations.
+        e.g. [[s1/n1, s2/n1], [s1/n2, s2/n2]]
+    """
+    service_instance_combinations = []
+    instance_names = set()
+    for service in services:
+        if service not in service_instances or not service_instances[service]:
+            logging.error("Does not found instance for service: %s", service)
+            return []
+        if not instance_names:
+            instance_names = set(service_instances[service])
+        else:
+            instance_names.intersection_update(set(service_instances[service]))
+
+    for name in instance_names:
+        instance_comb = []
+        for service in services:
+            new_instance = [service + '/' + name]
+            instance_comb.extend(new_instance)
+        service_instance_combinations.append(instance_comb)
+
+    return service_instance_combinations
diff --git a/utils/python/hal/hal_service_name_utils_unittest.py b/utils/python/hal/hal_service_name_utils_unittest.py
index 28285da..71f017d 100644
--- a/utils/python/hal/hal_service_name_utils_unittest.py
+++ b/utils/python/hal/hal_service_name_utils_unittest.py
@@ -22,7 +22,7 @@
 class HalServiceNameUtilsUnitTest(unittest.TestCase):
     """Tests for hal hidl gtest template"""
 
-    def testGetServiceInstancesCombinations(self):
+    def testGetServiceInstancesFullCombinations(self):
         """Test the function to get service instance combinations"""
 
         comb1 = hal_service_name_utils.GetServiceInstancesCombinations([], {})
@@ -57,6 +57,50 @@
                            "s2": ["n1", "n2"]})
         self.assertEqual([["s2/n1"], ["s2/n2"]], comb9)
 
+    def testGetServiceInstancesNameMatchCombinations(self):
+        """Test the function to get service instance combinations"""
+
+        mode = hal_service_name_utils.CombMode.NAME_MATCH
+        comb1 = hal_service_name_utils.GetServiceInstancesCombinations([], {},
+                                                                       mode)
+        self.assertEquals(0, len(comb1))
+        comb2 = hal_service_name_utils.GetServiceInstancesCombinations(
+            ["s1"], {}, mode)
+        self.assertEquals(0, len(comb2))
+        comb3 = hal_service_name_utils.GetServiceInstancesCombinations(
+            ["s1"], {"s1": ["n1"]}, mode)
+        #self.assertEqual([["s1/n1"]], comb3)
+        comb4 = hal_service_name_utils.GetServiceInstancesCombinations(
+            ["s1"], {"s1": ["n1", "n2"]}, mode)
+        self.assertEqual([["s1/n1"], ["s1/n2"]], comb4)
+        comb5 = hal_service_name_utils.GetServiceInstancesCombinations(
+            ["s1", "s2"], {"s1": ["n1", "n2"]}, mode)
+        self.assertEqual(0, len(comb5))
+        comb6 = hal_service_name_utils.GetServiceInstancesCombinations(
+            ["s1", "s2"], {"s1": ["n1", "n2"],
+                           "s2": ["n3"]}, mode)
+        self.assertEqual(0, len(comb6))
+        comb7 = hal_service_name_utils.GetServiceInstancesCombinations(
+            ["s1", "s2"], {"s1": ["n1", "n2"],
+                           "s2": ["n3", "n4"]}, mode)
+        self.assertEqual(0, len(comb7))
+        comb8 = hal_service_name_utils.GetServiceInstancesCombinations(
+            ["s1", "s2"], {"s1": ["n1", "n2"],
+                           "s2": []}, mode)
+        self.assertEqual(0, len(comb8))
+        comb9 = hal_service_name_utils.GetServiceInstancesCombinations(
+            ["s1", "s2"], {"s1": [],
+                           "s2": ["n1", "n2"]}, mode)
+        self.assertEqual(0, len(comb9))
+        comb10 = hal_service_name_utils.GetServiceInstancesCombinations(
+            ["s1", "s2"], {"s1": ["n1"],
+                           "s2": ["n1", "n2"]}, mode)
+        self.assertEqual([["s1/n1", "s2/n1"]], comb10)
+        comb11 = hal_service_name_utils.GetServiceInstancesCombinations(
+            ["s1", "s2"], {"s1": ["n1", "n2"],
+                           "s2": ["n1", "n2"]}, mode)
+        self.assertEqual([["s1/n1", "s2/n1"], ["s1/n2", "s2/n2"]], comb11)
+
 
 if __name__ == '__main__':
     unittest.main()
diff --git a/utils/python/retry/__init__.py b/utils/python/io/__init__.py
similarity index 100%
rename from utils/python/retry/__init__.py
rename to utils/python/io/__init__.py
diff --git a/utils/python/io/capture_printout.py b/utils/python/io/capture_printout.py
new file mode 100644
index 0000000..d1df4d0
--- /dev/null
+++ b/utils/python/io/capture_printout.py
@@ -0,0 +1,39 @@
+#
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+
+from cStringIO import StringIO
+import sys
+
+
+class CaptureStdout(list):
+    '''Capture system stdout as a list of string.
+
+    Usage example:
+        with CaptureStdout() as output:
+            print 'something'
+
+        print 'Got output list: %s' % output
+    '''
+
+    def __enter__(self):
+        self.sys_stdout = sys.stdout
+        sys.stdout = StringIO()
+        return self
+
+    def __exit__(self, *args):
+        self.extend(sys.stdout.getvalue().splitlines())
+        sys.stdout = self.sys_stdout
\ No newline at end of file
diff --git a/utils/python/io/file_util.py b/utils/python/io/file_util.py
new file mode 100644
index 0000000..082ee5d
--- /dev/null
+++ b/utils/python/io/file_util.py
@@ -0,0 +1,95 @@
+#
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+import logging
+import os
+import shutil
+import tempfile
+
+
+def FindFile(directory, filename):
+    '''Find a file under directory given the filename.
+
+    Args:
+        directory: string, directory path
+        filename: string, file name to find
+
+    Returns:
+        String, path to the file found. None if not found.
+    '''
+    for (dirpath, dirnames, filenames) in os.walk(
+            directory, followlinks=False):
+        for fn in filenames:
+            if fn == filename:
+                return os.path.join(dirpath, filename)
+
+    return None
+
+
+def Rmdirs(path, ignore_errors=False):
+    '''Remove the given directory and its contents recursively.
+
+    Args:
+        path: string, directory to delete
+        ignore_errors: bool, whether to ignore errors. Defaults to False
+
+    Returns:
+        bool, True if directory is deleted.
+              False if errors occur or directory does not exist.
+    '''
+    return_value = False
+    if os.path.exists(path):
+        try:
+            shutil.rmtree(path, ignore_errors=ignore_errors)
+            return_value = True
+        except OSError as e:
+            logging.exception(e)
+    return return_value
+
+
+def Makedirs(path, skip_if_exists=True):
+    '''Make directories lead to the given path.
+
+    Args:
+        path: string, directory to make
+        skip_if_exists: bool, True for ignoring exisitng dir. False for throwing
+                        error from os.mkdirs. Defaults to True
+
+    Returns:
+        bool, True if directory is created.
+              False if errors occur or directory already exist.
+    '''
+    return_value = False
+    if not skip_if_exists or not os.path.exists(path):
+        try:
+            os.makedirs(path)
+            return_value = True
+        except OSError as e:
+            logging.exception(e)
+    return return_value
+
+
+def MakeTempDir(base_dir):
+    """Make a temp directory based on the given path and return its path.
+
+    Args:
+        base_dir: string, base directory to make temp directory.
+
+    Returns:
+        string, relative path to created temp directory
+    """
+    Makedirs(base_dir)
+    return tempfile.mkdtemp(dir=base_dir)
\ No newline at end of file
diff --git a/utils/python/io/file_util_test.py b/utils/python/io/file_util_test.py
new file mode 100644
index 0000000..bb59a3c
--- /dev/null
+++ b/utils/python/io/file_util_test.py
@@ -0,0 +1,102 @@
+#!/usr/bin/env python
+#
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+import os
+import random
+import string
+import unittest
+
+from vts.utils.python.io import file_util
+
+
+class FileUtilTest(unittest.TestCase):
+    def setUp(self):
+        """Resets generated directory list"""
+        self._dirs = []
+
+    def tearDown(self):
+        """Removes existing directories"""
+        for path in self._dirs:
+            file_util.Rmdirs(path)
+
+    def testMakeAndRemoveDirs(self):
+        """Tests making and removing directories """
+        dir_name = ''.join(
+            random.choice(string.ascii_lowercase + string.digits)
+            for _ in range(12))
+        self._dirs.append(dir_name)
+
+        # make non-existing directory
+        result = file_util.Makedirs(dir_name)
+        self.assertEqual(True, result)
+
+        # make existing directory
+        result = file_util.Makedirs(dir_name)
+        self.assertEqual(False, result)
+
+        # delete existing directory
+        result = file_util.Rmdirs(dir_name)
+        self.assertEqual(True, result)
+
+        # delete non-existing directory
+        result = file_util.Rmdirs(dir_name)
+        self.assertEqual(False, result)
+
+    def testMakeTempDir(self):
+        """Tests making temp directory """
+        base_dir = ''.join(
+            random.choice(string.ascii_lowercase + string.digits)
+            for _ in range(12))
+        self._dirs.append(base_dir)
+
+        # make temp directory
+        result = file_util.MakeTempDir(base_dir)
+        self.assertTrue(os.path.join(base_dir, "tmp"))
+
+    def testMakeException(self):
+        """Tests making directory and raise exception """
+        dir_name = ''.join(
+            random.choice(string.ascii_lowercase + string.digits)
+            for _ in range(12))
+        self._dirs.append(dir_name)
+
+        file_util.Makedirs(dir_name)
+        self.assertRaises(Exception,
+                          file_util.Makedirs(dir_name, skip_if_exists=False))
+
+    def testRemoveException(self):
+        """Tests removing directory and raise exception """
+        dir_name = ''.join(
+            random.choice(string.ascii_lowercase + string.digits)
+            for _ in range(12))
+        self._dirs.append(dir_name)
+
+        link_name = ''.join(
+            random.choice(string.ascii_lowercase + string.digits)
+            for _ in range(12))
+
+        file_util.Makedirs(dir_name)
+        os.symlink(dir_name, link_name)
+        try:
+            self.assertRaises(Exception,
+                              file_util.Rmdirs(link_name, ignore_errors=False))
+        finally:
+            os.remove(link_name)
+
+
+if __name__ == "__main__":
+    unittest.main()
diff --git a/utils/python/library/vtable_parser.py b/utils/python/library/vtable_parser.py
index cf582b4..a489aa3 100644
--- a/utils/python/library/vtable_parser.py
+++ b/utils/python/library/vtable_parser.py
@@ -17,6 +17,7 @@
 import logging
 import os
 import re
+import stat
 
 from vts.runners.host import utils
 from vts.utils.python.common import cmd_utils
@@ -53,8 +54,19 @@
             "^(\\d+) +" + re.escape("(int (*)(...))") + " (.+)$")
 
     def __init__(self, dumper_path):
+        """Initializes the dumper path and its permission."""
         self._dumper_path = dumper_path
 
+        if not utils.is_on_windows():
+            bin_path = os.path.join(
+                self._dumper_path, "bin", self.VNDK_VTABLE_DUMPER)
+            try:
+                mode = os.stat(bin_path).st_mode
+                os.chmod(bin_path,
+                         mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
+            except OSError as e:
+                logging.warning("Fail to chmod vtable dumper: %s", e)
+
     def CallVtableDumper(self, lib_path):
         """Calls vndk-vtable-dumper and returns its output.
 
diff --git a/utils/python/mirror/py2pb.py b/utils/python/mirror/py2pb.py
index ec2a396..933a42b 100644
--- a/utils/python/mirror/py2pb.py
+++ b/utils/python/mirror/py2pb.py
@@ -163,6 +163,11 @@
                 logging.error("PyDict2PbStruct: unsupported type %s",
                               attr.type)
                 sys.exit(-1)
+        else:
+            # TODO: instead crash the test, consider to generate default value
+            # in case not provided in the py_value.
+            logging.error("PyDict2PbStruct: attr %s not provided", attr.name)
+            sys.exit(-1)
     if len(provided_attrs) > 0:
         logging.error("PyDict2PbStruct: provided dictionary included elements" +
                       " not part of the type being converted to: %s",
diff --git a/utils/python/performance/benchmark_parser.py b/utils/python/performance/benchmark_parser.py
index e837838..a6fcdeb 100644
--- a/utils/python/performance/benchmark_parser.py
+++ b/utils/python/performance/benchmark_parser.py
@@ -78,7 +78,7 @@
         Returns:
             A list of integers.
         """
-        return [x[self._REAL_TIME] for x in self._benchmarks]
+        return [int(float(x[self._REAL_TIME])) for x in self._benchmarks]
 
     def ToTable(self):
         """Returns the benchmarks in a table.
diff --git a/utils/python/precondition/precondition_utils.py b/utils/python/precondition/precondition_utils.py
index a891c18..b1f70c2 100644
--- a/utils/python/precondition/precondition_utils.py
+++ b/utils/python/precondition/precondition_utils.py
@@ -103,9 +103,95 @@
     if hal:
         testable, _ = hal_service_name_utils.GetHalServiceName(
             shell, hal, bitness, run_as_compliance_test)
-        if not testable:
-            logging.warn("The required hal %s is not testable.", hal)
-            return False
+        return testable
 
     logging.info("Precondition check pass.")
     return True
+
+
+def MeetFirstApiLevelPrecondition(test_instance, dut=None):
+    """Checks first API level precondition of a test instance.
+
+    If the device's ro.product.first_api_level is 0, this function checks
+    ro.build.version.sdk.
+
+    Args:
+        test_instance: the test instance which inherits BaseTestClass.
+        dut: the AndroidDevice under test.
+
+    Returns:
+        True if the device's first API level is greater than or equal to the
+        value of the precondition; False otherwise.
+    """
+    opt_params = [keys.ConfigKeys.IKEY_PRECONDITION_FIRST_API_LEVEL]
+    test_instance.getUserParams(opt_param_names=opt_params)
+    if not hasattr(test_instance,
+                   keys.ConfigKeys.IKEY_PRECONDITION_FIRST_API_LEVEL):
+        return True
+
+    precond_level_attr = getattr(
+        test_instance, keys.ConfigKeys.IKEY_PRECONDITION_FIRST_API_LEVEL, 0)
+    try:
+        precond_level = int(precond_level_attr)
+    except ValueError:
+        logging.error("Cannot parse first API level precondition: %s",
+                      precond_level_attr)
+        return True
+
+    if not dut:
+        logging.info("Read first API level from the first device.")
+        dut = test_instance.android_devices[0]
+    device_level = dut.getLaunchApiLevel(strict=False)
+    if not device_level:
+        logging.error("Cannot read first API level from device. "
+                      "Assume it meets the precondition.")
+        return True
+
+    logging.info("Device's first API level=%d; precondition=%d",
+                 device_level, precond_level)
+    return device_level >= precond_level
+
+
+def CheckSysPropPrecondition(test_instance,
+                             dut,
+                             shell=None):
+    """Checks sysprop precondition of a test instance.
+
+    Args:
+        test_instance: the test instance which inherits BaseTestClass.
+        dut: the AndroidDevice under test.
+        shell: the ShellMirrorObject to execute command on the device.
+               If not specified, the function creates one from dut.
+
+    Returns:
+        False if precondition is not met (i.e., to skip tests),
+        True otherwise (e.g., when no sysprop precondition is set;
+        the precondition is satisfied;
+        there is an error in retrieving the target sysprop; or
+        the specified sysprop is undefined)
+    """
+    if not hasattr(test_instance, keys.ConfigKeys.IKEY_PRECONDITION_SYSPROP):
+        return True
+
+    precond_sysprop = str(getattr(
+        test_instance, keys.ConfigKeys.IKEY_PRECONDITION_SYSPROP, ''))
+    if "=" not in precond_sysprop:
+        logging.error("precondition-sysprop value is invalid.")
+        return True
+
+    if shell is None:
+        dut.shell.InvokeTerminal("check_sysprop_precondition")
+        shell = dut.shell.check_sysprop_precondition
+
+    sysprop_key, sysprop_value = precond_sysprop.split('=')
+    cmd_results = shell.Execute('getprop %s' % sysprop_key)
+    if any(cmd_results[const.EXIT_CODE]):
+        logging.error('Failed to read sysprop:\n%s', sysprop_key)
+        return True
+    else:
+        value = cmd_results[const.STDOUT][0].strip()
+        if len(value) == 0:
+            return True
+        elif value != sysprop_value:
+            return False
+    return True
diff --git a/utils/python/profiling/profiling_utils.py b/utils/python/profiling/profiling_utils.py
index b3e4562..d3234c3 100644
--- a/utils/python/profiling/profiling_utils.py
+++ b/utils/python/profiling/profiling_utils.py
@@ -154,16 +154,17 @@
             hal_instrumentation_lib_path: string, the path of directory that stores
                                           profiling libraries.
         """
-        if hal_instrumentation_lib_path is None:
+        hal_instrumentation_lib_path_32 = HAL_INSTRUMENTATION_LIB_PATH_32
+        hal_instrumentation_lib_path_64 = HAL_INSTRUMENTATION_LIB_PATH_64
+        if hal_instrumentation_lib_path is not None:
             bitness = getattr(self, keys.ConfigKeys.IKEY_ABI_BITNESS, None)
             if bitness == '64':
-                hal_instrumentation_lib_path = HAL_INSTRUMENTATION_LIB_PATH_64
+                hal_instrumentation_lib_path_64 = hal_instrumentation_lib_path
             elif bitness == '32':
-                hal_instrumentation_lib_path = HAL_INSTRUMENTATION_LIB_PATH_32
+                hal_instrumentation_lib_path_32 = hal_instrumentation_lib_path
             else:
                 logging.error('Unknown abi bitness "%s". Using 64bit hal '
                               'instrumentation lib path.', bitness)
-                hal_instrumentation_lib_path = HAL_INSTRUMENTATION_LIB_PATH_64
 
         # cleanup any existing traces.
         shell.Execute("rm " + os.path.join(TARGET_PROFILING_TRACE_PATH,
@@ -173,8 +174,11 @@
         # give permission to write the trace file.
         shell.Execute("chmod 777 " + TARGET_PROFILING_TRACE_PATH)
 
-        shell.Execute("setprop hal.instrumentation.lib.path " +
-                      hal_instrumentation_lib_path)
+        shell.Execute("setprop hal.instrumentation.lib.path.32 " +
+                      hal_instrumentation_lib_path_32)
+        shell.Execute("setprop hal.instrumentation.lib.path.64 " +
+                      hal_instrumentation_lib_path_64)
+
         shell.Execute("setprop hal.instrumentation.enable true")
 
     def DisableVTSProfiling(self, shell):
@@ -207,7 +211,7 @@
         trace_processor_lib = os.path.join(data_file_path, "host", "lib64")
         trace_processor_cmd = [
             "chmod a+x %s" % trace_processor_binary,
-            "LD_LIBRARY_PATH=%s %s --profiling %s" %
+            "LD_LIBRARY_PATH=%s %s -m profiling_trace %s" %
             (trace_processor_lib, trace_processor_binary, trace_file)
         ]
 
diff --git a/utils/python/reporting/report_file_utils.py b/utils/python/reporting/report_file_utils.py
index 437fa8a..899a9a1 100644
--- a/utils/python/reporting/report_file_utils.py
+++ b/utils/python/reporting/report_file_utils.py
@@ -20,6 +20,9 @@
 import shutil
 
 
+PYTHON_OUTPUT_ADDITIONAL = 'additional_output_files'
+
+
 def NotNoneStr(item):
     '''Convert a veriable to string only if it is not None'''
     return str(item) if item is not None else None
@@ -200,3 +203,12 @@
             return urls
         except IOError as e:
             logging.exception(e)
+
+    def GetAdditioanlOutputDirectory(self):
+        '''Returns a directory to store additional output files.
+
+        Files under this directory will be included in log output folder.
+        All files will be uploaded to VTS dashboard if enabled;
+        Only files with recognized file types will be included in TradeFed output.
+        '''
+        return os.path.join(logging.log_path, PYTHON_OUTPUT_ADDITIONAL)
\ No newline at end of file
diff --git a/utils/python/retry/retry.py b/utils/python/retry/retry.py
deleted file mode 100644
index 1494742..0000000
--- a/utils/python/retry/retry.py
+++ /dev/null
@@ -1,129 +0,0 @@
-#
-# Copyright (C) 2016 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-import sys
-import time
-
-def GenericRetry(handler, max_retry, functor,
-                 sleep=0, backoff_factor=1, success_functor=lambda x: None,
-                 raise_first_exception_on_failure=True, *args, **kwargs):
-    """Generic retry loop w/ optional break out depending on exceptions.
-
-    To retry based on the return value of |functor| see the timeout_util module.
-
-    Keep in mind that the total sleep time will be the triangular value of
-    max_retry multiplied by the sleep value.  e.g. max_retry=5 and sleep=10
-    will be T5 (i.e. 5+4+3+2+1) times 10, or 150 seconds total.  Rather than
-    use a large sleep value, you should lean more towards large retries and
-    lower sleep intervals, or by utilizing backoff_factor.
-
-    Args:
-        handler: A functor invoked w/ the exception instance that
-            functor(*args, **kwargs) threw.  If it returns True, then a
-            retry is attempted.  If False, the exception is re-raised.
-        max_retry: A positive integer representing how many times to retry
-            the command before giving up.  Worst case, the command is invoked
-            max_retry + 1) times before failing.
-        functor: A callable to pass args and kwargs to.
-        sleep: Optional keyword.  Multiplier for how long to sleep between
-            retries; will delay (1*sleep) the first time, then (2*sleep),
-            continuing via attempt * sleep.
-        backoff_factor: Optional keyword. If supplied and > 1, subsequent sleeps
-                        will be of length (backoff_factor ^ (attempt - 1)) * sleep,
-                        rather than the default behavior of attempt * sleep.
-        success_functor: Optional functor that accepts 1 argument. Will be called
-                         after successful call to |functor|, with the argument
-                         being the number of attempts (1 = |functor| succeeded on
-                         first try).
-        raise_first_exception_on_failure: Optional boolean which determines which
-                                          exception is raised upon failure after
-                                          retries. If True, the first exception
-                                          that was encountered. If False, the
-                                          final one. Default: True.
-        *args: Positional args passed to functor.
-        **kwargs: Optional args passed to functor.
-
-    Returns:
-        Whatever functor(*args, **kwargs) returns.
-
-    Raises:
-        Exception: Whatever exceptions functor(*args, **kwargs) throws and
-            isn't suppressed is raised.  Note that the first exception encountered
-            is what's thrown.
-    """
-
-    if max_retry < 0:
-        raise ValueError('max_retry needs to be zero or more: %s' % max_retry)
-
-    if backoff_factor < 1:
-        raise ValueError('backoff_factor must be 1 or greater: %s'
-                          % backoff_factor)
-
-    ret, success = (None, False)
-    attempt = 0
-
-    exc_info = None
-    for attempt in xrange(max_retry + 1):
-        if attempt and sleep:
-            if backoff_factor > 1:
-                sleep_time = sleep * backoff_factor ** (attempt - 1)
-            else:
-                sleep_time = sleep * attempt
-            time.sleep(sleep_time)
-        try:
-            ret = functor(*args, **kwargs)
-            success = True
-            break
-        except Exception as e:
-            # Note we're not snagging BaseException, so MemoryError/KeyboardInterrupt
-            # and friends don't enter this except block.
-            if not handler(e):
-                raise
-            # If raise_first_exception_on_failure, we intentionally ignore
-            # any failures in later attempts since we'll throw the original
-            # failure if all retries fail.
-            if exc_info is None or not raise_first_exception_on_failure:
-                exc_info = sys.exc_info()
-
-    if success:
-        success_functor(attempt + 1)
-        return ret
-
-    raise exc_info[0], exc_info[1], exc_info[2]
-
-def RetryException(exc_retry, max_retry, functor, *args, **kwargs):
-    """Convenience wrapper for GenericRetry based on exceptions.
-
-    Args:
-        exc_retry: A class (or tuple of classes).  If the raised exception
-            is the given class(es), a retry will be attempted.  Otherwise,
-            the exception is raised.
-        max_retry: See GenericRetry.
-        functor: See GenericRetry.
-        *args: See GenericRetry.
-        **kwargs: See GenericRetry.
-
-    Returns:
-        Return what functor returns.
-
-    Raises:
-        TypeError, if exc_retry is of an unexpected type.
-    """
-    if not isinstance(exc_retry, (tuple, type)):
-        raise TypeError("exc_retry should be an exception (or tuple), not %r" %
-                         exc_retry)
-    def _Handler(exc, values=exc_retry):
-        return isinstance(exc, values)
-    return GenericRetry(_Handler, max_retry, functor, *args, **kwargs)
diff --git a/utils/python/systrace/systrace_utils.py b/utils/python/systrace/systrace_utils.py
index 215c71d..74e00b0 100644
--- a/utils/python/systrace/systrace_utils.py
+++ b/utils/python/systrace/systrace_utils.py
@@ -111,8 +111,7 @@
             process = controller.process_name
             time = feature_utils.GetTimestamp()
             report_path = getattr(self, keys.ConfigKeys.IKEY_SYSTRACE_REPORT_PATH)
-            report_destination_file_name = '{module}_{test}_{process}_{time}.html'.format(
-                module=test_module_name,
+            report_destination_file_name = 'systrace_{test}_{process}_{time}.html'.format(
                 test=test_name,
                 process=process,
                 time=time)
diff --git a/utils/python/retry/__init__.py b/utils/python/vndk/__init__.py
similarity index 100%
copy from utils/python/retry/__init__.py
copy to utils/python/vndk/__init__.py
diff --git a/utils/python/vndk/vndk_utils.py b/utils/python/vndk/vndk_utils.py
new file mode 100644
index 0000000..d52686d
--- /dev/null
+++ b/utils/python/vndk/vndk_utils.py
@@ -0,0 +1,95 @@
+#
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+import logging
+
+from vts.utils.python.android import api
+
+
+def IsVndkRuntimeEnforced(dut):
+    """Returns whether VNDK runtime should be enabled on the device.
+
+    VNDK runtime is optional in O-MR1 (API 27); enforced after O-MR1. If it is
+    enabled, the device has the property of vndk_version.
+    The usage of this function is to decide whether to skip VNDK test cases.
+
+    Args:
+        dut: The AndroidDevice under test.
+
+    Returns:
+        A boolean, whether VNDK runtime should be enabled.
+    """
+    api_level = dut.getLaunchApiLevel(strict=False)
+    if not api_level:
+        logging.error("Cannot get first API level. "
+                      "Assume VNDK runtime to be enforced.")
+        return True
+    return bool(api_level > api.PLATFORM_API_LEVEL_O_MR1 or dut.vndk_version)
+
+
+def FormatVndkPath(pattern, bitness, version=""):
+    """Formats a VNDK path.
+
+    Args:
+        pattern: The path pattern containing {LIB} and {VER}.
+        bitness: A string or an integer, 32 or 64.
+        version: A string, the VNDK version.
+
+    Returns:
+        A string, the formatted path.
+    """
+    return pattern.format(
+        LIB=("lib64" if str(bitness) == "64" else "lib"),
+        VER=("-" + version if version and version != "current" else ""))
+
+
+def GetVndkCoreDirectory(bitness, version):
+    """Returns the path to VNDK-core directory on device.
+
+    Args:
+        bitness: A string or an integer, 32 or 64.
+        version: A string, the VNDK version.
+
+    Returns:
+        A string, the path to VNDK-core directory.
+    """
+    return FormatVndkPath("/system/{LIB}/vndk{VER}", bitness, version)
+
+
+def GetVndkSpDirectory(bitness, version):
+    """Returns the path to VNDK-SP directory on device.
+
+    Args:
+        bitness: A string or an integer, 32 or 64.
+        version: A string, the VNDK version.
+
+    Returns:
+        A string, the path to VNDK-SP directory.
+    """
+    return FormatVndkPath("/system/{LIB}/vndk-sp{VER}", bitness, version)
+
+
+def GetVndkSpExtDirectories(bitness):
+    """Returns the paths to VNDK-SP extension directories on device.
+
+    Args:
+        bitness: A string or an integer, 32 or 64.
+
+    Returns:
+        A list of strings, the paths to VNDK-SP extension directories.
+    """
+    return [FormatVndkPath("/odm/{LIB}/vndk-sp", bitness),
+            FormatVndkPath("/vendor/{LIB}/vndk-sp", bitness)]
diff --git a/utils/python/web/web_utils.py b/utils/python/web/web_utils.py
index 43f2433..ebcc762 100644
--- a/utils/python/web/web_utils.py
+++ b/utils/python/web/web_utils.py
@@ -33,7 +33,7 @@
     """Feature object for web functionality.
 
     Attributes:
-        enabled: boolean, True if systrace is enabled, False otherwise
+        enabled: boolean, True if web feature is enabled, False otherwise
         report_msg: TestReportMessage, Proto summarizing the test run
         current_test_report_msg: TestCaseReportMessage, Proto summarizing the current test case
         rest_client: DashboardRestClient, client to which data will be posted
@@ -47,7 +47,10 @@
         keys.ConfigKeys.IKEY_ANDROID_DEVICE, keys.ConfigKeys.IKEY_ABI_NAME,
         keys.ConfigKeys.IKEY_ABI_BITNESS
     ]
-    _OPTIONAL_PARAMS = []
+    _OPTIONAL_PARAMS = [
+        keys.ConfigKeys.RUN_AS_VTS_SELFTEST,
+        keys.ConfigKeys.IKEY_ENABLE_PROFILING,
+    ]
 
     def __init__(self, user_params):
         """Initializes the web feature.
@@ -77,6 +80,11 @@
         self.report_msg = ReportMsg.TestReportMessage()
         self.report_msg.test = str(
             getattr(self, keys.ConfigKeys.KEY_TESTBED_NAME))
+
+        if getattr(self, keys.ConfigKeys.IKEY_ENABLE_PROFILING, False):
+            logging.info("Profiling test")
+            self.report_msg.test += "Profiling"
+
         self.report_msg.test_type = ReportMsg.VTS_HOST_DRIVEN_STRUCTURAL
         self.report_msg.start_timestamp = feature_utils.GetTimestamp()
         self.report_msg.host_info.hostname = socket.gethostname()
@@ -386,7 +394,8 @@
             return None
 
         # Handle case when runner fails, tests aren't executed
-        if (executed and executed[-1].test_name == "setup_class"):
+        if (not getattr(self, keys.ConfigKeys.RUN_AS_VTS_SELFTEST, False)
+            and executed and executed[-1].test_name == "setup_class"):
             # Test failed during setup, all tests were not executed
             start_index = 0
         else: