Merge "Snap for 5611628 from 49ddcfc5b100a5470fc824de62ab91170275ac94 to sdk-release" into sdk-release
diff --git a/.classpath b/.classpath
index 5ed457d..2f5448b 100644
--- a/.classpath
+++ b/.classpath
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <classpath>
-	<classpathentry kind="src" path="src"/>
+	<classpathentry excluding="com/android/tradefed/testtype/junit4/LongevityHostRunner.java|com/android/tradefed/testtype/junit4/builder/DeviceJUnit4ClassRunnerBuilder.java" kind="src" path="src"/>
 	<classpathentry kind="src" path="res"/>
 	<classpathentry kind="src" path="hamcrest"/>
 	<classpathentry kind="src" path="jline"/>
@@ -10,7 +10,11 @@
 	<classpathentry combineaccessrules="false" kind="src" path="/ddmlib"/>
 	<classpathentry combineaccessrules="false" kind="src" path="/loganalysis"/>
 	<classpathentry combineaccessrules="false" kind="src" path="/platform-annotations"/>
-	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER">
+		<attributes>
+			<attribute name="module" value="true"/>
+		</attributes>
+	</classpathentry>
 	<classpathentry exported="true" kind="var" path="TRADEFED_ROOT/prebuilts/misc/common/commons-compress/commons-compress-prebuilt.jar"/>
 	<classpathentry exported="true" kind="var" path="TRADEFED_ROOT/prebuilts/misc/common/devtools-annotations/devtools-annotations-prebuilt.jar"/>
 	<classpathentry exported="true" kind="var" path="TRADEFED_ROOT/prebuilts/misc/common/kxml2/kxml2-2.3.0.jar"/>
diff --git a/Android.bp b/Android.bp
index cbd1e50..2b63059 100644
--- a/Android.bp
+++ b/Android.bp
@@ -69,6 +69,7 @@
     },
     static_libs: [
         "platformprotos",
+        "asuite_proto_java",
     ],
 }
 
diff --git a/atest/Android.bp b/atest/Android.bp
index d67972b..f93be04 100644
--- a/atest/Android.bp
+++ b/atest/Android.bp
@@ -52,6 +52,7 @@
     exclude_srcs: [
         "*_unittest.py",
         "*/*_unittest.py",
+        "asuite_lib_test/*.py",
         "proto/*_pb2.py",
         "proto/__init__.py",
     ],
@@ -111,6 +112,7 @@
         "unittest_data/**/.*",
     ],
     exclude_srcs: [
+        "asuite_lib_test/*.py",
         "proto/*_pb2.py",
         "proto/__init__.py",
     ],
@@ -149,6 +151,20 @@
     },
 }
 
+java_library_host {
+    name: "asuite_proto_java",
+    srcs: [
+        "proto/*.proto",
+    ],
+    libs: [
+        "libprotobuf-java-full",
+    ],
+    proto: {
+        canonical_path_from_root: false,
+        include_dirs: ["external/protobuf/src"],
+    },
+}
+
 python_library_host {
     name: "asuite_proto",
     defaults: ["asuite_default"],
@@ -174,3 +190,4 @@
         "asuite_metrics",
     ],
 }
+
diff --git a/atest/TEST_MAPPING b/atest/TEST_MAPPING
index 0769294..5ea63b3 100644
--- a/atest/TEST_MAPPING
+++ b/atest/TEST_MAPPING
@@ -3,6 +3,22 @@
     {
       "name": "atest_unittests",
       "host": true
+    },
+    {
+      "name": "asuite_metrics_lib_tests",
+      "host": true
+    },
+    {
+      "name": "asuite_metrics_lib_py3_tests",
+      "host": true
+    },
+    {
+      "name": "asuite_cc_lib_tests",
+      "host": true
+    },
+    {
+      "name": "asuite_cc_lib_py3_tests",
+      "host": true
     }
   ]
 }
diff --git a/atest/asuite_lib_test/Android.bp b/atest/asuite_lib_test/Android.bp
new file mode 100644
index 0000000..ba93d93
--- /dev/null
+++ b/atest/asuite_lib_test/Android.bp
@@ -0,0 +1,83 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Separate asuite_metrics and asuite_cc_client libs to different tests, due to asuite_cc_client
+// also include asuite_metrics and other needed python files, in order to make sure asuite_metrics
+// tests result is accurate, separate them to two different test modules.
+
+// For testing asuite_metrics python2 libs
+python_test_host {
+    name: "asuite_metrics_lib_tests",
+    main: "asuite_lib_run_test.py",
+    // These tests primarily check that the metric libs can be imported properly (see b/132086641).
+    // Specify a different pkg_path so that we can properly test them in isolation.
+    pkg_path: "asuite_test",
+    srcs: [
+        "asuite_lib_run_test.py",
+        "asuite_metrics_test.py",
+    ],
+    libs: [
+        "asuite_metrics",
+    ],
+    test_suites: ["general-tests"],
+    defaults: ["atest_py2_default"],
+}
+
+// For testing asuite_metrics python3 libs
+python_test_host {
+    name: "asuite_metrics_lib_py3_tests",
+    main: "asuite_lib_run_test.py",
+    pkg_path: "asuite_test",
+    srcs: [
+        "asuite_lib_run_test.py",
+        "asuite_metrics_test.py",
+    ],
+    libs: [
+        "asuite_metrics",
+    ],
+    test_suites: ["general-tests"],
+    defaults: ["atest_lib_default"],
+}
+
+// For testing asuite_cc_client python2 libs
+python_test_host {
+    name: "asuite_cc_lib_tests",
+    main: "asuite_lib_run_test.py",
+    pkg_path: "asuite_test",
+    srcs: [
+        "asuite_lib_run_test.py",
+        "asuite_cc_client_test.py",
+    ],
+    libs: [
+        "asuite_cc_client",
+    ],
+    test_suites: ["general-tests"],
+    defaults: ["atest_py2_default"],
+}
+
+// For testing asuite_cc_client python3 libs
+python_test_host {
+    name: "asuite_cc_lib_py3_tests",
+    main: "asuite_lib_run_test.py",
+    pkg_path: "asuite_test",
+    srcs: [
+        "asuite_lib_run_test.py",
+        "asuite_cc_client_test.py",
+    ],
+    libs: [
+        "asuite_cc_client",
+    ],
+    test_suites: ["general-tests"],
+    defaults: ["atest_lib_default"],
+}
diff --git a/atest/asuite_lib_test/asuite_cc_client_test.py b/atest/asuite_lib_test/asuite_cc_client_test.py
new file mode 100755
index 0000000..8ae1068
--- /dev/null
+++ b/atest/asuite_lib_test/asuite_cc_client_test.py
@@ -0,0 +1,35 @@
+#!/usr/bin/env python
+#
+# Copyright 2019, The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Unittest for atest_execution_info."""
+
+import unittest
+
+
+class AsuiteCCLibTest(unittest.TestCase):
+    """Tests for verify asuite_metrics libs"""
+
+    def test_import_asuite_cc_lib(self):
+        """Test asuite_cc_lib."""
+        # pylint: disable=import-error, unused-variable
+        from asuite.metrics import metrics
+        from asuite.metrics import metrics_base
+        from asuite.metrics import metrics_utils
+
+        # TODO (b/132602907): Add the real usage for checking if metrics pass or fail.
+
+if __name__ == "__main__":
+    unittest.main()
diff --git a/atest/asuite_lib_test/asuite_lib_run_test.py b/atest/asuite_lib_test/asuite_lib_run_test.py
new file mode 100644
index 0000000..ad98ae8
--- /dev/null
+++ b/atest/asuite_lib_test/asuite_lib_run_test.py
@@ -0,0 +1,77 @@
+#!/usr/bin/env python
+#
+# Copyright 2019 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Main entrypoint for all of atest's unittest."""
+
+import os
+import sys
+import unittest
+from importlib import import_module
+
+
+def get_test_modules():
+    """Returns a list of test modules.
+
+    Finds all the test files (*_test.py) and get their relative
+    path (internal/lib/utils_test.py) and translate it to an import path and
+    strip the py ext (internal.lib.utils_test).
+
+    Returns:
+        List of strings (the testable module import path).
+    """
+    testable_modules = []
+    base_path = os.path.dirname(os.path.realpath(__file__))
+
+    for dirpath, _, files in os.walk(base_path):
+        for f in files:
+            if f.endswith("_test.py"):
+                # Now transform it into a relative import path.
+                full_file_path = os.path.join(dirpath, f)
+                rel_file_path = os.path.relpath(full_file_path, base_path)
+                rel_file_path, _ = os.path.splitext(rel_file_path)
+                rel_file_path = rel_file_path.replace(os.sep, ".")
+                testable_modules.append(rel_file_path)
+
+    return testable_modules
+
+
+def main(_):
+    """Main unittest entry.
+
+    Args:
+        argv: A list of system arguments. (unused)
+
+    Returns:
+        0 if success. None-zero if fails.
+    """
+    # Force remove syspath related to atest to make sure the env is clean.
+    # These tests need to run in isolation (to find bugs like b/132086641)
+    # so we scrub out all atest modules.
+    for path in sys.path:
+        if path.endswith('/atest'):
+            sys.path.remove(path)
+    test_modules = get_test_modules()
+    for mod in test_modules:
+        import_module(mod)
+
+    loader = unittest.defaultTestLoader
+    test_suite = loader.loadTestsFromNames(test_modules)
+    runner = unittest.TextTestRunner(verbosity=2)
+    result = runner.run(test_suite)
+    sys.exit(not result.wasSuccessful())
+
+
+if __name__ == '__main__':
+    main(sys.argv[1:])
diff --git a/atest/asuite_lib_test/asuite_metrics_test.py b/atest/asuite_lib_test/asuite_metrics_test.py
new file mode 100755
index 0000000..b150f7f
--- /dev/null
+++ b/atest/asuite_lib_test/asuite_metrics_test.py
@@ -0,0 +1,33 @@
+#!/usr/bin/env python
+#
+# Copyright 2019, The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Unittest for atest_execution_info."""
+
+import unittest
+
+
+class AsuiteMetricsTest(unittest.TestCase):
+    """Tests for verify asuite_metrics libs"""
+
+    def test_import_asuite_metrics_lib(self):
+        """Test asuite_metrics_lib."""
+        # pylint: disable=import-error, unused-variable
+        from asuite import asuite_metrics
+
+        # TODO (b/132602907): Add the real usage for checking if metrics pass or fail.
+
+if __name__ == "__main__":
+    unittest.main()
diff --git a/atest/atest_execution_info_unittest.py b/atest/atest_execution_info_unittest.py
index 2c4ec31..f4fd7dc 100755
--- a/atest/atest_execution_info_unittest.py
+++ b/atest/atest_execution_info_unittest.py
@@ -32,7 +32,7 @@
     test_time='(10ms)',
     runner_total=None,
     group_total=2,
-    perf_info={}
+    additional_info={}
 )
 
 # pylint: disable=protected-access
diff --git a/atest/proto/clientanalytics.proto b/atest/proto/clientanalytics.proto
index 41293ea..e75bf78 100644
--- a/atest/proto/clientanalytics.proto
+++ b/atest/proto/clientanalytics.proto
@@ -1,5 +1,7 @@
 syntax = "proto2";
 
+option java_package = "com.android.asuite.clearcut";
+
 message LogRequest {
   optional ClientInfo client_info = 1;
   optional int32 log_source = 2;
diff --git a/atest/proto/common.proto b/atest/proto/common.proto
index d11a0f9..49cc48c 100644
--- a/atest/proto/common.proto
+++ b/atest/proto/common.proto
@@ -1,5 +1,7 @@
 syntax = "proto2";
 
+option java_package = "com.android.asuite.clearcut";
+
 message Duration {
   required int64 seconds = 1;
   required int32 nanos = 2;
@@ -11,4 +13,4 @@
 enum UserType {
   GOOGLE = 0;
   EXTERNAL = 1;
-}
\ No newline at end of file
+}
diff --git a/atest/proto/external_user_log.proto b/atest/proto/external_user_log.proto
index 505497e..533ff0a 100644
--- a/atest/proto/external_user_log.proto
+++ b/atest/proto/external_user_log.proto
@@ -2,6 +2,8 @@
 
 import "proto/common.proto";
 
+option java_package = "com.android.asuite.clearcut";
+
 // Proto used by Atest CLI Tool for External Non-PII Users
 message AtestLogEventExternal {
 
diff --git a/atest/proto/internal_user_log.proto b/atest/proto/internal_user_log.proto
index 8fbecc6..05d4dee 100644
--- a/atest/proto/internal_user_log.proto
+++ b/atest/proto/internal_user_log.proto
@@ -2,6 +2,8 @@
 
 import "proto/common.proto";
 
+option java_package = "com.android.asuite.clearcut";
+
 // Proto used by Atest CLI Tool for internal Users
 message AtestLogEventInternal {
 
diff --git a/atest/result_reporter.py b/atest/result_reporter.py
index 52eb181..2cd4930 100644
--- a/atest/result_reporter.py
+++ b/atest/result_reporter.py
@@ -357,14 +357,8 @@
                                                  test.status,
                                                  constants.GREEN),
                                              test.test_time))
