Merge "[Atest] Set raw data mode to be default while running robolectric test."
diff --git a/.classpath b/.classpath
index 6eee727..5ed457d 100644
--- a/.classpath
+++ b/.classpath
@@ -28,5 +28,6 @@
 	<classpathentry kind="var" path="TRADEFED_ROOT/out/soong/.intermediates/external/jacoco/jacoco-cli/linux_glibc_common/combined/jacoco-cli.jar"/>
 	<classpathentry exported="true" kind="var" path="TRADEFED_ROOT/prebuilts/tools/common/google-api-services-storage/1.23.0/google-api-services-storage-v1-rev114-1.23.0.jar"/>
 	<classpathentry kind="var" path="TRADEFED_ROOT/out/soong/.intermediates/tools/tradefederation/core/tradefed-protos/linux_glibc_common/combined/tradefed-protos.jar"/>
+	<classpathentry kind="var" path="TRADEFED_ROOT/prebuilts/tools/common/m2/repository/com/google/code/gson/gson/2.8.0/gson-2.8.0.jar"/>
 	<classpathentry kind="output" path="bin"/>
 </classpath>
diff --git a/atest/Android.bp b/atest/Android.bp
index d67972b..f1facb3 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",
     ],
@@ -174,3 +176,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/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/proto/summary_record.proto b/proto/summary_record.proto
deleted file mode 100644
index a3a72b7..0000000
--- a/proto/summary_record.proto
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * 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.
- */
-syntax = "proto3";
-
-import "google/protobuf/any.proto";
-import "google/protobuf/timestamp.proto";
-import "tools/tradefederation/core/proto/metric_measurement.proto";
-
-option java_package = "com.android.tradefed.result.proto";
-option java_outer_classname = "SummaryRecordProto";
-
-package android_summary_record;
-
-// A Summary of the top level information related to an invocation.
-message SummaryRecord {
-
-  // The total number of expected test cases.
-  int64 num_expected_tests = 1;
-
-  // The total number of executed test cases.
-  int64 num_total_tests = 2;
-
-  // The total number of failed test cases.
-  int64 num_failed_tests = 3;
-
-  // The total number of failed test runs.
-  int64 num_failed_runs = 4;
-
-  // Extra debugging information for the invocation level, if set an invocation
-  // failure occurred and this field contains the error.
-  string error_message = 5;
-
-  // A short summary message or link associated with the invocation.
-  string summary_message = 6;
-
-  // The time at which this invocation started.
-  google.protobuf.Timestamp start_time = 7;
-
-  // The time at which this invocation finished.
-  google.protobuf.Timestamp end_time = 8;
-
-  // Metadata describing the invocation that was run.
-  google.protobuf.Any description = 9;
-}
\ No newline at end of file
diff --git a/remote/.classpath b/remote/.classpath
index b59b5c8..74e2efb 100644
--- a/remote/.classpath
+++ b/remote/.classpath
@@ -10,7 +10,6 @@
 	<classpathentry exported="true" kind="var" path="TRADEFED_ROOT/prebuilts/misc/common/sdklib/sdklib-prebuilt.jar"/>
 	<classpathentry exported="true" kind="var" path="TRADEFED_ROOT/prebuilts/misc/common/tools-common/tools-common-prebuilt.jar"/>
 	<classpathentry combineaccessrules="false" kind="src" path="/ddmlib"/>
-	<classpathentry kind="var" path="TRADEFED_ROOT/out/soong/.intermediates/external/guava/guava/linux_glibc_common/combined/guava.jar"/>
 	<classpathentry kind="var" path="TRADEFED_ROOT/external/error_prone/error_prone/error_prone_annotations-2.3.2.jar"/>
 	<classpathentry kind="output" path="bin"/>
 </classpath>
diff --git a/src/com/android/tradefed/device/NativeDevice.java b/src/com/android/tradefed/device/NativeDevice.java
index 92c2c83..a1d307a 100644
--- a/src/com/android/tradefed/device/NativeDevice.java
+++ b/src/com/android/tradefed/device/NativeDevice.java
@@ -37,6 +37,7 @@
 import com.android.tradefed.device.contentprovider.ContentProviderHandler;
 import com.android.tradefed.host.IHostOptions;
 import com.android.tradefed.log.ITestLogger;
