Merge "Sort modules in the XML by name and abi"
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/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 6a31ff5..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,13 +55,16 @@
 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;
 import com.android.tradefed.util.RunUtil;
 import com.android.tradefed.util.TimeUtil;
 
 import com.google.common.annotations.VisibleForTesting;
 
+import java.io.File;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -302,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);
@@ -526,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());
@@ -809,11 +825,32 @@
     public static void printStageDelimiter(Stage phase, boolean end) {
         String startEnd = end ? "ENDING" : "STARTING";
         String message = String.format("===== %s PHASE %s =====", phase, startEnd);
-        String limit = "";
-        for (int i = 0; i < message.length(); i++) {
-            limit += "=";
+        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);
+            }
         }
-        String finalFormat = String.format("\n%s\n%s\n%s", limit, message, limit);
-        CLog.d(finalFormat);
     }
 }
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 28ae397..1aa9997 100644
--- a/src/com/android/tradefed/result/suite/XmlSuiteResultFormatter.java
+++ b/src/com/android/tradefed/result/suite/XmlSuiteResultFormatter.java
@@ -124,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;
     }
diff --git a/src/com/android/tradefed/sandbox/SandboxInvocationRunner.java b/src/com/android/tradefed/sandbox/SandboxInvocationRunner.java
index 71f44a9..2d9ca16 100644
--- a/src/com/android/tradefed/sandbox/SandboxInvocationRunner.java
+++ b/src/com/android/tradefed/sandbox/SandboxInvocationRunner.java
@@ -22,6 +22,7 @@
 import com.android.tradefed.result.ITestInvocationListener;
 import com.android.tradefed.util.CommandResult;
 import com.android.tradefed.util.CommandStatus;
+import com.android.tradefed.util.PrettyPrintDelimiter;
 import com.android.tradefed.util.SerializationUtil;
 
 import org.json.JSONException;
@@ -45,12 +46,14 @@
         if (sandbox == null) {
             throw new RuntimeException("Couldn't find the sandbox object.");
         }
+        PrettyPrintDelimiter.printStageDelimiter("Starting Sandbox Environment Setup");
         Exception res = sandbox.prepareEnvironment(context, config, listener);
         if (res != null) {
             CLog.w("Sandbox prepareEnvironment threw an Exception.");
             sandbox.tearDown();
             throw res;
         }
+        PrettyPrintDelimiter.printStageDelimiter("Done with Sandbox Environment Setup");
         try {
             CommandResult result = sandbox.run(config, listener);
             if (!CommandStatus.SUCCESS.equals(result.getStatus())) {
diff --git a/src/com/android/tradefed/sandbox/TradefedSandbox.java b/src/com/android/tradefed/sandbox/TradefedSandbox.java
index 92fe045..e1f6724 100644
--- a/src/com/android/tradefed/sandbox/TradefedSandbox.java
+++ b/src/com/android/tradefed/sandbox/TradefedSandbox.java
@@ -40,6 +40,7 @@
 import com.android.tradefed.util.CommandStatus;
 import com.android.tradefed.util.FileUtil;
 import com.android.tradefed.util.IRunUtil;
+import com.android.tradefed.util.PrettyPrintDelimiter;
 import com.android.tradefed.util.QuotationAwareTokenizer;
 import com.android.tradefed.util.RunUtil;
 import com.android.tradefed.util.StreamUtil;
@@ -64,6 +65,8 @@
  */
 public class TradefedSandbox implements ISandbox {
 
+    private static final String SANDBOX_PREFIX = "sandbox-";
+
     private File mStdoutFile = null;
     private File mStderrFile = null;
     private OutputStream mStdout = null;
@@ -149,6 +152,11 @@
             result.setStderr(
                     String.format("Event receiver thread did not complete.:\n%s", stderrText));
         }
+        PrettyPrintDelimiter.printStageDelimiter(
+                String.format(
+                        "Execution of the tests occurred in the sandbox, you can find its logs "
+                                + "under the name pattern '%s*'",
+                        SANDBOX_PREFIX));
 
         return result;
     }
@@ -290,7 +298,7 @@
             String commandLine = config.getCommandLine();
             if (getSandboxOptions(config).shouldUseProtoReporter()) {
                 mProtoReceiver =
-                        new StreamProtoReceiver(listener, context, false, false, "sandbox-");
+                        new StreamProtoReceiver(listener, context, false, false, SANDBOX_PREFIX);
                 // Force the child to the same mode as the parent.
                 commandLine = commandLine + " --" + SandboxOptions.USE_PROTO_REPORTER;
             } else {
diff --git a/src/com/android/tradefed/testtype/AndroidJUnitTest.java b/src/com/android/tradefed/testtype/AndroidJUnitTest.java
index bc70bb6..838d9e7 100644
--- a/src/com/android/tradefed/testtype/AndroidJUnitTest.java
+++ b/src/com/android/tradefed/testtype/AndroidJUnitTest.java
@@ -329,6 +329,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/host/PrettyTestEventLogger.java b/src/com/android/tradefed/testtype/host/PrettyTestEventLogger.java
index 7c414f9..44ffab3 100644
--- a/src/com/android/tradefed/testtype/host/PrettyTestEventLogger.java
+++ b/src/com/android/tradefed/testtype/host/PrettyTestEventLogger.java
@@ -50,7 +50,7 @@
                 String.format(
                         "==================== %s STARTED: %s ====================",
                         test.toString(), date.toString());
-        CLog.d(message);
+        CLog.d("\n" + message);
         logOnAllDevices(message);
     }
 
@@ -66,7 +66,7 @@
                 String.format(
                         "==================== %s ENDED: %s ====================",
                         test.toString(), date.toString());
-        CLog.d(message);
+        CLog.d("\n" + message);
         if (mTrace != null
                 && mTrace.contains(DeviceNotAvailableException.class.getCanonicalName())) {
             CLog.d("Skip logging device side, device was unavailable.");
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/src/com/android/tradefed/util/PrettyPrintDelimiter.java b/src/com/android/tradefed/util/PrettyPrintDelimiter.java
new file mode 100644
index 0000000..d69dabf
--- /dev/null
+++ b/src/com/android/tradefed/util/PrettyPrintDelimiter.java
@@ -0,0 +1,32 @@
+/*
+ * 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.util;
+
+import com.android.tradefed.log.LogUtil.CLog;
+
+/** Helper utility that helps to print delimited message that stands out. */
+public final class PrettyPrintDelimiter {
+
+    /** Prints a delimited message given a base String. */
+    public static void printStageDelimiter(String message) {
+        String limit = "";
+        for (int i = 0; i < message.length(); i++) {
+            limit += "=";
+        }
+        String finalFormat = String.format("\n%s\n%s\n%s", limit, message, limit);
+        CLog.d(finalFormat);
+    }
+}
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/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);
         }
     }