-                if test.perf_info.keys():
-                    print('\t%s: %s(ns) %s: %s(ns) %s: %s'
-                          %(au.colorize('cpu_time', constants.BLUE),
-                            test.perf_info['cpu_time'],
-                            au.colorize('real_time', constants.BLUE),
-                            test.perf_info['real_time'],
-                            au.colorize('iterations', constants.BLUE),
-                            test.perf_info['iterations']))
+                for key, data in test.additional_info.items():
+                    print('\t%s: %s' % (au.colorize(key, constants.BLUE), data))
             elif test.status == test_runner_base.IGNORED_STATUS:
                 # Example: [33/92] test_name: IGNORED (12ms)
                 print('[%s/%s] %s: %s %s' % (test.test_count, test.group_total,
diff --git a/atest/result_reporter_unittest.py b/atest/result_reporter_unittest.py
index 6b14932..3468a48 100755
--- a/atest/result_reporter_unittest.py
+++ b/atest/result_reporter_unittest.py
@@ -32,7 +32,7 @@
     test_time='(10ms)',
     runner_total=None,
     group_total=2,
-    perf_info={}
+    additional_info={}
 )
 
 RESULT_PASSED_TEST_MODULE_2 = test_runner_base.TestResult(
@@ -45,7 +45,7 @@
     test_time='(10ms)',
     runner_total=None,
     group_total=2,
-    perf_info={}
+    additional_info={}
 )
 
 RESULT_PASSED_TEST_RUNNER_2_NO_MODULE = test_runner_base.TestResult(
@@ -58,7 +58,7 @@
     test_time='(10ms)',
     runner_total=None,
     group_total=2,
-    perf_info={}
+    additional_info={}
 )
 
 RESULT_FAILED_TEST = test_runner_base.TestResult(
@@ -71,7 +71,7 @@
     test_time='',
     runner_total=None,
     group_total=2,
-    perf_info={}
+    additional_info={}
 )
 
 RESULT_RUN_FAILURE = test_runner_base.TestResult(
@@ -84,7 +84,7 @@
     test_time='',
     runner_total=None,
     group_total=2,
-    perf_info={}
+    additional_info={}
 )
 
 RESULT_INVOCATION_FAILURE = test_runner_base.TestResult(
@@ -97,7 +97,7 @@
     test_time='',
     runner_total=None,
     group_total=None,
-    perf_info={}
+    additional_info={}
 )
 
 RESULT_IGNORED_TEST = test_runner_base.TestResult(
@@ -110,7 +110,7 @@
     test_time='(10ms)',
     runner_total=None,
     group_total=2,
-    perf_info={}
+    additional_info={}
 )
 
 RESULT_ASSUMPTION_FAILED_TEST = test_runner_base.TestResult(
@@ -123,7 +123,7 @@
     test_time='(10ms)',
     runner_total=None,
     group_total=2,
-    perf_info={}
+    additional_info={}
 )
 
 #pylint: disable=protected-access
diff --git a/atest/test_finders/module_finder.py b/atest/test_finders/module_finder.py
index e3767e4..6c3ecd6 100644
--- a/atest/test_finders/module_finder.py
+++ b/atest/test_finders/module_finder.py
@@ -212,13 +212,12 @@
                 return test_config
         return rel_config
 
-    def _get_test_info_filter(self, path, methods, module_name, **kwargs):
+    def _get_test_info_filter(self, path, methods, **kwargs):
         """Get test info filter.
 
         Args:
             path: A string of the test's path.
             methods: A set of method name strings.
-            module_name: A string of the module name.
             rel_module_dir: Optional. A string of the module dir relative to
                 root.
             class_name: Optional. A string of the class name.
@@ -251,7 +250,6 @@
                         kwargs.get('class_name', '*'), methods), frozenset())])
         # Path to non-module dir, treat as package.
         elif (not file_name
-              and not self.module_info.is_auto_gen_test_config(module_name)
               and kwargs.get('rel_module_dir', None) !=
               os.path.relpath(path, self.root_dir)):
             dir_items = [os.path.join(path, f) for f in os.listdir(path)]
@@ -371,7 +369,7 @@
         if not test_path:
             return None
         test_filter = self._get_test_info_filter(
-            test_path, methods, module_name, class_name=class_name,
+            test_path, methods, class_name=class_name,
             is_native_test=is_native_test)
         tinfo = self._get_test_info(test_path, rel_config, module_name,
                                     test_filter)
@@ -488,7 +486,7 @@
         if not rel_module_dir:
             return None
         rel_config = os.path.join(rel_module_dir, constants.MODULE_CONFIG)
-        test_filter = self._get_test_info_filter(path, methods, None,
+        test_filter = self._get_test_info_filter(path, methods,
                                                  rel_module_dir=rel_module_dir)
         return self._get_test_info(path, rel_config, None, test_filter)
 
diff --git a/atest/test_runners/event_handler.py b/atest/test_runners/event_handler.py
index e1523a2..a5d00f3 100644
--- a/atest/test_runners/event_handler.py
+++ b/atest/test_runners/event_handler.py
@@ -124,7 +124,7 @@
             test_time='',
             runner_total=None,
             group_total=self.state['current_group_total'],
-            perf_info={}))
+            additional_info={}))
 
     def _invocation_failed(self, event_data):
         # Broadest possible failure. May not even start the module/test run.
@@ -138,7 +138,7 @@
             test_time='',
             runner_total=None,
             group_total=self.state['current_group_total'],
-            perf_info={}))
+            additional_info={}))
 
     def _run_ended(self, event_data):
         pass
@@ -170,14 +170,11 @@
             status = test_runner_base.PASSED_STATUS
             trace = None
 
-        cpu_time = event_data.get('cpu_time', None)
-        real_time = event_data.get('real_time', None)
-        iterations = event_data.get('iterations', None)
-        perf_info = {}
-        if (cpu_time and real_time and iterations):
-            perf_info['cpu_time'] = cpu_time
-            perf_info['real_time'] = real_time
-            perf_info['iterations'] = iterations
+        default_event_keys = ['className', 'end_time', 'testName']
+        additional_info = {}
+        for event_key in event_data.keys():
+            if event_key not in default_event_keys:
+                additional_info[event_key] = event_data.get(event_key, None)
 
         self.reporter.process_test_result(test_runner_base.TestResult(
             runner_name=self.runner_name,
@@ -188,7 +185,7 @@
             test_count=self.state['test_count'],
             test_time=test_time,
             runner_total=None,
-            perf_info=perf_info,
+            additional_info=additional_info,
             group_total=self.state['current_group_total']))
 
     def _log_association(self, event_data):
@@ -253,7 +250,7 @@
                     test_time='',
                     runner_total=None,
                     group_total=self.state['current_group_total'],
-                    perf_info={}))
+                    additional_info={}))
             raise EventHandleError(EVENTS_NOT_BALANCED % (start_event,
                                                           event_name))
 
diff --git a/atest/test_runners/event_handler_unittest.py b/atest/test_runners/event_handler_unittest.py
index e10cee2..c1994eb 100755
--- a/atest/test_runners/event_handler_unittest.py
+++ b/atest/test_runners/event_handler_unittest.py
@@ -107,6 +107,11 @@
     ('TEST_ENDED', {'end_time':9876450, 'className':'someClassName2',
                     'testName':'someTestName2', 'cpu_time':'1234.1234(ns)',
                     'real_time':'5678.5678(ns)', 'iterations':'6666'}),
+    ('TEST_STARTED', {'start_time':10, 'className':'someClassName3',
+                      'testName':'someTestName3'}),
+    ('TEST_ENDED', {'end_time':70, 'className':'someClassName3',
+                    'testName':'someTestName3', 'additional_info_min':'102773',
+                    'additional_info_mean':'105973', 'additional_info_median':'103778'}),
     ('TEST_RUN_ENDED', {}),
     ('TEST_MODULE_ENDED', {'foo': 'bar'}),
 ]
@@ -137,7 +142,7 @@
             test_time='(996ms)',
             runner_total=None,
             group_total=2,
-            perf_info={}
+            additional_info={}
         ))
         call2 = mock.call(test_runner_base.TestResult(
             runner_name=atf_tr.AtestTradefedTestRunner.NAME,
@@ -149,7 +154,7 @@
             test_time='(2h44m36.402s)',
             runner_total=None,
             group_total=2,
-            perf_info={}
+            additional_info={}
         ))
         self.mock_reporter.process_test_result.assert_has_calls([call1, call2])
 
@@ -167,7 +172,7 @@
             test_time='',
             runner_total=None,
             group_total=2,
-            perf_info={}
+            additional_info={}
         ))
         self.mock_reporter.process_test_result.assert_has_calls([call])
 
@@ -185,7 +190,7 @@
             test_time='',
             runner_total=None,
             group_total=None,
-            perf_info={}
+            additional_info={}
         ))
         self.mock_reporter.process_test_result.assert_has_calls([call])
 
@@ -204,7 +209,7 @@
             test_time='(8ms)',
             runner_total=None,
             group_total=2,
-            perf_info={}
+            additional_info={}
         ))
         self.mock_reporter.process_test_result.assert_has_calls([call])
         # Event pair: TEST_STARTED -> TEST_RUN_ENDED
@@ -236,7 +241,7 @@
             test_time='(10ms)',
             runner_total=None,
             group_total=2,
-            perf_info={}
+            additional_info={}
         ))
         call2 = mock.call(test_runner_base.TestResult(
             runner_name=atf_tr.AtestTradefedTestRunner.NAME,
@@ -248,11 +253,11 @@
             test_time='(62ms)',
             runner_total=None,
             group_total=2,
-            perf_info={}
+            additional_info={}
         ))
         self.mock_reporter.process_test_result.assert_has_calls([call1, call2])
 
-    def test_process_event_with_perf_info(self):
+    def test_process_event_with_additional_info(self):
         """Test process_event method with perf information."""
         for name, data in EVENTS_WITH_PERF_INFO:
             self.fake_eh.process_event(name, data)
@@ -266,11 +271,11 @@
             test_time='(996ms)',
             runner_total=None,
             group_total=2,
-            perf_info={}
+            additional_info={}
         ))
 
-        test_perf_info = {'cpu_time':'1234.1234(ns)', 'real_time':'5678.5678(ns)',
-                          'iterations':'6666'}
+        test_additional_info = {'cpu_time':'1234.1234(ns)', 'real_time':'5678.5678(ns)',
+                                'iterations':'6666'}
         call2 = mock.call(test_runner_base.TestResult(
             runner_name=atf_tr.AtestTradefedTestRunner.NAME,
             group_name='someTestModule',
@@ -281,9 +286,25 @@
             test_time='(2h44m36.402s)',
             runner_total=None,
             group_total=2,
-            perf_info=test_perf_info
+            additional_info=test_additional_info
         ))
-        self.mock_reporter.process_test_result.assert_has_calls([call1, call2])
+
+        test_additional_info2 = {'additional_info_min':'102773',
+                                 'additional_info_mean':'105973',
+                                 'additional_info_median':'103778'}
+        call3 = mock.call(test_runner_base.TestResult(
+            runner_name=atf_tr.AtestTradefedTestRunner.NAME,
+            group_name='someTestModule',
+            test_name='someClassName3#someTestName3',
+            status=test_runner_base.PASSED_STATUS,
+            details=None,
+            test_count=3,
+            test_time='(60ms)',
+            runner_total=None,
+            group_total=2,
+            additional_info=test_additional_info2
+        ))
+        self.mock_reporter.process_test_result.assert_has_calls([call1, call2, call3])
 
 if __name__ == '__main__':
     unittest.main()
diff --git a/atest/test_runners/robolectric_test_runner.py b/atest/test_runners/robolectric_test_runner.py
index e2de7f5..dacbc62 100644
--- a/atest/test_runners/robolectric_test_runner.py
+++ b/atest/test_runners/robolectric_test_runner.py
@@ -56,7 +56,11 @@
     def __init__(self, results_dir, **kwargs):
         """Init stuff for robolectric runner class."""
         super(RobolectricTestRunner, self).__init__(results_dir, **kwargs)
-        self.is_verbose = logging.getLogger().isEnabledFor(logging.DEBUG)
+        # TODO: Rollback when b/131301211 is fixed.
+        if not os.getenv(test_runner_base.OLD_OUTPUT_ENV_VAR):
+            self.is_verbose = True
+        else:
+            self.is_verbose = logging.getLogger().isEnabledFor(logging.DEBUG)
 
     def run_tests(self, test_infos, extra_args, reporter):
         """Run the list of test_infos. See base class for more.
@@ -69,9 +73,10 @@
         Returns:
             0 if tests succeed, non-zero otherwise.
         """
+        # TODO: Rollback when b/131301211 is fixed.
         if os.getenv(test_runner_base.OLD_OUTPUT_ENV_VAR):
-            return self.run_tests_raw(test_infos, extra_args, reporter)
-        return self.run_tests_pretty(test_infos, extra_args, reporter)
+            return self.run_tests_pretty(test_infos, extra_args, reporter)
+        return self.run_tests_raw(test_infos, extra_args, reporter)
 
     def run_tests_raw(self, test_infos, extra_args, reporter):
         """Run the list of test_infos with raw output.
diff --git a/atest/test_runners/test_runner_base.py b/atest/test_runners/test_runner_base.py
index fc2ab0c..562c32d 100644
--- a/atest/test_runners/test_runner_base.py
+++ b/atest/test_runners/test_runner_base.py
@@ -41,7 +41,7 @@
                                        'test_name', 'status', 'details',
                                        'test_count', 'test_time',
                                        'runner_total', 'group_total',
-                                       'perf_info'])
+                                       'additional_info'])
 ASSUMPTION_FAILED = 'ASSUMPTION_FAILED'
 FAILED_STATUS = 'FAILED'
 PASSED_STATUS = 'PASSED'