+import com.android.tradefed.log.LogUtil;
 import com.android.tradefed.log.LogUtil.CLog;
 import com.android.tradefed.result.ByteArrayInputStreamSource;
 import com.android.tradefed.result.FileInputStreamSource;
@@ -222,6 +223,8 @@
     /** Keep track of the last time Tradefed itself triggered a reboot. */
     private long mLastTradefedRebootTime = 0L;
 
+    private File mExecuteShellCommandLogs = null;
+
     /**
      * Interface for a generic device communication attempt.
      */
@@ -713,7 +716,29 @@
         CollectingOutputReceiver receiver = new CollectingOutputReceiver();
         executeShellCommand(command, receiver);
         String output = receiver.getOutput();
-        CLog.v("%s on %s returned %s", command, getSerialNumber(), output);
+        if (mExecuteShellCommandLogs != null) {
+            // Log all output to a dedicated file as it can be very verbose.
+            String formatted =
+                    LogUtil.getLogFormatString(
+                            LogLevel.VERBOSE,
+                            "NativeDevice",
+                            String.format(
+                                    "%s on %s returned %s\n==== END OF OUTPUT ====\n",
+                                    command, getSerialNumber(), output));
+            try {
+                FileUtil.writeToFile(formatted, mExecuteShellCommandLogs, true);
+            } catch (IOException e) {
+                // Ignore the full error
+                CLog.e("Failed to log to executeShellCommand log: %s", e.getMessage());
+            }
+        }
+        if (output.length() > 80) {
+            CLog.v(
+                    "%s on %s returned %s <truncated - See executeShellCommand log for full trace>",
+                    command, getSerialNumber(), output.substring(0, 80));
+        } else {
+            CLog.v("%s on %s returned %s", command, getSerialNumber(), output);
+        }
         return output;
     }
 
@@ -4083,6 +4108,13 @@
         // Default implementation
         mContentProvider = null;
         mShouldSkipContentProviderSetup = false;