diff --git a/src/com/android/tradefed/device/IDeviceStateMonitor.java b/src/com/android/tradefed/device/IDeviceStateMonitor.java
index a305ce3..7a166cf 100644
--- a/src/com/android/tradefed/device/IDeviceStateMonitor.java
+++ b/src/com/android/tradefed/device/IDeviceStateMonitor.java
@@ -116,6 +116,14 @@
     public boolean waitForDeviceInRecovery(long waitTime);
 
     /**
+     * Waits for the device to be in the 'adb sideload' state
+     *
+     * @param waitTime the maximum time in ms to wait
+     * @return True if the device is in sideload before the timeout, False otherwise.
+     */
+    public boolean waitForDeviceInSideload(long waitTime);
+
+    /**
      * Gets the serial number of the device.
      */
     public String getSerialNumber();
diff --git a/src/com/android/tradefed/device/INativeDevice.java b/src/com/android/tradefed/device/INativeDevice.java
index 99a1b04..172f0bd 100644
--- a/src/com/android/tradefed/device/INativeDevice.java
+++ b/src/com/android/tradefed/device/INativeDevice.java
@@ -391,6 +391,22 @@
     public String executeAdbCommand(String... commandArgs) throws DeviceNotAvailableException;
 
     /**
+     * Helper method which executes a adb command as a system command with a specified timeout.
+     *
+     * <p>{@link #executeShellCommand(String)} should be used instead wherever possible, as that
+     * method provides better failure detection and performance.
+     *
+     * @param timeout the time in milliseconds before the device is considered unresponsive, 0L for
+     *     no timeout
+     * @param commandArgs the adb command and arguments to run
+     * @return the stdout from command. <code>null</code> if command failed to execute.
+     * @throws DeviceNotAvailableException if connection with device is lost and cannot be
+     *     recovered.
+     */
+    public String executeAdbCommand(long timeout, String... commandArgs)
+            throws DeviceNotAvailableException;
+
+    /**
      * Helper method which executes a fastboot command as a system command with a default timeout
      * of 2 minutes.
      * <p/>
@@ -880,6 +896,15 @@
     public void rebootIntoRecovery() throws DeviceNotAvailableException;
 
     /**
+     * Reboots the device into adb sideload mode (note that this is a special mode under recovery)
+     *
+     * <p>Blocks until device enters sideload mode
+     *
+     * @throws DeviceNotAvailableException if device is not in sideload after reboot
+     */
+    public void rebootIntoSideload() throws DeviceNotAvailableException;
+
+    /**
      * An alternate to {@link #reboot()} that only blocks until device is online ie visible to adb.
      *
      * @throws DeviceNotAvailableException if device is not available after reboot
@@ -1051,6 +1076,15 @@
     public boolean waitForDeviceInRecovery(final long waitTime);
 
     /**
+     * Blocks for the device to be in the 'adb sideload' state
+     *
+     * @param waitTime the time in ms to wait
+     * @return <code>true</code> if device boots into sideload before time expires. <code>false
+     *     </code> otherwise
+     */
+    public boolean waitForDeviceInSideload(final long waitTime);
+
+    /**
      * Waits for device to be responsive to a basic adb shell command.
      *
      * @param waitTime the time in ms to wait
diff --git a/src/com/android/tradefed/device/NativeDevice.java b/src/com/android/tradefed/device/NativeDevice.java
index a1d307a..f4c8f37 100644
--- a/src/com/android/tradefed/device/NativeDevice.java
+++ b/src/com/android/tradefed/device/NativeDevice.java
@@ -250,14 +250,20 @@
         /** the output from the command */
         String mOutput = null;
         private String[] mCmd;
+        private long mTimeout;
 
         AdbAction(String[] cmd) {
+            this(getCommandTimeout(), cmd);
+        }
+
+        AdbAction(long timeout, String[] cmd) {
+            mTimeout = timeout;
             mCmd = cmd;
         }
 
         @Override
         public boolean run() throws TimeoutException, IOException {
-            CommandResult result = getRunUtil().runTimedCmd(getCommandTimeout(), mCmd);
+            CommandResult result = getRunUtil().runTimedCmd(mTimeout, mCmd);
             // TODO: how to determine device not present with command failing for other reasons
             if (result.getStatus() == CommandStatus.EXCEPTION) {
                 throw new IOException();
@@ -1777,13 +1783,19 @@
      */
     @Override
     public String executeAdbCommand(String... cmdArgs) throws DeviceNotAvailableException {
+        return executeAdbCommand(getCommandTimeout(), cmdArgs);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String executeAdbCommand(long timeout, String... cmdArgs)
+            throws DeviceNotAvailableException {
         final String[] fullCmd = buildAdbCommand(cmdArgs);
-        AdbAction adbAction = new AdbAction(fullCmd);
+        AdbAction adbAction = new AdbAction(timeout, fullCmd);
         performDeviceAction(String.format("adb %s", cmdArgs[0]), adbAction, MAX_RETRY_ATTEMPTS);
         return adbAction.mOutput;
     }
 
-
     /**
      * {@inheritDoc}
      */
@@ -2913,6 +2925,24 @@
         }
     }
 
+
+    /** {@inheritDoc} */
+    @Override
+    public void rebootIntoSideload() throws DeviceNotAvailableException {
+        if (TestDeviceState.FASTBOOT == getDeviceState()) {
+            CLog.w(
+                    "device %s in fastboot when requesting boot to sideload. "
+                            + "Rebooting to userspace first.",
+                    getSerialNumber());
+            rebootUntilOnline();
+        }
+        doAdbReboot("sideload");
+        if (!waitForDeviceInSideload(mOptions.getAdbRecoveryTimeout())) {
+            // using recovery mode because sideload is a sub-mode under recovery
+            recoverDeviceInRecovery();
+        }
+    }
+
     /**
      * {@inheritDoc}
      */
@@ -3388,6 +3418,12 @@
         return mStateMonitor.waitForDeviceInRecovery(waitTime);
     }
 
+    /** {@inheritDoc} */
+    @Override
+    public boolean waitForDeviceInSideload(long waitTime) {
+        return mStateMonitor.waitForDeviceInSideload(waitTime);
+    }
+
     /**
      * Small helper function to throw an NPE if the passed arg is null.  This should be used when
      * some value will be stored and used later, in which case it'll avoid hard-to-trace
diff --git a/src/com/android/tradefed/device/NativeDeviceStateMonitor.java b/src/com/android/tradefed/device/NativeDeviceStateMonitor.java
index a107c71..43f48eb 100644
--- a/src/com/android/tradefed/device/NativeDeviceStateMonitor.java
+++ b/src/com/android/tradefed/device/NativeDeviceStateMonitor.java
@@ -154,6 +154,12 @@
         return waitForDeviceState(TestDeviceState.RECOVERY, waitTime);
     }
 
+    /** {@inheritDoc} */
+    @Override
+    public boolean waitForDeviceInSideload(long waitTime) {
+        return waitForDeviceState(TestDeviceState.SIDELOAD, waitTime);
+    }
+
     /**
      * {@inheritDoc}
      */