+        try {
+            mExecuteShellCommandLogs =
+                    FileUtil.createTempFile("TestDevice_ExecuteShellCommands", ".txt");
+        } catch (IOException e) {
+            throw new TargetSetupError(
+                    "Failed to create the executeShellCommand log file.", e, getDeviceDescriptor());
+        }
     }
 
     /**
@@ -4090,6 +4122,8 @@
      */
     @Override
     public void postInvocationTearDown() {
+        FileUtil.deleteFile(mExecuteShellCommandLogs);
+        mExecuteShellCommandLogs = null;
         // Default implementation
         if (getIDevice() instanceof StubDevice) {
             return;
@@ -4434,4 +4468,9 @@
     void resetContentProviderSetup() {
         mShouldSkipContentProviderSetup = false;
     }
+
+    /** The log that contains all the {@link #executeShellCommand(String)} logs. */
+    public final File getExecuteShellCommandLog() {
+        return mExecuteShellCommandLogs;
+    }
 }
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/invoker/RemoteInvocationExecution.java b/src/com/android/tradefed/invoker/RemoteInvocationExecution.java
index 3051db9..f615387 100644
--- a/src/com/android/tradefed/invoker/RemoteInvocationExecution.java
+++ b/src/com/android/tradefed/invoker/RemoteInvocationExecution.java
@@ -42,7 +42,6 @@
 import com.android.tradefed.result.LogDataType;
 import com.android.tradefed.result.proto.FileProtoResultReporter;
 import com.android.tradefed.result.proto.ProtoResultParser;
-import com.android.tradefed.result.proto.SummaryProtoResultReporter;
 import com.android.tradefed.targetprep.BuildError;
 import com.android.tradefed.targetprep.TargetSetupError;
 import com.android.tradefed.util.CommandResult;
@@ -78,7 +77,6 @@
 
     public static final String REMOTE_USER_DIR = "/home/{$USER}/";
     public static final String PROTO_RESULT_NAME = "output.pb";
-    public static final String PROTO_SUMMARY_NAME = "summary-output.pb";
     public static final String STDOUT_FILE = "screen-VM_tradefed-stdout.txt";
     public static final String STDERR_FILE = "screen-VM_tradefed-stderr.txt";
     public static final String REMOTE_CONFIG = "configuration";
@@ -524,10 +522,6 @@
         FileProtoResultReporter protoReporter = new FileProtoResultReporter();
         protoReporter.setFileOutput(new File(resultDirPath + PROTO_RESULT_NAME));
         reporters.add(protoReporter);
-        // Setup a summary reporter
-        SummaryProtoResultReporter summaryReporter = new SummaryProtoResultReporter();
-        summaryReporter.setFileOutput(new File(resultDirPath + PROTO_SUMMARY_NAME));
-        reporters.add(summaryReporter);
 
         config.setTestInvocationListeners(reporters);
 
diff --git a/src/com/android/tradefed/invoker/TestInvocation.java b/src/com/android/tradefed/invoker/TestInvocation.java
index f8f96ac..b7b2599 100644
--- a/src/com/android/tradefed/invoker/TestInvocation.java
+++ b/src/com/android/tradefed/invoker/TestInvocation.java
@@ -25,9 +25,12 @@
 import com.android.tradefed.device.DeviceUnresponsiveException;
 import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.device.ITestDevice.RecoveryMode;
+import com.android.tradefed.device.NativeDevice;
 import com.android.tradefed.device.StubDevice;
 import com.android.tradefed.device.TestDeviceState;
 import com.android.tradefed.device.cloud.ManagedRemoteDevice;
+import com.android.tradefed.device.cloud.NestedRemoteDevice;
+import com.android.tradefed.device.cloud.RemoteAndroidVirtualDevice;
 import com.android.tradefed.guice.InvocationScope;
 import com.android.tradefed.invoker.sandbox.ParentSandboxInvocationExecution;
 import com.android.tradefed.invoker.sandbox.SandboxedInvocationExecution;
@@ -35,9 +38,11 @@
 import com.android.tradefed.log.BaseLeveledLogOutput;
 import com.android.tradefed.log.ILeveledLogOutput;
 import com.android.tradefed.log.ILogRegistry;
+import com.android.tradefed.log.ITestLogger;
 import com.android.tradefed.log.LogRegistry;
 import com.android.tradefed.log.LogUtil.CLog;
 import com.android.tradefed.postprocessor.IPostProcessor;
+import com.android.tradefed.result.FileInputStreamSource;
 import com.android.tradefed.result.ITestInvocationListener;
 import com.android.tradefed.result.InputStreamSource;
 import com.android.tradefed.result.LogDataType;
@@ -50,6 +55,7 @@
 import com.android.tradefed.testtype.IRemoteTest;
 import com.android.tradefed.testtype.IResumableTest;
 import com.android.tradefed.testtype.IRetriableTest;
+import com.android.tradefed.util.FileUtil;
 import com.android.tradefed.util.IRunUtil;
 import com.android.tradefed.util.PrettyPrintDelimiter;
 import com.android.tradefed.util.RunInterruptedException;
@@ -58,6 +64,7 @@
 
 import com.google.common.annotations.VisibleForTesting;
 
+import java.io.File;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -303,6 +310,9 @@
                     takeBugreport(badDevice, listener, bugreportName);
                 }
             }
+            // Save the device executeShellCommand logs
+            logExecuteShellCommand(context.getDevices(), listener);
+
             mStatus = "tearing down";
             try {
                 invocationPath.doTeardown(context, config, listener, exception);
@@ -527,6 +537,11 @@
             if (testDevice.getIDevice() instanceof StubDevice) {
                 continue;
             }
+            if (testDevice instanceof RemoteAndroidVirtualDevice
+                    || testDevice instanceof NestedRemoteDevice) {
+                // Vritual devices have a fake battery there is no point in logging it.
+                continue;
+            }
             Integer batteryLevel = testDevice.getBattery();
             if (batteryLevel == null) {
                 CLog.v("Failed to get battery level for %s", testDevice.getSerialNumber());
@@ -812,4 +827,30 @@
         String message = String.format("===== %s PHASE %s =====", phase, startEnd);
         PrettyPrintDelimiter.printStageDelimiter(message);
     }
+
+    private void logExecuteShellCommand(List<ITestDevice> devices, ITestLogger logger) {
+        for (ITestDevice device : devices) {
+            if (!(device instanceof NativeDevice)) {
+                return;
+            }
+            File log = ((NativeDevice) device).getExecuteShellCommandLog();
+            if (log == null || !log.exists()) {
+                return;
+            }
+            try {
+                if (log != null && !FileUtil.readStringFromFile(log).isEmpty()) {
+                    try (InputStreamSource source = new FileInputStreamSource(log)) {
+                        logger.testLog(
+                                String.format(
+                                        "executeShellCommandLog_%s", device.getSerialNumber()),
+                                LogDataType.TEXT,
+                                source);
+                    }
+                }
+            } catch (IOException e) {
+                // Ignored
+                CLog.e(e);
+            }
+        }
+    }
 }
diff --git a/src/com/android/tradefed/result/IResultSummaryReceiver.java b/src/com/android/tradefed/result/IResultSummaryReceiver.java
deleted file mode 100644
index 23644e8..0000000
--- a/src/com/android/tradefed/result/IResultSummaryReceiver.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * 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.result;
-
-import com.android.tradefed.result.proto.SummaryRecordProto.SummaryRecord;
-
-/**
- * Interface describing an {@link ITestInvocationListener} that can receive a summary of the
- * invocation only instead of the full results.
- */
-public interface IResultSummaryReceiver {
-
-    /**
-     * Callback where the {@link SummaryRecord} of the results is passed. Will always be called
-     * before {@link ITestInvocationListener#invocationEnded(long)}.
-     *
-     * @param summary a {@link SummaryRecord} containing the summary of the invocation.
-     */
-    public void setResultSummary(SummaryRecord summary);
-}
diff --git a/src/com/android/tradefed/result/LogSaverResultForwarder.java b/src/com/android/tradefed/result/LogSaverResultForwarder.java
index 14d9f84..6e379a2 100644
--- a/src/com/android/tradefed/result/LogSaverResultForwarder.java
+++ b/src/com/android/tradefed/result/LogSaverResultForwarder.java
@@ -18,14 +18,12 @@
 
 import com.android.tradefed.invoker.IInvocationContext;
 import com.android.tradefed.log.LogUtil.CLog;
-import com.android.tradefed.result.proto.SummaryRecordProto.SummaryRecord;
 
 import java.io.IOException;
 import java.util.List;
 
 /** A {@link ResultForwarder} for saving logs with the global file saver. */
-public class LogSaverResultForwarder extends ResultForwarder
-        implements ILogSaverListener, IResultSummaryReceiver {
+public class LogSaverResultForwarder extends ResultForwarder implements ILogSaverListener {
 
     ILogSaver mLogSaver;
 
@@ -134,20 +132,4 @@
             }
         }
     }
-
-    /** {@inheritDoc} */
-    @Override
-    public void setResultSummary(SummaryRecord summary) {
-        for (ITestInvocationListener listener : getListeners()) {
-            try {
-                // Forward the logAssociation call
-                if (listener instanceof IResultSummaryReceiver) {
-                    ((IResultSummaryReceiver) listener).setResultSummary(summary);
-                }
-            } catch (RuntimeException e) {
-                CLog.e("Failed to setResultSummary on %s", listener);
-                CLog.e(e);
-            }
-        }
-    }
 }