diff --git a/src/com/android/tradefed/device/TestDevice.java b/src/com/android/tradefed/device/TestDevice.java
index c82a8bd..8df4860 100644
--- a/src/com/android/tradefed/device/TestDevice.java
+++ b/src/com/android/tradefed/device/TestDevice.java
@@ -901,7 +901,7 @@
      */
     @Override
     protected void doAdbReboot(final String into) throws DeviceNotAvailableException {
-        if (!doAdbFrameworkReboot(into)) {
+        if (!TestDeviceState.ONLINE.equals(getDeviceState()) || !doAdbFrameworkReboot(into)) {
             super.doAdbReboot(into);
         }
     }
diff --git a/src/com/android/tradefed/device/TestDeviceState.java b/src/com/android/tradefed/device/TestDeviceState.java
index de159d9..33512e2 100644
--- a/src/com/android/tradefed/device/TestDeviceState.java
+++ b/src/com/android/tradefed/device/TestDeviceState.java
@@ -27,6 +27,7 @@
     FASTBOOT,
     ONLINE,
     RECOVERY,
+    SIDELOAD,
     NOT_AVAILABLE;
 
     /**
@@ -39,6 +40,8 @@
                 return DeviceState.ONLINE;
             case RECOVERY:
                 return DeviceState.RECOVERY;
+            case SIDELOAD:
+                return DeviceState.SIDELOAD;
             default:
                 return null;
         }
@@ -59,6 +62,8 @@
                 return TestDeviceState.NOT_AVAILABLE;
             case RECOVERY:
                 return TestDeviceState.RECOVERY;
+            case SIDELOAD:
+                return TestDeviceState.SIDELOAD;
             case UNAUTHORIZED:
                 return TestDeviceState.NOT_AVAILABLE;
             case BOOTLOADER:
diff --git a/src/com/android/tradefed/device/cloud/NestedRemoteDevice.java b/src/com/android/tradefed/device/cloud/NestedRemoteDevice.java
index 82de51c..ebf577e 100644
--- a/src/com/android/tradefed/device/cloud/NestedRemoteDevice.java
+++ b/src/com/android/tradefed/device/cloud/NestedRemoteDevice.java
@@ -31,10 +31,12 @@
 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.google.common.base.Joiner;
 
 import java.io.File;
+import java.io.IOException;
 import java.text.SimpleDateFormat;
 import java.util.Date;
 import java.util.HashMap;
@@ -180,6 +182,13 @@
             CLog.e("%s doesn't exists, skip logging it.", launcherLog.getAbsolutePath());
             return;
         }
+        try {
+            // Attempt to print the log
+            CLog.e("Launcher.log content: %s", FileUtil.readStringFromFile(launcherLog));
+        } catch (IOException e) {
+            // Ignore
+            CLog.e(e);
+        }
         try (InputStreamSource source = new FileInputStreamSource(launcherLog)) {
             logger.testLog(logName, LogDataType.TEXT, source);
         }
diff --git a/src/com/android/tradefed/device/cloud/RemoteAndroidVirtualDevice.java b/src/com/android/tradefed/device/cloud/RemoteAndroidVirtualDevice.java
index d136ab3..4ba6cfc 100644
--- a/src/com/android/tradefed/device/cloud/RemoteAndroidVirtualDevice.java
+++ b/src/com/android/tradefed/device/cloud/RemoteAndroidVirtualDevice.java
@@ -43,6 +43,8 @@
 
 import java.io.File;
 import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 
 /**
@@ -52,11 +54,6 @@
  */
 public class RemoteAndroidVirtualDevice extends RemoteAndroidDevice implements ITestLoggerReceiver {
 
-    /** The directory where to find debug logs for a nested remote instance. */
-    public static final String NESTED_REMOTE_LOG_DIR = "${HOME}/../vsoc-01/cuttlefish_runtime/";
-    /** The directory where to find debug logs for an emulator instance. */
-    public static final String EMULATOR_REMOTE_LOG_DIR = "/home/vsoc-01/log/";
-
     private String mInitialSerial;
     private GceAvdInfo mGceAvd;
     private ITestLogger mTestLogger;
@@ -69,6 +66,8 @@
     private static final long WAIT_FOR_TUNNEL_OFFLINE = 5 * 1000;
     private static final int WAIT_TIME_DIVISION = 4;
 
+    private static final long FETCH_TOMBSTONES_TIMEOUT_MS = 5 * 60 * 1000;
+
     /**
      * Creates a {@link RemoteAndroidVirtualDevice}.
      *
@@ -347,6 +346,39 @@
     }
 
     /**
+     * Cuttlefish has a special feature that brings the tombstones to the remote host where we can
+     * get them directly.
+     */
+    @Override
+    public List<File> getTombstones() throws DeviceNotAvailableException {
+        InstanceType type = getOptions().getInstanceType();
+        if (InstanceType.CUTTLEFISH.equals(type) || InstanceType.REMOTE_NESTED_AVD.equals(type)) {
+            List<File> tombs = new ArrayList<>();
+            String remoteRuntimePath =
+                    String.format(
+                                    CommonLogRemoteFileUtil.NESTED_REMOTE_LOG_DIR,
+                                    getOptions().getInstanceUser())
+                            + "tombstones";
+            File tombstonesDir =
+                    RemoteFileUtil.fetchRemoteDir(
+                            mGceAvd,
+                            getOptions(),
+                            getRunUtil(),
+                            FETCH_TOMBSTONES_TIMEOUT_MS,
+                            remoteRuntimePath);
+            if (tombstonesDir == null) {
+                CLog.e("Pulled tombstone dir was not valid. Path: %s", tombstonesDir);
+            } else {
+                // TODO: If possible delete the tombstones parent temp dir.
+                tombs.addAll(Arrays.asList(tombstonesDir.listFiles()));
+            }
+            return tombs;
+        }
+        // If it's not Cuttlefish, use the standard call.
+        return super.getTombstones();
+    }
+
+    /**
      * Returns the {@link com.android.tradefed.device.cloud.GceSshTunnelMonitor} of the device.
      * Exposed for testing.
      */
diff --git a/src/com/android/tradefed/device/cloud/RemoteFileUtil.java b/src/com/android/tradefed/device/cloud/RemoteFileUtil.java
index 21d4e1d..5330992 100644
--- a/src/com/android/tradefed/device/cloud/RemoteFileUtil.java
+++ b/src/com/android/tradefed/device/cloud/RemoteFileUtil.java
@@ -25,6 +25,7 @@
 
 import java.io.File;
 import java.io.IOException;
+import java.util.Arrays;
 import java.util.List;
 
 /** Utility class to handle file from a remote instance */
@@ -39,7 +40,7 @@
      * @param runUtil a {@link IRunUtil} to execute commands.
      * @param timeout in millisecond for the fetch to complete
      * @param remoteFilePath The remote path where to find the file.
-     * @return True if successful, False otherwise
+     * @return The pulled filed if successful, null otherwise
      */
     public static File fetchRemoteFile(
             GceAvdInfo remoteInstance,
@@ -95,6 +96,45 @@
     }
 
     /**
+     * Fetch a remote directory from the remote host.
+     *
+     * @param remoteInstance The {@link GceAvdInfo} that describe the device.
+     * @param options a {@link TestDeviceOptions} describing the device options to be used for the
+     *     GCE device.
+     * @param runUtil a {@link IRunUtil} to execute commands.
+     * @param timeout in millisecond for the fetch to complete
+     * @param remoteDirPath The remote path where to find the directory.
+     * @return The pulled directory {@link File} if successful, null otherwise
+     */
+    public static File fetchRemoteDir(
+            GceAvdInfo remoteInstance,
+            TestDeviceOptions options,
+            IRunUtil runUtil,
+            long timeout,
+            String remoteDirPath) {
+        String dirName = new File(remoteDirPath).getName();
+        File localFile = null;
+        try {
+            localFile = FileUtil.createTempDir(dirName);
+            if (internalScpExec(
+                    remoteInstance,
+                    options,
+                    Arrays.asList("-r"),
+                    runUtil,
+                    timeout,
+                    remoteDirPath,
+                    localFile,
+                    ScpMode.PULL)) {
+                return localFile;
+            }
+        } catch (IOException e) {
+            CLog.e(e);
+        }
+        FileUtil.deleteFile(localFile);
+        return null;
+    }
+
+    /**
      * Push a {@link File} from the local host to the remote instance
      *
      * @param remoteInstance The {@link GceAvdInfo} that describe the device.
diff --git a/src/com/android/tradefed/device/contentprovider/ContentProviderHandler.java b/src/com/android/tradefed/device/contentprovider/ContentProviderHandler.java
index f88a403..b6babf9 100644
--- a/src/com/android/tradefed/device/contentprovider/ContentProviderHandler.java
+++ b/src/com/android/tradefed/device/contentprovider/ContentProviderHandler.java
@@ -167,53 +167,7 @@
      */
     public boolean pullDir(String deviceFilePath, File localDir)
             throws DeviceNotAvailableException {
-        if (!localDir.isDirectory()) {
-            CLog.e("Local path %s is not a directory", localDir.getAbsolutePath());
-            return false;
-        }
-
-        String contentUri = createEscapedContentUri(deviceFilePath);
-        String queryContentCommand =
-                String.format(
-                        "content query --user %d --uri %s", mDevice.getCurrentUser(), contentUri);
-
-        String listCommandResult = mDevice.executeShellCommand(queryContentCommand);
-
-        if (listCommandResult.equals(NO_RESULTS_STRING)) {
-            // Empty directory.
-            return true;
-        }
-
-        String[] listResult = listCommandResult.split("[\\r\\n]+");
-
-        for (String row : listResult) {
-            HashMap<String, String> columnValues = parseQueryResultRow(row);
-            boolean isDirectory = Boolean.valueOf(columnValues.get(COLUMN_DIRECTORY));
-            String name = columnValues.get(COLUMN_NAME);
-            String path = columnValues.get(COLUMN_ABSOLUTE_PATH);
-
-            File localChild = new File(localDir, name);
-            if (isDirectory) {
-                if (!localChild.mkdir()) {
-                    CLog.w(
-                            "Failed to create sub directory %s, aborting.",
-                            localChild.getAbsolutePath());
-                    return false;
-                }
-
-                if (!pullDir(path, localChild)) {
-                    CLog.w("Failed to pull sub directory %s from device, aborting", path);
-                    return false;
-                }
-            } else {
-                // handle regular file
-                if (!pullFile(path, localChild)) {
-                    CLog.w("Failed to pull file %s from device, aborting", path);
-                    return false;
-                }
-            }
-        }
-        return true;
+        return pullDirInternal(deviceFilePath, localDir, /* currentUser */ null);
     }
 
     /**
@@ -227,33 +181,7 @@
      */
     public boolean pullFile(String deviceFilePath, File localFile)
             throws DeviceNotAvailableException {
-        String contentUri = createEscapedContentUri(deviceFilePath);
-        String pullCommand =
-                String.format(
-                        "content read --user %d --uri %s", mDevice.getCurrentUser(), contentUri);
-
-        // Open the output stream to the local file.
-        OutputStream localFileStream;
-        try {
-            localFileStream = new FileOutputStream(localFile);
-        } catch (FileNotFoundException e) {
-            CLog.e("Failed to open OutputStream to the local file. Error: %s", e.getMessage());
-            return false;
-        }
-
-        try {
-            CommandResult pullResult = mDevice.executeShellV2Command(pullCommand, localFileStream);
-            if (isSuccessful(pullResult)) {
-                return true;
-            }
-
-            CLog.e(
-                    "Failed to pull a file at '%s' to %s using content provider. Error: '%s'",
-                    deviceFilePath, localFile, pullResult.getStderr());
-            return false;
-        } finally {
-            StreamUtil.close(localFileStream);
-        }
+        return pullFileInternal(deviceFilePath, localFile, /* currentUser */ null);
     }
 
     /**
@@ -357,4 +285,95 @@
         }
         return columnValues;
     }
+
+    /**
+     * Internal method to actually do the pull directory but without re-querying the current user
+     * while doing the recursive pull.
+     */
+    private boolean pullDirInternal(String deviceFilePath, File localDir, Integer currentUser)
+            throws DeviceNotAvailableException {
+        if (!localDir.isDirectory()) {
+            CLog.e("Local path %s is not a directory", localDir.getAbsolutePath());
+            return false;
+        }
+
+        String contentUri = createEscapedContentUri(deviceFilePath);
+        if (currentUser == null) {
+            // Keep track of the user so if we recursively pull dir we don't re-query it.
+            currentUser = mDevice.getCurrentUser();
+        }
+        String queryContentCommand =
+                String.format("content query --user %d --uri %s", currentUser, contentUri);
+
+        String listCommandResult = mDevice.executeShellCommand(queryContentCommand);
+
+        if (NO_RESULTS_STRING.equals(listCommandResult.trim())) {
+            // Empty directory.
+            return true;
+        }
+
+        String[] listResult = listCommandResult.split("[\\r\\n]+");
+
+        for (String row : listResult) {
+            HashMap<String, String> columnValues = parseQueryResultRow(row);
+            boolean isDirectory = Boolean.valueOf(columnValues.get(COLUMN_DIRECTORY));
+            String name = columnValues.get(COLUMN_NAME);
+            String path = columnValues.get(COLUMN_ABSOLUTE_PATH);
+
+            File localChild = new File(localDir, name);
+            if (isDirectory) {
+                if (!localChild.mkdir()) {
+                    CLog.w(
+                            "Failed to create sub directory %s, aborting.",
+                            localChild.getAbsolutePath());
+                    return false;
+                }
+
+                if (!pullDirInternal(path, localChild, currentUser)) {
+                    CLog.w("Failed to pull sub directory %s from device, aborting", path);
+                    return false;
+                }
+            } else {
+                // handle regular file
+                if (!pullFileInternal(path, localChild, currentUser)) {
+                    CLog.w("Failed to pull file %s from device, aborting", path);
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+
+    private boolean pullFileInternal(String deviceFilePath, File localFile, Integer currentUser)
+            throws DeviceNotAvailableException {
+        String contentUri = createEscapedContentUri(deviceFilePath);
+        if (currentUser == null) {
+            currentUser = mDevice.getCurrentUser();
+        }
+        String pullCommand =
+                String.format("content read --user %d --uri %s", currentUser, contentUri);
+
+        // Open the output stream to the local file.
+        OutputStream localFileStream;
+        try {
+            localFileStream = new FileOutputStream(localFile);
+        } catch (FileNotFoundException e) {
+            CLog.e("Failed to open OutputStream to the local file. Error: %s", e.getMessage());
+            return false;
+        }
+
+        try {
+            CommandResult pullResult = mDevice.executeShellV2Command(pullCommand, localFileStream);
+            if (isSuccessful(pullResult)) {
+                return true;
+            }
+
+            CLog.e(
+                    "Failed to pull a file at '%s' to %s using content provider. Error: '%s'",
+                    deviceFilePath, localFile, pullResult.getStderr());
+            return false;
+        } finally {
+            StreamUtil.close(localFileStream);
+        }
+    }
 }
diff --git a/src/com/android/tradefed/device/metric/HostStatsdMetricCollector.java b/src/com/android/tradefed/device/metric/HostStatsdMetricCollector.java
index 70ea199..4f2e029 100644
--- a/src/com/android/tradefed/device/metric/HostStatsdMetricCollector.java
+++ b/src/com/android/tradefed/device/metric/HostStatsdMetricCollector.java
@@ -16,6 +16,7 @@
 package com.android.tradefed.device.metric;
 
 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;
@@ -36,6 +37,7 @@
  * commands. It has basic push metrics and dump report functions. It can be extended by subclasses
  * to process statsd metric report based on the needs.
  */
+@OptionClass(alias = "host-statsd-collector")
 public class HostStatsdMetricCollector extends BaseDeviceMetricCollector {
 
     @Option(name = "binary-stats-config", description = "Path to the binary Statsd config file")
diff --git a/src/com/android/tradefed/device/metric/LogcatOnFailureCollector.java b/src/com/android/tradefed/device/metric/LogcatOnFailureCollector.java
index 33be39b..3dcc717 100644
--- a/src/com/android/tradefed/device/metric/LogcatOnFailureCollector.java
+++ b/src/com/android/tradefed/device/metric/LogcatOnFailureCollector.java
@@ -82,7 +82,9 @@
 
     @VisibleForTesting
     ILogcatReceiver createLogcatReceiver(ITestDevice device) {
-        return new LogcatReceiver(device, "logcat", device.getOptions().getMaxLogcatDataSize(), 0);
+        // Use logcat -T 'count' to only print a few line before we start and not the full buffer
+        return new LogcatReceiver(
+                device, "logcat -T 150", device.getOptions().getMaxLogcatDataSize(), 0);
     }
 
     @VisibleForTesting
diff --git a/src/com/android/tradefed/result/proto/FileProtoResultReporter.java b/src/com/android/tradefed/result/proto/FileProtoResultReporter.java
index f72abf4..3bb8f03 100644
--- a/src/com/android/tradefed/result/proto/FileProtoResultReporter.java
+++ b/src/com/android/tradefed/result/proto/FileProtoResultReporter.java
@@ -16,6 +16,7 @@
 package com.android.tradefed.result.proto;
 
 import com.android.tradefed.config.Option;
+import com.android.tradefed.invoker.IInvocationContext;
 import com.android.tradefed.log.LogUtil.CLog;
 import com.android.tradefed.result.proto.TestRecordProto.TestRecord;
 
@@ -33,18 +34,74 @@
     )
     private File mOutputFile;
 
+    @Option(
+        name = "periodic-proto-writing",
+        description =
+                "Whether or not to output intermediate proto per module following a numbered "
+                        + "sequence."
+    )
+    private boolean mPeriodicWriting = false;
+
+    // Current index of the sequence of proto output
+    private int mIndex = 0;
+    private File mCurrentOutputFile;
+
+    @Override
+    public void processStartInvocation(
+            TestRecord invocationStartRecord, IInvocationContext invocationContext) {
+        writeProto(invocationStartRecord);
+    }
+
+    @Override
+    public void processTestModuleEnd(TestRecord moduleRecord) {
+        writeProto(moduleRecord);
+    }
+
+    @Override
+    public void processTestRunEnded(TestRecord runRecord, boolean moduleInProgress) {
+        if (!moduleInProgress) {
+            // If it's a testRun outside of the module scope, output it to ensure we support
+            // non-module use cases.
+            writeProto(runRecord);
+        }
+    }
+
     @Override
     public void processFinalProto(TestRecord finalRecord) {
+        writeProto(finalRecord);
+    }
+
+    /** Sets the file where to output the result. */
+    public void setFileOutput(File output) {
+        mOutputFile = output;
+        if (mPeriodicWriting) {
+            mCurrentOutputFile = new File(mOutputFile.getAbsolutePath() + mIndex);
+        }
+    }
+
+    /** Enable writing each module individualy to a file. */
+    public void setPeriodicWriting(boolean enabled) {
+        mPeriodicWriting = enabled;
+    }
+
+    private void writeProto(TestRecord record) {
+        File outputFile = mOutputFile;
+        if (mPeriodicWriting) {
+            outputFile = mCurrentOutputFile;
+        }
         try {
-            finalRecord.writeDelimitedTo(new FileOutputStream(mOutputFile));
+            record.writeDelimitedTo(new FileOutputStream(outputFile));
+            if (mPeriodicWriting) {
+                nextOutputFile();
+            }
         } catch (IOException e) {
             CLog.e(e);
             throw new RuntimeException(e);
         }
     }
 
-    /** Sets the file where to output the result. */
-    public void setFileOutput(File output) {
-        mOutputFile = output;
+    private void nextOutputFile() {
+        mIndex++;
+        mCurrentOutputFile = new File(mOutputFile.getAbsolutePath() + mIndex);
     }
 }
diff --git a/src/com/android/tradefed/result/proto/ProtoResultParser.java b/src/com/android/tradefed/result/proto/ProtoResultParser.java
index 8f5ecd6..50e4340 100644
--- a/src/com/android/tradefed/result/proto/ProtoResultParser.java
+++ b/src/com/android/tradefed/result/proto/ProtoResultParser.java
@@ -32,6 +32,7 @@
 import com.android.tradefed.result.proto.TestRecordProto.ChildReference;
 import com.android.tradefed.result.proto.TestRecordProto.TestRecord;
 import com.android.tradefed.testtype.suite.ModuleDefinition;
+import com.android.tradefed.util.proto.TestRecordProtoUtil;
 
 import com.google.common.base.Strings;
 import com.google.protobuf.Any;
@@ -39,6 +40,7 @@
 import com.google.protobuf.Timestamp;
 
 import java.io.File;
+import java.io.IOException;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.util.HashMap;
@@ -62,6 +64,8 @@
 
     private boolean mQuietParsing = true;
 
+    private boolean mInvocationStarted = false;
+
     /** Ctor. */
     public ProtoResultParser(
             ITestInvocationListener listener,
@@ -108,7 +112,7 @@
         // Invocation Start
         handleInvocationStart(finalProto);
 
-        evalProto(finalProto.getChildrenList(), false);
+        evalChildrenProto(finalProto.getChildrenList(), false);
         // Invocation End
         handleInvocationEnded(finalProto);
     }
@@ -140,33 +144,56 @@
         }
     }
 
-    private void evalProto(List<ChildReference> children, boolean isInRun) {
+    /**
+     * In case of parsing proto files directly, handle direct parsing of them as a sequence.
+     * Associated with {@link FileProtoResultReporter} when reporting a sequence of files.
+     *
+     * @param protoFile The proto file to be parsed.
+     * @throws IOException
+     */
+    public void processFileProto(File protoFile) throws IOException {
+        TestRecord record = TestRecordProtoUtil.readFromFile(protoFile);
+        if (!mInvocationStarted) {
+            handleInvocationStart(record);
+            mInvocationStarted = true;
+        } else if (record.getParentTestRecordId().isEmpty()) {
+            handleInvocationEnded(record);
+        } else {
+            evalProto(record, false);
+        }
+    }
+
+    private void evalChildrenProto(List<ChildReference> children, boolean isInRun) {
         for (ChildReference child : children) {
             TestRecord childProto = child.getInlineTestRecord();
-            if (isInRun) {
-                // test case
-                String[] info = childProto.getTestRecordId().split("#");
-                TestDescription description = new TestDescription(info[0], info[1]);
-                mListener.testStarted(description, timeStampToMillis(childProto.getStartTime()));
-                handleTestCaseEnd(description, childProto);
+            evalProto(childProto, isInRun);
+        }
+    }
+
+    private void evalProto(TestRecord childProto, boolean isInRun) {
+        if (isInRun) {
+            // test case
+            String[] info = childProto.getTestRecordId().split("#");
+            TestDescription description = new TestDescription(info[0], info[1]);
+            mListener.testStarted(description, timeStampToMillis(childProto.getStartTime()));
+            handleTestCaseEnd(description, childProto);
+        } else {
+            boolean inRun = false;
+            if (childProto.hasDescription()) {
+                // Module start
+                handleModuleStart(childProto);
             } else {
-                boolean inRun = false;
-                if (childProto.hasDescription()) {
-                    // Module start
-                    handleModuleStart(childProto);
-                } else {
-                    // run start
-                    handleTestRunStart(childProto);
-                    inRun = true;
-                }
-                evalProto(childProto.getChildrenList(), inRun);
-                if (childProto.hasDescription()) {
-                    // Module end
-                    handleModuleProto(childProto);
-                } else {
-                    // run end
-                    handleTestRunEnd(childProto);
-                }
+                // run start
+                handleTestRunStart(childProto);
+                inRun = true;
+            }
+            evalChildrenProto(childProto.getChildrenList(), inRun);
+            if (childProto.hasDescription()) {
+                // Module end
+                handleModuleProto(childProto);
+            } else {
+                // run end
+                handleTestRunEnd(childProto);
             }
         }
     }
diff --git a/src/com/android/tradefed/result/proto/ProtoResultReporter.java b/src/com/android/tradefed/result/proto/ProtoResultReporter.java
index 8fa6c35..227f518 100644
--- a/src/com/android/tradefed/result/proto/ProtoResultReporter.java
+++ b/src/com/android/tradefed/result/proto/ProtoResultReporter.java
@@ -100,8 +100,9 @@
      * occurred.
      *
      * @param runRecord The finalized proto representing the run.
+     * @param moduleInProgress whether or not a module is in progress.
      */
-    public void processTestRunEnded(TestRecord runRecord) {}
+    public void processTestRunEnded(TestRecord runRecord, boolean moduleInProgress) {}
 
     /**
      * Handling of the partial test case record proto after {@link #testStarted(TestDescription,
@@ -286,7 +287,7 @@
         TestRecord runRecord = runBuilder.build();
         parentBuilder.addChildren(createChildReference(runRecord));
         try {
-            processTestRunEnded(runRecord);
+            processTestRunEnded(runRecord, mModuleInProgress);
         } catch (RuntimeException e) {
             CLog.e("Failed to process test run end:");
             CLog.e(e);
diff --git a/src/com/android/tradefed/result/proto/StreamProtoResultReporter.java b/src/com/android/tradefed/result/proto/StreamProtoResultReporter.java
index 2fd1b1b..46c0e47 100644
--- a/src/com/android/tradefed/result/proto/StreamProtoResultReporter.java
+++ b/src/com/android/tradefed/result/proto/StreamProtoResultReporter.java
@@ -59,7 +59,7 @@
     }
 
     @Override
-    public void processTestRunEnded(TestRecord runRecord) {
+    public void processTestRunEnded(TestRecord runRecord, boolean moduleInProgress) {
         writeRecordToSocket(runRecord);
     }
 
diff --git a/src/com/android/tradefed/result/suite/XmlSuiteResultFormatter.java b/src/com/android/tradefed/result/suite/XmlSuiteResultFormatter.java
index 6544565..1aa9997 100644
--- a/src/com/android/tradefed/result/suite/XmlSuiteResultFormatter.java
+++ b/src/com/android/tradefed/result/suite/XmlSuiteResultFormatter.java
@@ -15,6 +15,7 @@
  */
 package com.android.tradefed.result.suite;
 
+import com.android.annotations.VisibleForTesting;
 import com.android.ddmlib.testrunner.TestResult.TestStatus;
 import com.android.tradefed.invoker.IInvocationContext;
 import com.android.tradefed.invoker.InvocationContext;
@@ -53,6 +54,8 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
 import java.util.Date;
 import java.util.HashMap;
 import java.util.LinkedHashMap;
@@ -266,8 +269,9 @@
         serializer.attribute(NS, MODULES_TOTAL_ATTR, Integer.toString(holder.totalModules));
         serializer.endTag(NS, SUMMARY_TAG);
 
+        List<TestRunResult> sortedModuleList = sortModules(holder.runResults, holder.modulesAbi);
         // Results
-        for (TestRunResult module : holder.runResults) {
+        for (TestRunResult module : sortedModuleList) {
             serializer.startTag(NS, MODULE_TAG);
             // To be compatible of CTS strip the abi from the module name when available.
             if (holder.modulesAbi.get(module.getName()) != null) {
@@ -521,6 +525,40 @@
         return invocation;
     }
 
+    /** Sort the list of results based on their name without abi primarily then secondly on abi. */
+    @VisibleForTesting
+    List<TestRunResult> sortModules(
+            Collection<TestRunResult> results, Map<String, IAbi> moduleAbis) {
+        List<TestRunResult> sortedList = new ArrayList<>(results);
+        Collections.sort(
+                sortedList,
+                new Comparator<TestRunResult>() {
+                    @Override
+                    public int compare(TestRunResult o1, TestRunResult o2) {
+                        String module1NameStripped = o1.getName();
+                        String module1Abi = "";
+                        if (moduleAbis.get(module1NameStripped) != null) {
+                            module1Abi = moduleAbis.get(module1NameStripped).getName();
+                            module1NameStripped = module1NameStripped.replace(module1Abi + " ", "");
+                        }
+
+                        String module2NameStripped = o2.getName();
+                        String module2Abi = "";
+                        if (moduleAbis.get(module2NameStripped) != null) {
+                            module2Abi = moduleAbis.get(module2NameStripped).getName();
+                            module2NameStripped = module2NameStripped.replace(module2Abi + " ", "");
+                        }
+                        int res = module1NameStripped.compareTo(module2NameStripped);
+                        if (res != 0) {
+                            return res;
+                        }
+                        // Use the Abi as discriminant to always sort abi in the same order.
+                        return module1Abi.compareTo(module2Abi);
+                    }
+                });
+        return sortedList;
+    }
+
     /** Handle the parsing and replay of all run history information. */
     private void handleRunHistoryLevel(XmlPullParser parser)
             throws IOException, XmlPullParserException {
diff --git a/src/com/android/tradefed/targetprep/SideloadOtaTargetPreparer.java b/src/com/android/tradefed/targetprep/SideloadOtaTargetPreparer.java
new file mode 100644
index 0000000..836c4a8
--- /dev/null
+++ b/src/com/android/tradefed/targetprep/SideloadOtaTargetPreparer.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tradefed.targetprep;
+
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.config.Option;
+import com.android.tradefed.config.OptionClass;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.log.LogUtil.CLog;
+
+import java.io.File;
+
+/**
+ * A target preparer that performs sideload of a specified OTA package, applies the package, waits
+ * for device to boot up, and injects the device build properties to use as build info
+ *
+ * <p>This target preparer assumes that the device will be in regular adb mode when started, and
+ * will ensure that the device exits in the same mode but with the newer build applied. Any
+ * unexpected device state transition during the process will be reported as {@link
+ * TargetSetupError}, and same applies to any OTA sideload error detected.
+ */
+@OptionClass(alias = "sideload-ota")
+public class SideloadOtaTargetPreparer extends DeviceBuildInfoInjector {
+
+    private static final String SIDELOAD_CMD = "sideload";
+    // the timeout for state transition from sideload finishes to recovery mode, not making this
+    // an option because it should be a transient state: 10s is already very generous
+    private static final long POST_SIDELOAD_TRANSITION_TIMEOUT = 10 * 1000;
+
+    @Option(name = "sideload-ota-package", description = "the OTA package to be sideloaded")
+    private File mSideloadOtaPackage = null;
+
+    @Option(
+        name = "sideload-timeout",
+        description = "timeout for sideloading the OTA package",
+        isTimeVal = true
+    )
+    // defaults to 10m: assuming USB 2.0 transfer speed, concurrency and some buffer
+    private long mSideloadTimeout = 10 * 60 * 1000;
+
+    @Option(
+        name = "inject-build-info",
+        description =
+                "whether build info should be injected "
+                        + "based on device attributes after sideloading"
+    )
+    private boolean mInjectBuildInfo = true;
+
+    @Override
+    public void setUp(ITestDevice device, IBuildInfo buildInfo)
+            throws TargetSetupError, BuildError, DeviceNotAvailableException {
+        if (mSideloadOtaPackage == null) {
+            CLog.i("No sideload file provided, assuming no-op; skipping ...");
+            return;
+        }
+        device.rebootIntoSideload();
+        String filePath = mSideloadOtaPackage.getAbsolutePath();
+        CLog.d("Sideloading package from %s ...", filePath);
+        device.executeAdbCommand(mSideloadTimeout, SIDELOAD_CMD, filePath);
+        // after applying sideload, device should transition to recovery mode
+        device.waitForDeviceInRecovery(POST_SIDELOAD_TRANSITION_TIMEOUT);
+        // now reboot and wait for the device to become available
+        device.reboot();
+        // calling this last because we want to inject device side build info after device boots up
+        if (mInjectBuildInfo) {
+            super.setUp(device, buildInfo);
+        }
+    }
+}
diff --git a/src/com/android/tradefed/testtype/AndroidJUnitTest.java b/src/com/android/tradefed/testtype/AndroidJUnitTest.java
index bc70bb6..adee140 100644
--- a/src/com/android/tradefed/testtype/AndroidJUnitTest.java
+++ b/src/com/android/tradefed/testtype/AndroidJUnitTest.java
@@ -145,11 +145,14 @@
     private Integer mMaxShard = null;
 
     @Option(
-            name = "device-listeners",
-            description =
-                    "Specify a device side instrumentation listener to be added for the run. "
-                            + "Can be repeated.")
-    private Set<String> mExtraDeviceListeners = new HashSet<>();
+        name = "device-listeners",
+        description =
+                "Specify device side instrumentation listeners to be added for the run. "
+                        + "Can be repeated. Note that while the ordering here is followed for "
+                        + "now, future versions of AndroidJUnitRunner might not preserve the "
+                        + "listener ordering."
+    )
+    private List<String> mExtraDeviceListeners = new ArrayList<>();
 
     @Option(
         name = "use-new-run-listener-order",
@@ -329,6 +332,8 @@
             mDeviceIncludeFile = mTestFilterDir.replaceAll("/$", "") + "/" + INCLUDE_FILE;
             pushTestFile(mIncludeTestFile, mDeviceIncludeFile, listener);
             pushedFile = true;
+            // If an explicit include file filter is provided, do not use the package
+            setTestPackageName(null);
         }
 
         // if mExcludeTestFile is set, perform filtering with this file
diff --git a/src/com/android/tradefed/testtype/GTest.java b/src/com/android/tradefed/testtype/GTest.java
index d81ec95..a3a4907 100644
--- a/src/com/android/tradefed/testtype/GTest.java
+++ b/src/com/android/tradefed/testtype/GTest.java
@@ -226,6 +226,26 @@
         return sb.toString();
     }
 
+    @Override
+    protected String createFlagFile(String filter) throws DeviceNotAvailableException {
+        String flagPath = super.createFlagFile(filter);
+        if (flagPath == null) {
+            // Return null to fall back to base filter
+            return null;
+        }
+        File flagFile = new File(flagPath);
+        String devicePath = "/data/local/tmp/" + flagFile.getName();
+        try {
+            if (!mDevice.pushFile(flagFile, devicePath)) {
+                // Failed to push flagfile, return null to fall back to base filter
+                return null;
+            }
+        } finally {
+            FileUtil.deleteFile(flagFile);
+        }
+        return devicePath;
+    }
+
     /**
      * Run the given gtest binary
      *
diff --git a/src/com/android/tradefed/testtype/GTestBase.java b/src/com/android/tradefed/testtype/GTestBase.java
index a218aed..7766621 100644
--- a/src/com/android/tradefed/testtype/GTestBase.java
+++ b/src/com/android/tradefed/testtype/GTestBase.java
@@ -24,9 +24,12 @@
 import com.android.tradefed.log.LogUtil.CLog;
 import com.android.tradefed.result.ITestInvocationListener;
 import com.android.tradefed.util.ArrayUtil;
+import com.android.tradefed.util.FileUtil;
 
 import com.google.common.annotations.VisibleForTesting;
 
+import java.io.File;
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.LinkedHashSet;
@@ -158,6 +161,7 @@
     protected static final String GTEST_FLAG_FILTER = "--gtest_filter";
     protected static final String GTEST_FLAG_RUN_DISABLED_TESTS = "--gtest_also_run_disabled_tests";
     protected static final String GTEST_FLAG_LIST_TESTS = "--gtest_list_tests";
+    protected static final String GTEST_FLAG_FILE = "--gtest_flagfile";
     protected static final String GTEST_XML_OUTPUT = "--gtest_output=xml:%s";
     // Expected extension for the filter file associated with the binary (json formatted file)
     @VisibleForTesting protected static final String FILTER_EXTENSION = ".filter";
@@ -399,7 +403,37 @@
                 }
             }
         }
-        return filter.toString();
+        String filterFlag = filter.toString();
+        // Handle long args
+        if (filterFlag.length() > 500) {
+            String tmpFlag = createFlagFile(filterFlag);
+            if (tmpFlag != null) {
+                return String.format("%s=%s", GTEST_FLAG_FILE, tmpFlag);
+            }
+        }
+
+        return filterFlag;
+    }
+
+    /**
+     * Create a file containing the filters that will be used via --gtest_flagfile to avoid any OS
+     * limitation in args size.
+     *
+     * @param filter The filter string
+     * @return The path to the file containing the filter.
+     * @throws DeviceNotAvailableException
+     */
+    protected String createFlagFile(String filter) throws DeviceNotAvailableException {
+        File tmpFlagFile = null;
+        try {
+            tmpFlagFile = FileUtil.createTempFile("flagfile", ".txt");
+            FileUtil.writeToFile(filter, tmpFlagFile);
+        } catch (IOException e) {
+            FileUtil.deleteFile(tmpFlagFile);
+            CLog.e(e);
+            return null;
+        }
+        return tmpFlagFile.getAbsolutePath();
     }
 
     /**
diff --git a/src/com/android/tradefed/testtype/InstrumentationTest.java b/src/com/android/tradefed/testtype/InstrumentationTest.java
index 99f8441..f468fdc 100644
--- a/src/com/android/tradefed/testtype/InstrumentationTest.java
+++ b/src/com/android/tradefed/testtype/InstrumentationTest.java
@@ -56,7 +56,6 @@
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
@@ -298,7 +297,7 @@
 
     private ListInstrumentationParser mListInstrumentationParser = null;
 
-    private Set<String> mExtraDeviceListener = new HashSet<>();
+    private List<String> mExtraDeviceListener = new ArrayList<>();
 
     private boolean mIsRerun = false;
 
@@ -635,7 +634,7 @@
     }
 
     /** Allows to add more custom listeners to the runner */
-    public void addDeviceListeners(Set<String> extraListeners) {
+    public void addDeviceListeners(List<String> extraListeners) {
         mExtraDeviceListener.addAll(extraListeners);
     }
 
diff --git a/src/com/android/tradefed/testtype/suite/GranularRetriableTestWrapper.java b/src/com/android/tradefed/testtype/suite/GranularRetriableTestWrapper.java
index 946d878..4b243ed 100644
--- a/src/com/android/tradefed/testtype/suite/GranularRetriableTestWrapper.java
+++ b/src/com/android/tradefed/testtype/suite/GranularRetriableTestWrapper.java
@@ -19,6 +19,8 @@
 import com.android.tradefed.config.IConfiguration;
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.device.DeviceUnresponsiveException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.device.StubDevice;
 import com.android.tradefed.device.metric.CollectorHelper;
 import com.android.tradefed.device.metric.IMetricCollector;
 import com.android.tradefed.device.metric.IMetricCollectorReceiver;
@@ -95,6 +97,7 @@
     private Map<String, Integer> mAttemptSuccess = new HashMap<>();
 
     private RetryStrategy mRetryStrategy = RetryStrategy.RETRY_TEST_CASE_FAILURE;
+    private boolean mRebootAtLastRetry = false;
 
     public GranularRetriableTestWrapper(
             IRemoteTest test,
@@ -172,6 +175,11 @@
         mRetryStrategy = retryStrategy;
     }
 
+    /** Sets the flag to reboot devices at the last intra-module retry. */
+    public final void setRebootAtLastRetry(boolean rebootAtLastRetry) {
+        mRebootAtLastRetry = rebootAtLastRetry;
+    }
+
     /**
      * Initialize a new {@link ModuleListener} for each test run.
      *
@@ -293,7 +301,16 @@
                         break;
                     }
                 }
-
+                // Reboot device at the last intra-module retry if reboot-at-last-retry is set.
+                if (mRebootAtLastRetry && (attemptNumber == (mMaxRunLimit-1))) {
+                    for (ITestDevice device : mModuleInvocationContext.getDevices()) {
+                        if (!(device.getIDevice() instanceof StubDevice)) {
+                            CLog.i("Rebooting device: %s at the last intra-module retry.",
+                                    device.getSerialNumber());
+                            device.reboot();
+                        }
+                    }
+                }
                 // Run the tests again
                 intraModuleRun(allListeners);
 
diff --git a/src/com/android/tradefed/testtype/suite/ITestSuite.java b/src/com/android/tradefed/testtype/suite/ITestSuite.java
index 5021b10..faa79be 100644
--- a/src/com/android/tradefed/testtype/suite/ITestSuite.java
+++ b/src/com/android/tradefed/testtype/suite/ITestSuite.java
@@ -178,6 +178,11 @@
     @Option(name = "reboot-per-module", description = "Reboot the device before every module run.")
     private boolean mRebootPerModule = false;
 
+    @Deprecated
+    @Option(name = "reboot-at-last-retry",
+        description = "Reboot the device at the last intra-module retry")
+    private boolean mRebootAtLastRetry = false;
+
     @Option(name = "skip-all-system-status-check",
             description = "Whether all system status check between modules should be skipped")
     private boolean mSkipAllSystemStatusCheck = false;
@@ -199,14 +204,12 @@
     )
     private boolean mReportSystemChecker = false;
 
-    @Deprecated
     @Option(
         name = "random-order",
         description = "Whether randomizing the order of the modules to be ran or not."
     )
     private boolean mRandomOrder = false;
 
-    @Deprecated
     @Option(
         name = "random-seed",
         description = "Seed to randomize the order of the modules."
@@ -680,6 +683,8 @@
         module.setLogSaver(mMainConfiguration.getLogSaver());
         // Pass the retry strategy to the module
         module.setRetryStrategy(mRetryStrategy, mMergeAttempts);
+        // Pass the reboot strategy at the last intra-module retry to the module
+        module.setRebootAtLastRetry(mRebootAtLastRetry);
 
         // Actually run the module
         module.run(listener, moduleListeners, failureListener, mMaxRunLimit);
diff --git a/src/com/android/tradefed/testtype/suite/ModuleDefinition.java b/src/com/android/tradefed/testtype/suite/ModuleDefinition.java
index 117e120..45b78b0 100644
--- a/src/com/android/tradefed/testtype/suite/ModuleDefinition.java
+++ b/src/com/android/tradefed/testtype/suite/ModuleDefinition.java
@@ -143,6 +143,7 @@
 
     private RetryStrategy mRetryStrategy = RetryStrategy.RETRY_TEST_CASE_FAILURE;
     private boolean mMergeAttempts = true;
+    private boolean mRebootAtLastRetry = false;
 
     // Token during sharding
     private Set<TokenProperty> mRequiredTokens = new HashSet<>();
@@ -590,6 +591,7 @@
         retriableTest.setInvocationContext(mModuleInvocationContext);
         retriableTest.setLogSaver(mLogSaver);
         retriableTest.setRetryStrategy(mRetryStrategy);
+        retriableTest.setRebootAtLastRetry(mRebootAtLastRetry);
         return retriableTest;
     }
 
@@ -873,6 +875,11 @@
         mMergeAttempts = mergeAttempts;
     }
 
+    /** Sets the flag to reboot devices at the last intra-module retry. */
+    public final void setRebootAtLastRetry(boolean rebootAtLastRetry) {
+        mRebootAtLastRetry = rebootAtLastRetry;
+    }
+
     /** Returns a list of tests that ran in this module. */
     List<TestRunResult> getTestsResults() {
         return mTestsResults;
diff --git a/src/com/android/tradefed/testtype/suite/retry/ResultsPlayer.java b/src/com/android/tradefed/testtype/suite/retry/ResultsPlayer.java
index 3627286..3b7f914 100644
--- a/src/com/android/tradefed/testtype/suite/retry/ResultsPlayer.java
+++ b/src/com/android/tradefed/testtype/suite/retry/ResultsPlayer.java
@@ -135,7 +135,7 @@
             TestRunResult module,
             Collection<Entry<TestDescription, TestResult>> testSet,
             ITestInvocationListener listener) {
-        listener.testRunStarted(module.getName(), module.getNumTests());
+        listener.testRunStarted(module.getName(), testSet.size());
         for (Map.Entry<TestDescription, TestResult> testEntry : testSet) {
             listener.testStarted(testEntry.getKey(), testEntry.getValue().getStartTime());
             switch (testEntry.getValue().getStatus()) {
diff --git a/src/com/android/tradefed/util/StringEscapeUtils.java b/src/com/android/tradefed/util/StringEscapeUtils.java
index 1b7ea44..846c0e7 100644
--- a/src/com/android/tradefed/util/StringEscapeUtils.java
+++ b/src/com/android/tradefed/util/StringEscapeUtils.java
@@ -74,7 +74,7 @@
             // this may lead to incorrect results such as \n -> n, or \t -> t, but it's unclear why
             // a command line param would have \t anyways
             param = param.replaceAll("\\\\(.)", "$1");
-            String[] args = QuotationAwareTokenizer.tokenizeLine(param);
+            String[] args = QuotationAwareTokenizer.tokenizeLine(param, /* Logging */ false);
             if (args.length != 0) {
                 result.addAll(Arrays.asList(args));
             }
diff --git a/tests/src/com/android/tradefed/device/DeviceStateMonitorTest.java b/tests/src/com/android/tradefed/device/DeviceStateMonitorTest.java
index 3aced89..1a76fa6 100644
--- a/tests/src/com/android/tradefed/device/DeviceStateMonitorTest.java
+++ b/tests/src/com/android/tradefed/device/DeviceStateMonitorTest.java
@@ -790,4 +790,14 @@
         };
         assertNull(mMonitor.waitForDeviceAvailable(WAIT_TIMEOUT_REACHED_MS));
     }
+
+    /** Test {@link DeviceStateMonitor#waitForDeviceInSideload(long)} */
+    public void testWaitForDeviceInSideload() throws Exception {
+        mMockDevice = EasyMock.createMock(IDevice.class);
+        EasyMock.expect(mMockDevice.getState()).andReturn(DeviceState.SIDELOAD).anyTimes();
+        EasyMock.expect(mMockDevice.getSerialNumber()).andReturn(SERIAL_NUMBER).anyTimes();
+        EasyMock.replay(mMockDevice);
+        mMonitor = new DeviceStateMonitor(mMockMgr, mMockDevice, true);
+        assertTrue(mMonitor.waitForDeviceInSideload(WAIT_TIMEOUT_NOT_REACHED_MS));
+    }
 }
diff --git a/tests/src/com/android/tradefed/device/NativeDeviceTest.java b/tests/src/com/android/tradefed/device/NativeDeviceTest.java
index 27d5ce6..091fc5a 100644
--- a/tests/src/com/android/tradefed/device/NativeDeviceTest.java
+++ b/tests/src/com/android/tradefed/device/NativeDeviceTest.java
@@ -1643,6 +1643,26 @@
         EasyMock.verify(mMockIDevice, mMockStateMonitor, mMockDvcMonitor);
     }
 
+    /** Unit test for {@link NativeDevice#rebootIntoSideload()}}. */
+    @Test
+    public void testRebootIntoSideload() throws Exception {
+        NativeDevice testDevice =
+                new NativeDevice(mMockIDevice, mMockStateMonitor, mMockDvcMonitor) {
+                    @Override
+                    public TestDeviceState getDeviceState() {
+                        return TestDeviceState.ONLINE;
+                    }
+                };
+        String into = "sideload";
+        mMockIDevice.reboot(into);
+        EasyMock.expectLastCall();
+        EasyMock.expect(mMockStateMonitor.waitForDeviceInSideload(EasyMock.anyLong()))
+                .andReturn(true);
+        EasyMock.replay(mMockIDevice, mMockStateMonitor, mMockDvcMonitor);
+        testDevice.rebootIntoSideload();
+        EasyMock.verify(mMockIDevice, mMockStateMonitor, mMockDvcMonitor);
+    }
+
     /** Unit test for {@link NativeDevice#unlockDevice()} already decrypted. */
     @Test
     public void testUnlockDevice_skipping() throws Exception {
diff --git a/tests/src/com/android/tradefed/device/cloud/RemoteFileUtilTest.java b/tests/src/com/android/tradefed/device/cloud/RemoteFileUtilTest.java
index 4113359..a6886b5 100644
--- a/tests/src/com/android/tradefed/device/cloud/RemoteFileUtilTest.java
+++ b/tests/src/com/android/tradefed/device/cloud/RemoteFileUtilTest.java
@@ -113,6 +113,42 @@
         EasyMock.verify(mMockRunUtil);
     }
 
+    /** Test pulling a directory from the remote hosts. */
+    @Test
+    public void testFetchRemoteDir() throws Exception {
+        GceAvdInfo fakeInfo = new GceAvdInfo("ins-gce", HostAndPort.fromHost("127.0.0.1"));
+        String remotePath = "/home/vsoc-01/cuttlefish_runtime/tombstones";
+        CommandResult res = new CommandResult(CommandStatus.SUCCESS);
+        EasyMock.expect(
+                        mMockRunUtil.runTimedCmd(
+                                EasyMock.anyLong(),
+                                EasyMock.eq("scp"),
+                                EasyMock.eq("-o"),
+                                EasyMock.eq("UserKnownHostsFile=/dev/null"),
+                                EasyMock.eq("-o"),
+                                EasyMock.eq("StrictHostKeyChecking=no"),
+                                EasyMock.eq("-o"),
+                                EasyMock.eq("ServerAliveInterval=10"),
+                                EasyMock.eq("-i"),
+                                EasyMock.anyObject(),
+                                EasyMock.eq("-r"),
+                                EasyMock.eq("root@127.0.0.1:" + remotePath),
+                                EasyMock.anyObject()))
+                .andReturn(res);
+        EasyMock.replay(mMockRunUtil);
+        File resDir = null;
+        try {
+            resDir =
+                    RemoteFileUtil.fetchRemoteDir(
+                            fakeInfo, mOptions, mMockRunUtil, 500L, remotePath);
+            // The original remote name is used.
+            assertTrue(resDir.isDirectory());
+        } finally {
+            FileUtil.recursiveDelete(resDir);
+        }
+        EasyMock.verify(mMockRunUtil);
+    }
+
     /** Test pushing a file to a remote instance via scp. */
     @Test
     public void testPushFileToRemote() throws Exception {
diff --git a/tests/src/com/android/tradefed/device/contentprovider/ContentProviderHandlerTest.java b/tests/src/com/android/tradefed/device/contentprovider/ContentProviderHandlerTest.java
index df5ad01..88b0d45 100644
--- a/tests/src/com/android/tradefed/device/contentprovider/ContentProviderHandlerTest.java
+++ b/tests/src/com/android/tradefed/device/contentprovider/ContentProviderHandlerTest.java
@@ -230,7 +230,7 @@
     public void testPullDir_EmptyDirectory() throws Exception {
         File pullTo = FileUtil.createTempDir("content-provider-test");
 
-        doReturn("No result found.").when(mMockDevice).executeShellCommand(anyString());
+        doReturn("No result found.\n").when(mMockDevice).executeShellCommand(anyString());
 
         try {
             assertTrue(mProvider.pullDir("path/somewhere", pullTo));
diff --git a/tests/src/com/android/tradefed/result/proto/FileProtoResultReporterTest.java b/tests/src/com/android/tradefed/result/proto/FileProtoResultReporterTest.java
index 591a17b..db48f97 100644
--- a/tests/src/com/android/tradefed/result/proto/FileProtoResultReporterTest.java
+++ b/tests/src/com/android/tradefed/result/proto/FileProtoResultReporterTest.java
@@ -17,17 +17,23 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 import com.android.tradefed.config.ConfigurationDescriptor;
 import com.android.tradefed.invoker.IInvocationContext;
 import com.android.tradefed.invoker.InvocationContext;
 import com.android.tradefed.invoker.proto.InvocationContext.Context;
+import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
+import com.android.tradefed.result.ITestInvocationListener;
+import com.android.tradefed.result.TestDescription;
 import com.android.tradefed.result.proto.TestRecordProto.TestRecord;
+import com.android.tradefed.testtype.suite.ModuleDefinition;
 import com.android.tradefed.util.FileUtil;
 import com.android.tradefed.util.proto.TestRecordProtoUtil;
 
 import com.google.protobuf.Any;
 
+import org.easymock.EasyMock;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -35,6 +41,9 @@
 import org.junit.runners.JUnit4;
 
 import java.io.File;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
 
 /** Unit tests for {@link FileProtoResultReporter}. */
 @RunWith(JUnit4.class)
@@ -42,17 +51,23 @@
 
     private FileProtoResultReporter mReporter;
     private File mOutput;
+    private List<File> mToDelete = new ArrayList<>();
+    private ITestInvocationListener mMockListener;
 
     @Before
     public void setUp() throws Exception {
         mOutput = FileUtil.createTempFile("proto-file-reporter-test", ".pb");
         mReporter = new FileProtoResultReporter();
         mReporter.setFileOutput(mOutput);
+        mMockListener = EasyMock.createMock(ITestInvocationListener.class);
     }
 
     @After
     public void tearDown() {
         FileUtil.deleteFile(mOutput);
+        for (File f : mToDelete) {
+            FileUtil.deleteFile(f);
+        }
     }
 
     @Test
@@ -75,4 +90,75 @@
                 InvocationContext.fromProto(anyDescription.unpack(Context.class));
         assertEquals("test", endContext.getAttributes().get("test").get(0));
     }
+
+    @Test
+    public void testWriteResults_periodic() throws Exception {
+        TestDescription test1 = new TestDescription("class1", "test1");
+        mReporter.setPeriodicWriting(true);
+        mReporter.setFileOutput(mOutput);
+        IInvocationContext context = new InvocationContext();
+        context.setConfigurationDescriptor(new ConfigurationDescriptor());
+        context.addInvocationAttribute("test", "test");
+        mReporter.invocationStarted(context);
+        mReporter.testModuleStarted(createModuleContext("module1"));
+        mReporter.testRunStarted("run1", 1);
+        mReporter.testStarted(test1);
+        mReporter.testEnded(test1, new HashMap<String, Metric>());
+        mReporter.testRunEnded(200L, new HashMap<String, Metric>());
+        mReporter.testModuleEnded();
+        mReporter.testModuleStarted(createModuleContext("module2"));
+        mReporter.testModuleEnded();
+        // Run without a module
+        mReporter.testRunStarted("run2", 1);
+        mReporter.testStarted(test1);
+        mReporter.testEnded(test1, new HashMap<String, Metric>());
+        mReporter.testRunEnded(200L, new HashMap<String, Metric>());
+        mReporter.invocationEnded(500L);
+
+        int index = 0;
+        File currentOutput = new File(mOutput.getAbsolutePath() + index);
+        if (!currentOutput.exists()) {
+            fail("Should have output at least one file");
+        }
+
+        mMockListener.invocationStarted(EasyMock.anyObject());
+        mMockListener.testModuleStarted(EasyMock.anyObject());
+        mMockListener.testRunStarted(
+                EasyMock.eq("run1"), EasyMock.eq(1), EasyMock.eq(0), EasyMock.anyLong());
+        mMockListener.testStarted(EasyMock.eq(test1), EasyMock.anyLong());
+        mMockListener.testEnded(
+                EasyMock.eq(test1),
+                EasyMock.anyLong(),
+                EasyMock.<HashMap<String, Metric>>anyObject());
+        mMockListener.testRunEnded(200L, new HashMap<String, Metric>());
+        mMockListener.testModuleEnded();
+        mMockListener.testModuleStarted(EasyMock.anyObject());
+        mMockListener.testModuleEnded();
+        mMockListener.testRunStarted(
+                EasyMock.eq("run2"), EasyMock.eq(1), EasyMock.eq(0), EasyMock.anyLong());
+        mMockListener.testStarted(EasyMock.eq(test1), EasyMock.anyLong());
+        mMockListener.testEnded(
+                EasyMock.eq(test1),
+                EasyMock.anyLong(),
+                EasyMock.<HashMap<String, Metric>>anyObject());
+        mMockListener.testRunEnded(200L, new HashMap<String, Metric>());
+        mMockListener.invocationEnded(500L);
+
+        EasyMock.replay(mMockListener);
+        ProtoResultParser parser = new ProtoResultParser(mMockListener, context, true);
+        while (currentOutput.exists()) {
+            mToDelete.add(currentOutput);
+            parser.processFileProto(currentOutput);
+            index++;
+            currentOutput = new File(mOutput.getAbsolutePath() + index);
+        }
+        EasyMock.verify(mMockListener);
+    }
+
+    private IInvocationContext createModuleContext(String moduleId) {
+        IInvocationContext context = new InvocationContext();
+        context.addInvocationAttribute(ModuleDefinition.MODULE_ID, moduleId);
+        context.setConfigurationDescriptor(new ConfigurationDescriptor());
+        return context;
+    }
 }
diff --git a/tests/src/com/android/tradefed/result/proto/ProtoResultParserTest.java b/tests/src/com/android/tradefed/result/proto/ProtoResultParserTest.java
index e85176d..860835f 100644
--- a/tests/src/com/android/tradefed/result/proto/ProtoResultParserTest.java
+++ b/tests/src/com/android/tradefed/result/proto/ProtoResultParserTest.java
@@ -90,7 +90,7 @@
         }
 
         @Override
-        public void processTestRunEnded(TestRecord runRecord) {
+        public void processTestRunEnded(TestRecord runRecord, boolean moduleInProgress) {
             mParser.processNewProto(runRecord);
         }
 
diff --git a/tests/src/com/android/tradefed/result/suite/XmlSuiteResultFormatterTest.java b/tests/src/com/android/tradefed/result/suite/XmlSuiteResultFormatterTest.java
index ef697dd..8f61d29 100644
--- a/tests/src/com/android/tradefed/result/suite/XmlSuiteResultFormatterTest.java
+++ b/tests/src/com/android/tradefed/result/suite/XmlSuiteResultFormatterTest.java
@@ -50,6 +50,7 @@
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
 
@@ -538,6 +539,34 @@
         assertEquals(RUN_HISTORY, holder.context.getAttributes().getUniqueMap().get("run_history"));
     }
 
+    /** Ensure the order is sorted according to module name and abi. */
+    @Test
+    public void testSortModules() {
+        List<TestRunResult> originalList = new ArrayList<>();
+        originalList.add(createFakeResult("armeabi-v7a module1", 1, 0, 0, 0));
+        originalList.add(createFakeResult("arm64-v8a module3", 1, 0, 0, 0));
+        originalList.add(createFakeResult("armeabi-v7a module2", 1, 0, 0, 0));
+        originalList.add(createFakeResult("arm64-v8a module1", 1, 0, 0, 0));
+        originalList.add(createFakeResult("armeabi-v7a module4", 1, 0, 0, 0));
+        originalList.add(createFakeResult("arm64-v8a module2", 1, 0, 0, 0));
+        Map<String, IAbi> moduleAbis = new HashMap<>();
+        moduleAbis.put("armeabi-v7a module1", new Abi("armeabi-v7a", "32"));
+        moduleAbis.put("arm64-v8a module1", new Abi("arm64-v8a", "64"));
+        moduleAbis.put("armeabi-v7a module2", new Abi("armeabi-v7a", "32"));
+        moduleAbis.put("arm64-v8a module2", new Abi("arm64-v8a", "64"));
+        moduleAbis.put("arm64-v8a module3", new Abi("arm64-v8a", "64"));
+        moduleAbis.put("armeabi-v7a module4", new Abi("armeabi-v7a", "32"));
+
+        List<TestRunResult> sortedResult = mFormatter.sortModules(originalList, moduleAbis);
+        assertEquals(6, sortedResult.size());
+        assertEquals("arm64-v8a module1", sortedResult.get(0).getName());
+        assertEquals("armeabi-v7a module1", sortedResult.get(1).getName());
+        assertEquals("arm64-v8a module2", sortedResult.get(2).getName());
+        assertEquals("armeabi-v7a module2", sortedResult.get(3).getName());
+        assertEquals("arm64-v8a module3", sortedResult.get(4).getName());
+        assertEquals("armeabi-v7a module4", sortedResult.get(5).getName());
+    }
+
     private TestRunResult createResultWithLog(String runName, int count, LogDataType type) {
         TestRunResult fakeRes = new TestRunResult();
         fakeRes.testRunStarted(runName, count);
diff --git a/tests/src/com/android/tradefed/testtype/GTestTest.java b/tests/src/com/android/tradefed/testtype/GTestTest.java
index 2a5e10c..f47190f 100644
--- a/tests/src/com/android/tradefed/testtype/GTestTest.java
+++ b/tests/src/com/android/tradefed/testtype/GTestTest.java
@@ -247,6 +247,23 @@
         doTestFilter(String.format("%s=%s:%s", GTEST_FLAG_FILTER, includeFilter1, includeFilter2));
     }
 
+    /** Test that large filters are converted to flagfile. */
+    @Test
+    public void testLargeFilters() throws DeviceNotAvailableException {
+        StringBuilder includeFilter1 = new StringBuilder("abc");
+        for (int i = 0; i < 550; i++) {
+            includeFilter1.append("a");
+        }
+        String includeFilter2 = "def";
+        mGTest.addIncludeFilter(includeFilter1.toString());
+        mGTest.addIncludeFilter(includeFilter2);
+
+        EasyMock.expect(mMockITestDevice.pushFile(EasyMock.anyObject(), EasyMock.anyObject()))
+                .andReturn(true);
+
+        doTestFilter(String.format("%s=/data/local/tmp/flagfile", GTestBase.GTEST_FLAG_FILE));
+    }
+
     /** Test the exclude filtering of test methods. */
     @Test
     public void testExcludeFilter() throws DeviceNotAvailableException {
@@ -277,20 +294,15 @@
     @Test
     public void testCommandTooLong() throws DeviceNotAvailableException {
         String deviceScriptPath = "/data/local/tmp/gtest_script.sh";
-        StringBuilder filterString = new StringBuilder(GTEST_FLAG_FILTER);
-        filterString.append("=-");
-        for (int i = 0; i < 100; i++) {
-            if (i != 0) {
-                filterString.append(":");
-            }
-            String filter = String.format("ExcludeClass%d", i);
-            filterString.append(filter);
-            mGTest.addExcludeFilter(filter);
+        StringBuilder testNameBuilder = new StringBuilder();
+        for (int i = 0; i < 1005; i++) {
+            testNameBuilder.append("a");
         }
+        String testName = testNameBuilder.toString();
         // filter string will be longer than GTest.GTEST_CMD_CHAR_LIMIT
 
         String nativeTestPath = GTest.DEFAULT_NATIVETEST_PATH;
-        String testPath = nativeTestPath + "/test1";
+        String testPath = nativeTestPath + "/" + testName;
         // configure the mock file system to have a single test
         MockFileUtil.setMockDirContents(mMockITestDevice, nativeTestPath, "test1");
         EasyMock.expect(mMockITestDevice.doesFileExist(nativeTestPath)).andReturn(true);
@@ -298,7 +310,7 @@
         EasyMock.expect(mMockITestDevice.isDirectory(testPath)).andReturn(false);
         // report the file as executable
         EasyMock.expect(mMockITestDevice.isExecutable(testPath)).andReturn(true);
-        String[] files = new String[] {"test1"};
+        String[] files = new String[] {testName};
         EasyMock.expect(mMockITestDevice.getChildren(nativeTestPath)).andReturn(files);
         // Expect push of script file
         EasyMock.expect(mMockITestDevice.pushString(EasyMock.<String>anyObject(),
@@ -482,6 +494,28 @@
         assertEquals("test_path flags", cmd_line);
     }
 
+    /**
+     * Test {@link GTest#getGTestFilters(String)} When the push to file fails, in this case we use
+     * the original filter arguments instead of hte flagfile.
+     */
+    @Test
+    public void testGetGTestFilters_largeFilters_pushFail() throws Exception {
+        StringBuilder includeFilter1 = new StringBuilder("abc");
+        for (int i = 0; i < 550; i++) {
+            includeFilter1.append("a");
+        }
+        mGTest.addIncludeFilter(includeFilter1.toString());
+        // Fail to push
+        EasyMock.expect(mMockITestDevice.pushFile(EasyMock.anyObject(), EasyMock.anyObject()))
+                .andReturn(false);
+
+        EasyMock.replay(mMockITestDevice);
+        String flag = mGTest.getGTestFilters("/path/");
+        // We fallback to the original command line filter
+        assertEquals("--gtest_filter=" + includeFilter1.toString(), flag);
+        EasyMock.verify(mMockITestDevice);
+    }
+
     /** Test {@link GTest#getGTestCmdLine(String, String)} with non-default user. */
     @Test
     public void testGetGTestCmdLine_runAs() throws Exception {
diff --git a/tests/src/com/android/tradefed/testtype/suite/GranularRetriableTestWrapperTest.java b/tests/src/com/android/tradefed/testtype/suite/GranularRetriableTestWrapperTest.java
index f34d409..c941afd 100644
--- a/tests/src/com/android/tradefed/testtype/suite/GranularRetriableTestWrapperTest.java
+++ b/tests/src/com/android/tradefed/testtype/suite/GranularRetriableTestWrapperTest.java
@@ -20,10 +20,12 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
+import com.android.ddmlib.IDevice;
 import com.android.ddmlib.testrunner.TestResult.TestStatus;
 import com.android.tradefed.config.IConfiguration;
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.device.DeviceUnresponsiveException;
+import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.device.metric.BaseDeviceMetricCollector;
 import com.android.tradefed.device.metric.DeviceMetricData;
 import com.android.tradefed.device.metric.IMetricCollector;
@@ -39,6 +41,8 @@
 import com.android.tradefed.testtype.ITestFilterReceiver;
 import com.android.tradefed.testtype.suite.ITestSuite.RetryStrategy;
 
+import org.easymock.EasyMock;
+import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
@@ -58,6 +62,7 @@
 
     private static final String RUN_NAME = "test run";
     private static final String RUN_NAME_2 = "test run 2";
+    private InvocationContext mModuleInvocationContext;
 
     private class BasicFakeTest implements IRemoteTest {
 
@@ -252,7 +257,7 @@
         granularTestWrapper.setMarkTestsSkipped(false);
         granularTestWrapper.setMetricCollectors(collectors);
         // Setup InvocationContext.
-        granularTestWrapper.setInvocationContext(new InvocationContext());
+        granularTestWrapper.setInvocationContext(mModuleInvocationContext);
         // Setup logsaver.
         granularTestWrapper.setLogSaver(new FileSystemLogSaver());
         IConfiguration mockModuleConfiguration = Mockito.mock(IConfiguration.class);
@@ -260,6 +265,11 @@
         return granularTestWrapper;
     }
 
+    @Before
+    public void setUp() {
+        mModuleInvocationContext = new InvocationContext();
+    }
+
     /**
      * Test that the intra module retry does not inhibit DeviceNotAvailableException. They are
      * bubbled up to the top.
@@ -802,6 +812,53 @@
         assertTrue(calledCollector.wasCalled);
     }
 
+    /**
+     * Test to reboot device at the last intra-module retry.
+     */
+    @Test
+    public void testIntraModuleRun_rebootAtLastIntraModuleRetry() throws Exception {
+        FakeTest test = new FakeTest();
+        test.setRunFailure("I failed!");
+        ITestDevice mMockDevice = EasyMock.createMock(ITestDevice.class);
+        mModuleInvocationContext.addAllocatedDevice("default-device1", mMockDevice);
+        GranularRetriableTestWrapper granularTestWrapper = createGranularTestWrapper(test, 3);
+        granularTestWrapper.setRetryStrategy(RetryStrategy.RETRY_ANY_FAILURE);
+        granularTestWrapper.setRebootAtLastRetry(true);
+        EasyMock.expect(mMockDevice.getIDevice()).andStubReturn(EasyMock.createMock(IDevice.class));
+        EasyMock.expect(mMockDevice.getSerialNumber()).andReturn("SERIAL");
+        mMockDevice.reboot();
+        EasyMock.replay(mMockDevice);
+        granularTestWrapper.run(new CollectingTestListener());
+        EasyMock.verify(mMockDevice);
+    }
+
+    /**
+     * Test to reboot multi-devices at the last intra-module retry.
+     */
+    @Test
+    public void testIntraModuleRun_rebootMultiDevicesAtLastIntraModuleRetry() throws Exception {
+        FakeTest test = new FakeTest();
+        test.setRunFailure("I failed!");
+        ITestDevice mMockDevice = EasyMock.createMock(ITestDevice.class);
+        ITestDevice mMockDevice2 = EasyMock.createMock(ITestDevice.class);
+        mModuleInvocationContext.addAllocatedDevice("default-device1", mMockDevice);
+        mModuleInvocationContext.addAllocatedDevice("default-device2", mMockDevice2);
+        GranularRetriableTestWrapper granularTestWrapper = createGranularTestWrapper(test, 3);
+        granularTestWrapper.setRetryStrategy(RetryStrategy.RETRY_ANY_FAILURE);
+        granularTestWrapper.setRebootAtLastRetry(true);
+        EasyMock.expect(mMockDevice.getIDevice()).andStubReturn(EasyMock.createMock(IDevice.class));
+        EasyMock.expect(mMockDevice.getSerialNumber()).andReturn("SERIAL");
+        EasyMock.expect(mMockDevice2.getIDevice()).andStubReturn(EasyMock.createMock(IDevice.class));
+        EasyMock.expect(mMockDevice2.getSerialNumber()).andReturn("SERIAL-2");
+        mMockDevice.reboot();
+        mMockDevice2.reboot();
+        EasyMock.replay(mMockDevice);
+        EasyMock.replay(mMockDevice2);
+        granularTestWrapper.run(new CollectingTestListener());
+        EasyMock.verify(mMockDevice);
+        EasyMock.verify(mMockDevice2);
+    }
+
     /** Collector that track if it was called or not */
     public static class CalledMetricCollector extends BaseDeviceMetricCollector {
 
diff --git a/tests/src/com/android/tradefed/testtype/suite/ITestSuiteTest.java b/tests/src/com/android/tradefed/testtype/suite/ITestSuiteTest.java
index 39e072a..8374a80 100644
--- a/tests/src/com/android/tradefed/testtype/suite/ITestSuiteTest.java
+++ b/tests/src/com/android/tradefed/testtype/suite/ITestSuiteTest.java
@@ -496,7 +496,7 @@
         mTestSuite.run(mMockListener);
         verifyMocks();
     }
-    
+
     /**
      * Test for {@link ITestSuite#run(ITestInvocationListener)} when the System status checker is
      * passing pre-check but failing post-check and we enable reporting a failure for it.
diff --git a/tests/src/com/android/tradefed/testtype/suite/ModuleDefinitionTest.java b/tests/src/com/android/tradefed/testtype/suite/ModuleDefinitionTest.java
index aac5eb3..c71fea9 100644
--- a/tests/src/com/android/tradefed/testtype/suite/ModuleDefinitionTest.java
+++ b/tests/src/com/android/tradefed/testtype/suite/ModuleDefinitionTest.java
@@ -1403,7 +1403,6 @@
                         mMultiTargetPrepList,
                         new Configuration("", ""));
         mModule.setRetryStrategy(RetryStrategy.ITERATIONS, false);
-
         mModule.getModuleInvocationContext().addAllocatedDevice(DEFAULT_DEVICE_NAME, mMockDevice);
         mModule.getModuleInvocationContext()
                 .addDeviceBuildInfo(DEFAULT_DEVICE_NAME, mMockBuildInfo);
diff --git a/tests/src/com/android/tradefed/testtype/suite/retry/ResultsPlayerTest.java b/tests/src/com/android/tradefed/testtype/suite/retry/ResultsPlayerTest.java
index 97f2f08..ce47dc8 100644
--- a/tests/src/com/android/tradefed/testtype/suite/retry/ResultsPlayerTest.java
+++ b/tests/src/com/android/tradefed/testtype/suite/retry/ResultsPlayerTest.java
@@ -147,7 +147,7 @@
         mPlayer.addToReplay(null, createTestRunResult("run1", 2, 1), entry);
 
         // Verify Mock
-        mMockListener.testRunStarted("run1", 2);
+        mMockListener.testRunStarted("run1", 1);
         // Only the provided test is re-run
         mMockListener.testStarted(EasyMock.eq(test), EasyMock.anyLong());
         mMockListener.testAssumptionFailure(test, "assertionfailure");
@@ -184,7 +184,7 @@
         mPlayer.addToReplay(null, runResult, entry2);
 
         // Verify Mock
-        mMockListener.testRunStarted("run1", 5);
+        mMockListener.testRunStarted("run1", 2);
         // Only the provided test is re-run
         mMockListener.testStarted(EasyMock.eq(test), EasyMock.anyLong());
         mMockListener.testAssumptionFailure(test, "assertionfailure");