\ No newline at end of file
diff --git a/src/com/android/tradefed/result/proto/SummaryProtoParser.java b/src/com/android/tradefed/result/proto/SummaryProtoParser.java
deleted file mode 100644
index 7b635e8..0000000
--- a/src/com/android/tradefed/result/proto/SummaryProtoParser.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * 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.result.proto;
-
-import com.android.tradefed.result.proto.SummaryRecordProto.SummaryRecord;
-import com.android.tradefed.util.ByteArrayList;
-
-import com.google.protobuf.CodedInputStream;
-import com.google.protobuf.InvalidProtocolBufferException;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-
-public class SummaryProtoParser {
-
-    /**
-     * Pick a 4MB default size to allow the buffer to grow for big protobuf. The default value could
-     * fail in some cases.
-     */
-    private static final int DEFAULT_SIZE_BYTES = 4 * 1024 * 1024;
-
-    /**
-     * Read {@link SummaryRecord} from a file and return it.
-     *
-     * @param protoFile The {@link File} containing the record
-     * @return a {@link SummaryRecord} created from the file.
-     * @throws IOException, InvalidProtocolBufferException
-     */
-    public static SummaryRecord readFromFile(File protoFile)
-            throws IOException, InvalidProtocolBufferException {
-        SummaryRecord record = null;
-        try (InputStream stream = new FileInputStream(protoFile)) {
-            CodedInputStream is = CodedInputStream.newInstance(stream);
-            is.setSizeLimit(Integer.MAX_VALUE);
-            ByteArrayList data = new ByteArrayList(DEFAULT_SIZE_BYTES);
-            while (!is.isAtEnd()) {
-                int size = is.readRawVarint32();
-                byte[] dataByte = is.readRawBytes(size);
-                data.addAll(dataByte);
-            }
-            record = SummaryRecord.parseFrom(data.getContents());
-            data.clear();
-        }
-        return record;
-    }
-}
diff --git a/src/com/android/tradefed/result/proto/SummaryProtoResultReporter.java b/src/com/android/tradefed/result/proto/SummaryProtoResultReporter.java
deleted file mode 100644
index dc63a04..0000000
--- a/src/com/android/tradefed/result/proto/SummaryProtoResultReporter.java
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- * 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.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.CollectingTestListener;
-import com.android.tradefed.result.proto.SummaryRecordProto.SummaryRecord;
-import com.android.tradefed.util.StreamUtil;
-
-import com.google.protobuf.Any;
-import com.google.protobuf.Timestamp;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-
-/**
- * Reporters that creates a summary proto from an invocation. The summary contains some minimal
- * informations about the invocation.
- */
-public class SummaryProtoResultReporter extends CollectingTestListener {
-
-    @Option(
-        name = "summary-proto-output-file",
-        description = "File where the proto output will be saved",
-        mandatory = true
-    )
-    private File mOutputFile;
-
-    private Throwable mInvocationFailure = null;
-    private long mStartTimeMs;
-    private long mEndTimeMs;
-
-    @Override
-    public void invocationStarted(IInvocationContext context) {
-        mStartTimeMs = System.currentTimeMillis();
-        super.invocationStarted(context);
-    }
-
-    @Override
-    public void invocationFailed(Throwable cause) {
-        super.invocationFailed(cause);
-        mInvocationFailure = cause;
-    }
-
-    @Override
-    public void invocationEnded(long elapsedTime) {
-        mEndTimeMs = System.currentTimeMillis();
-        super.invocationEnded(elapsedTime);
-        writeSummaryProto();
-    }
-
-    /** Sets the file where to output the result. */
-    public void setFileOutput(File output) {
-        mOutputFile = output;
-    }
-
-    private void writeSummaryProto() {
-        if (mOutputFile == null) {
-            return;
-        }
-        SummaryRecord.Builder builder = SummaryRecord.newBuilder();
-        // Populate the proto
-        builder.setNumExpectedTests(getExpectedTests());
-        builder.setNumTotalTests(getNumTotalTests());
-        builder.setNumFailedTests(getNumAllFailedTests());
-        builder.setNumFailedRuns(getNumAllFailedTestRuns());
-        if (mInvocationFailure != null) {
-            builder.setErrorMessage(StreamUtil.getStackTrace(mInvocationFailure));
-        }
-        builder.setStartTime(createTimeStamp(mStartTimeMs));
-        builder.setEndTime(createTimeStamp(mEndTimeMs));
-        builder.setDescription(Any.pack(getInvocationContext().toProto()));
-        // Build and write the proto
-        SummaryRecord record = builder.build();
-        try {
-            record.writeDelimitedTo(new FileOutputStream(mOutputFile));
-        } catch (IOException e) {
-            CLog.e(e);
-            throw new RuntimeException(e);
-        }
-    }
-
-    /** Create and populate Timestamp as recommended in the javadoc of the Timestamp proto. */
-    private Timestamp createTimeStamp(long currentTimeMs) {
-        return Timestamp.newBuilder()
-                .setSeconds(currentTimeMs / 1000)
-                .setNanos((int) ((currentTimeMs % 1000) * 1000000))
-                .build();
-    }
-}
diff --git a/src/com/android/tradefed/result/suite/XmlSuiteResultFormatter.java b/src/com/android/tradefed/result/suite/XmlSuiteResultFormatter.java
index 1d8e981..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;
@@ -121,7 +124,7 @@
     private static final String LOG_FILE_NAME_ATTR = "file_name";
 
     /** Helper object for JSON conversion. */
-    private static final class RunHistory {
+    public static final class RunHistory {
         public long startTime;
         public long endTime;
     }
@@ -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/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/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/SuiteModuleLoader.java b/src/com/android/tradefed/testtype/suite/SuiteModuleLoader.java
index dbc3d9f..78d7b2f 100644
--- a/src/com/android/tradefed/testtype/suite/SuiteModuleLoader.java
+++ b/src/com/android/tradefed/testtype/suite/SuiteModuleLoader.java
@@ -203,7 +203,8 @@
             IModuleParameter mForcedParameter = null;
             if (mForcedModuleParameter != null) {
                 mForcedParameter =
-                        ModuleParametersHelper.getParameterHandler(mForcedModuleParameter);
+                        ModuleParametersHelper.getParameterHandler(
+                                mForcedModuleParameter, /* optionalParams */ false);
             }
 
             // Invokes parser to process the test module config file
@@ -547,7 +548,9 @@
                 CLog.d("'%s' was excluded via exclude-module-parameters.");
                 continue;
             }
-            IModuleParameter handler = ModuleParametersHelper.getParameterHandler(suiteParam);
+            IModuleParameter handler =
+                    ModuleParametersHelper.getParameterHandler(
+                            suiteParam, /* optionalParams */ false);
             params.add(handler);
         }
         return params;
diff --git a/src/com/android/tradefed/testtype/suite/params/InstantAppHandler.java b/src/com/android/tradefed/testtype/suite/params/InstantAppHandler.java
index 532d51f..5744f64 100644
--- a/src/com/android/tradefed/testtype/suite/params/InstantAppHandler.java
+++ b/src/com/android/tradefed/testtype/suite/params/InstantAppHandler.java
@@ -19,6 +19,7 @@
 import android.platform.test.annotations.AppModeInstant;
 
 import com.android.tradefed.config.IConfiguration;
+import com.android.tradefed.config.IDeviceConfiguration;
 import com.android.tradefed.targetprep.ITargetPreparer;
 import com.android.tradefed.targetprep.TestAppInstallSetup;
 import com.android.tradefed.testtype.IRemoteTest;
@@ -40,9 +41,11 @@
     @Override
     public void applySetup(IConfiguration moduleConfiguration) {
         // First, force target_preparers if they support it to install app in instant mode.
-        for (ITargetPreparer preparer : moduleConfiguration.getTargetPreparers()) {
-            if (preparer instanceof TestAppInstallSetup) {
-                ((TestAppInstallSetup) preparer).setInstantMode(true);
+        for (IDeviceConfiguration deviceConfig : moduleConfiguration.getDeviceConfig()) {
+            for (ITargetPreparer preparer : deviceConfig.getTargetPreparers()) {
+                if (preparer instanceof TestAppInstallSetup) {
+                    ((TestAppInstallSetup) preparer).setInstantMode(true);
+                }
             }
         }
         // TODO: Second, notify HostTest that instant mode might be needed for apks.
diff --git a/src/com/android/tradefed/testtype/suite/params/ModuleParameters.java b/src/com/android/tradefed/testtype/suite/params/ModuleParameters.java
index 71c7b9b..8fca54d 100644
--- a/src/com/android/tradefed/testtype/suite/params/ModuleParameters.java
+++ b/src/com/android/tradefed/testtype/suite/params/ModuleParameters.java
@@ -22,7 +22,9 @@
     NOT_INSTANT_APP("not_instant_app", "instant_app_family"),
 
     MULTI_ABI("multi_abi", "multi_abi_family"),
-    NOT_MULTI_ABI("not_multi_abi", "multi_abi_family");
+    NOT_MULTI_ABI("not_multi_abi", "multi_abi_family"),
+
+    SECONDARY_USER("secondary_user", "secondary_user_family");
 
     public static final String INSTANT_APP_FAMILY = "instant_app_family";
     public static final String MULTI_ABI_FAMILY = "multi_abi_family";
diff --git a/src/com/android/tradefed/testtype/suite/params/ModuleParametersHelper.java b/src/com/android/tradefed/testtype/suite/params/ModuleParametersHelper.java
index 517c6f0..e009976 100644
--- a/src/com/android/tradefed/testtype/suite/params/ModuleParametersHelper.java
+++ b/src/com/android/tradefed/testtype/suite/params/ModuleParametersHelper.java
@@ -31,8 +31,29 @@
         sHandlerMap.put(ModuleParameters.NOT_MULTI_ABI, new NotMultiAbiHandler());
     }
 
-    /** Returns the {@link IModuleParameter} associated with the requested parameter. */
-    public static IModuleParameter getParameterHandler(ModuleParameters param) {
-        return sHandlerMap.get(param);
+    /**
+     * Optional parameters are params that will not automatically be created when the module
+     * parameterization is enabled. They will need to be explicitly enabled. They represent a second
+     * set of parameterization that is less commonly requested to run. They could be upgraded to
+     * main parameters in the future by moving them above.
+     */
+    private static Map<ModuleParameters, IModuleParameter> sOptionalHandlerMap = new HashMap<>();
+
+    static {
+        sOptionalHandlerMap.put(ModuleParameters.SECONDARY_USER, new SecondaryUserHandler());
+    }
+
+    /**
+     * Returns the {@link IModuleParameter} associated with the requested parameter.
+     *
+     * @param withOptional Whether or not to also check optional params.
+     */
+    public static IModuleParameter getParameterHandler(
+            ModuleParameters param, boolean withOptional) {
+        IModuleParameter value = sHandlerMap.get(param);
+        if (value == null && withOptional) {
+            return sOptionalHandlerMap.get(param);
+        }
+        return value;
     }
 }
diff --git a/src/com/android/tradefed/testtype/suite/params/SecondaryUserHandler.java b/src/com/android/tradefed/testtype/suite/params/SecondaryUserHandler.java
new file mode 100644
index 0000000..f4a133d
--- /dev/null
+++ b/src/com/android/tradefed/testtype/suite/params/SecondaryUserHandler.java
@@ -0,0 +1,40 @@
+/*
+ * 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.testtype.suite.params;
+
+import com.android.tradefed.config.IConfiguration;
+import com.android.tradefed.config.IDeviceConfiguration;
+import com.android.tradefed.targetprep.CreateUserPreparer;
+import com.android.tradefed.targetprep.ITargetPreparer;
+
+import java.util.List;
+
+/** Handler for {@link ModuleParameters#SECONDARY_USER}. */
+public class SecondaryUserHandler implements IModuleParameter {
+
+    @Override
+    public String getParameterIdentifier() {
+        return "secondary_user";
+    }
+
+    @Override
+    public void applySetup(IConfiguration moduleConfiguration) {
+        for (IDeviceConfiguration deviceConfig : moduleConfiguration.getDeviceConfig()) {
+            List<ITargetPreparer> preparers = deviceConfig.getTargetPreparers();
+            preparers.add(0, new CreateUserPreparer());
+        }
+    }
+}
diff --git a/tests/src/com/android/tradefed/UnitTests.java b/tests/src/com/android/tradefed/UnitTests.java
index 1f130b3..c84ef69 100644
--- a/tests/src/com/android/tradefed/UnitTests.java
+++ b/tests/src/com/android/tradefed/UnitTests.java
@@ -163,7 +163,6 @@
 import com.android.tradefed.result.proto.ProtoResultParserTest;
 import com.android.tradefed.result.proto.ProtoResultReporterTest;
 import com.android.tradefed.result.proto.StreamProtoResultReporterTest;
-import com.android.tradefed.result.proto.SummaryProtoResultReporterTest;
 import com.android.tradefed.result.suite.FormattedGeneratorReporterTest;
 import com.android.tradefed.result.suite.XmlSuiteResultFormatterTest;
 import com.android.tradefed.sandbox.SandboxConfigDumpTest;
@@ -562,7 +561,6 @@
     ProtoResultParserTest.class,
     ProtoResultReporterTest.class,
     StreamProtoResultReporterTest.class,
-    SummaryProtoResultReporterTest.class,
 
     // result.suite
     FormattedGeneratorReporterTest.class,
diff --git a/tests/src/com/android/tradefed/device/cloud/NestedRemoteDeviceTest.java b/tests/src/com/android/tradefed/device/cloud/NestedRemoteDeviceTest.java
index efe6c23..4ac2b17 100644
--- a/tests/src/com/android/tradefed/device/cloud/NestedRemoteDeviceTest.java
+++ b/tests/src/com/android/tradefed/device/cloud/NestedRemoteDeviceTest.java
@@ -30,6 +30,7 @@
 import com.android.tradefed.util.CommandStatus;
 import com.android.tradefed.util.IRunUtil;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -71,6 +72,11 @@
                 };
     }
 
+    @After
+    public void tearDown() throws Exception {
+        mDevice.postInvocationTearDown();
+    }
+
     /** Test that reset device returns true in case of success */
     @Test
     public void testResetVirtualDevice() throws DeviceNotAvailableException {
diff --git a/tests/src/com/android/tradefed/invoker/RemoteInvocationExecutionTest.java b/tests/src/com/android/tradefed/invoker/RemoteInvocationExecutionTest.java
index 7427f71..bb688b0 100644
--- a/tests/src/com/android/tradefed/invoker/RemoteInvocationExecutionTest.java
+++ b/tests/src/com/android/tradefed/invoker/RemoteInvocationExecutionTest.java
@@ -34,7 +34,6 @@
 import com.android.tradefed.result.ITestInvocationListener;
 import com.android.tradefed.result.LogDataType;
 import com.android.tradefed.result.proto.ProtoResultReporter;
-import com.android.tradefed.result.proto.SummaryProtoResultReporter;
 import com.android.tradefed.util.FileUtil;
 
 import org.junit.Before;
@@ -98,9 +97,8 @@
                     ConfigurationFactory.getInstance()
                             .createConfigurationFromArgs(new String[] {res.getAbsolutePath()});
             List<ITestInvocationListener> listeners = reparse.getTestInvocationListeners();
-            assertEquals(2, listeners.size());
+            assertEquals(1, listeners.size());
             assertTrue(listeners.get(0) instanceof ProtoResultReporter);
-            assertTrue(listeners.get(1) instanceof SummaryProtoResultReporter);
             // Ensure the requested type is reset
             assertNull(
                     ((DeviceSelectionOptions)
diff --git a/tests/src/com/android/tradefed/result/proto/SummaryProtoResultReporterTest.java b/tests/src/com/android/tradefed/result/proto/SummaryProtoResultReporterTest.java
deleted file mode 100644
index 59aaa93..0000000
--- a/tests/src/com/android/tradefed/result/proto/SummaryProtoResultReporterTest.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * 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.result.proto;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
-import com.android.tradefed.build.BuildInfo;
-import com.android.tradefed.config.ConfigurationDef;
-import com.android.tradefed.config.ConfigurationDescriptor;
-import com.android.tradefed.invoker.IInvocationContext;
-import com.android.tradefed.invoker.InvocationContext;
-import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
-import com.android.tradefed.result.proto.SummaryRecordProto.SummaryRecord;
-import com.android.tradefed.util.FileUtil;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-import java.io.File;
-import java.util.HashMap;
-
-/** Unit tests for {@link SummaryProtoResultReporter}. */
-@RunWith(JUnit4.class)
-public class SummaryProtoResultReporterTest {
-
-    private SummaryProtoResultReporter mReporter;
-    private File mOutputFile;
-    private IInvocationContext mContext;
-
-    @Before
-    public void setUp() throws Exception {
-        mOutputFile = FileUtil.createTempFile("test-summary-proto", ".pb");
-        mReporter = new SummaryProtoResultReporter();
-        mReporter.setFileOutput(mOutputFile);
-
-        mContext = new InvocationContext();
-        mContext.addDeviceBuildInfo(ConfigurationDef.DEFAULT_DEVICE_NAME, new BuildInfo());
-        mContext.setConfigurationDescriptor(new ConfigurationDescriptor());
-    }
-
-    @After
-    public void tearDown() {
-        FileUtil.deleteFile(mOutputFile);
-    }
-
-    @Test
-    public void testReportSummary() throws Exception {
-        mReporter.invocationStarted(mContext);
-        mReporter.testRunStarted("run1", 5);
-        mReporter.testRunEnded(5L, new HashMap<String, Metric>());
-        mReporter.invocationEnded(500L);
-
-        assertTrue(FileUtil.readStringFromFile(mOutputFile).length() > 50);
-
-        SummaryRecord summary = SummaryProtoParser.readFromFile(mOutputFile);
-        assertEquals(5, summary.getNumExpectedTests());
-        assertEquals(0, summary.getNumTotalTests());
-    }
-}
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/suite/params/ModuleParametersHelperTest.java b/tests/src/com/android/tradefed/testtype/suite/params/ModuleParametersHelperTest.java
index 34ef701..bd1f3fb 100644
--- a/tests/src/com/android/tradefed/testtype/suite/params/ModuleParametersHelperTest.java
+++ b/tests/src/com/android/tradefed/testtype/suite/params/ModuleParametersHelperTest.java
@@ -29,7 +29,8 @@
     @Test
     public void testHandlersExists() {
         for (ModuleParameters param : ModuleParameters.values()) {
-            IModuleParameter handler = ModuleParametersHelper.getParameterHandler(param);
+            IModuleParameter handler =
+                    ModuleParametersHelper.getParameterHandler(param, /* Include optional */ true);
             assertNotNull(handler);
         }
     }