Merge "Fixing b/141252815 by extracting splits from apks before parsing the file and installing them."
diff --git a/atest/test_data/test_commands.json b/atest/test_data/test_commands.json
index c87a51b..f8251aa 100644
--- a/atest/test_data/test_commands.json
+++ b/atest/test_data/test_commands.json
@@ -1,39 +1,39 @@
 {
 "hello_world_test": [
-"atest_tradefed.sh template/atest_local_min --template:map test=atest --include-filter hello_world_test --log-level WARN"
+"atest_tradefed.sh template/atest_local_min --template:map test=atest --include-filter hello_world_test --log-level WARN --skip-loading-config-jar --logcat-on-failure"
 ], 
 "packages/apps/Car/Messenger/tests/robotests/src/com/android/car/messenger/MessengerDelegateTest.java": [
 "./build/soong/soong_ui.bash --make-mode RunCarMessengerRoboTests"
 ], 
 "CtsAnimationTestCases:AnimatorTest": [
-"atest_tradefed.sh template/atest_local_min --template:map test=atest --include-filter CtsAnimationTestCases --atest-include-filter CtsAnimationTestCases:android.animation.cts.AnimatorTest --log-level WARN"
+"atest_tradefed.sh template/atest_local_min --template:map test=atest --include-filter CtsAnimationTestCases --atest-include-filter CtsAnimationTestCases:android.animation.cts.AnimatorTest --log-level WARN --skip-loading-config-jar --logcat-on-failure"
 ], 
 "CtsSampleDeviceTestCases:android.sample.cts.SampleDeviceReportLogTest": [
-"atest_tradefed.sh template/atest_local_min --template:map test=atest --include-filter CtsSampleDeviceTestCases --atest-include-filter CtsSampleDeviceTestCases:android.sample.cts.SampleDeviceReportLogTest --log-level WARN"
+"atest_tradefed.sh template/atest_local_min --template:map test=atest --include-filter CtsSampleDeviceTestCases --atest-include-filter CtsSampleDeviceTestCases:android.sample.cts.SampleDeviceReportLogTest --log-level WARN --skip-loading-config-jar --logcat-on-failure"
 ], 
 "CtsAnimationTestCases CtsSampleDeviceTestCases": [
-"atest_tradefed.sh template/atest_local_min --template:map test=atest --include-filter CtsAnimationTestCases --include-filter CtsSampleDeviceTestCases --log-level WARN"
+"atest_tradefed.sh template/atest_local_min --template:map test=atest --include-filter CtsAnimationTestCases --include-filter CtsSampleDeviceTestCases --log-level WARN --skip-loading-config-jar --logcat-on-failure"
 ], 
 "AnimatorTest": [
-"atest_tradefed.sh template/atest_local_min --template:map test=atest --include-filter CtsAnimationTestCases --atest-include-filter CtsAnimationTestCases:android.animation.cts.AnimatorTest --log-level WARN"
+"atest_tradefed.sh template/atest_local_min --template:map test=atest --include-filter CtsAnimationTestCases --atest-include-filter CtsAnimationTestCases:android.animation.cts.AnimatorTest --log-level WARN --skip-loading-config-jar --logcat-on-failure"
 ], 
 "PacketFragmenterTest": [
-"atest_tradefed.sh template/atest_local_min --template:map test=atest --include-filter net_test_hci --atest-include-filter net_test_hci:PacketFragmenterTest.* --log-level WARN"
+"atest_tradefed.sh template/atest_local_min --template:map test=atest --include-filter net_test_hci --atest-include-filter net_test_hci:PacketFragmenterTest.* --log-level WARN --skip-loading-config-jar --logcat-on-failure"
 ], 
 "android.animation.cts": [
-"atest_tradefed.sh template/atest_local_min --template:map test=atest --include-filter CtsAnimationTestCases --atest-include-filter CtsAnimationTestCases:android.animation.cts --log-level WARN"
+"atest_tradefed.sh template/atest_local_min --template:map test=atest --include-filter CtsAnimationTestCases --atest-include-filter CtsAnimationTestCases:android.animation.cts --log-level WARN --skip-loading-config-jar --logcat-on-failure"
 ], 
 "platform_testing/tests/example/native/Android.bp": [
-"atest_tradefed.sh template/atest_local_min --template:map test=atest --include-filter hello_world_test --log-level WARN"
+"atest_tradefed.sh template/atest_local_min --template:map test=atest --include-filter hello_world_test --log-level WARN --skip-loading-config-jar --logcat-on-failure"
 ], 
 "tools/tradefederation/core/res/config/native-benchmark.xml": [
-"atest_tradefed.sh template/atest_local_min --template:map test=atest --include-filter native-benchmark --log-level WARN"
+"atest_tradefed.sh template/atest_local_min --template:map test=atest --include-filter native-benchmark --log-level WARN --logcat-on-failure"
 ], 
 "native-benchmark": [
-"atest_tradefed.sh template/atest_local_min --template:map test=atest --include-filter native-benchmark --log-level WARN"
+"atest_tradefed.sh template/atest_local_min --template:map test=atest --include-filter native-benchmark --log-level WARN --logcat-on-failure"
 ], 
 "platform_testing/tests/example/native": [
-"atest_tradefed.sh template/atest_local_min --template:map test=atest --include-filter hello_world_test --log-level WARN"
+"atest_tradefed.sh template/atest_local_min --template:map test=atest --include-filter hello_world_test --log-level WARN --skip-loading-config-jar --logcat-on-failure"
 ], 
 "VtsCodelabHelloWorldTest": [
 "vts-tradefed run commandAndExit vts-staging-default -m VtsCodelabHelloWorldTest --skip-all-system-status-check --skip-preconditions --primary-abi-only"
@@ -42,16 +42,16 @@
 "atest_tradefed.sh template/atest_local_min --template:map test=atest --atest-log-file-path=/tmp/atest_run_1568627341_v33kdA/log --include-filter aidegen_unittests --log-level WARN"
 ], 
 "HelloWorldTests": [
-"atest_tradefed.sh template/atest_local_min --template:map test=atest --include-filter HelloWorldTests --log-level WARN"
+"atest_tradefed.sh template/atest_local_min --template:map test=atest --include-filter HelloWorldTests --log-level WARN --skip-loading-config-jar --logcat-on-failure"
 ], 
 "CtsSampleDeviceTestCases:SampleDeviceTest#testSharedPreferences": [
-"atest_tradefed.sh template/atest_local_min --template:map test=atest --include-filter CtsSampleDeviceTestCases --atest-include-filter CtsSampleDeviceTestCases:android.sample.cts.SampleDeviceTest#testSharedPreferences --log-level WARN"
+"atest_tradefed.sh template/atest_local_min --template:map test=atest --include-filter CtsSampleDeviceTestCases --atest-include-filter CtsSampleDeviceTestCases:android.sample.cts.SampleDeviceTest#testSharedPreferences --log-level WARN --skip-loading-config-jar --logcat-on-failure"
 ], 
 "CtsSampleDeviceTestCases:android.sample.cts": [
-"atest_tradefed.sh template/atest_local_min --template:map test=atest --include-filter CtsSampleDeviceTestCases --atest-include-filter CtsSampleDeviceTestCases:android.sample.cts --log-level WARN"
+"atest_tradefed.sh template/atest_local_min --template:map test=atest --include-filter CtsSampleDeviceTestCases --atest-include-filter CtsSampleDeviceTestCases:android.sample.cts --log-level WARN --skip-loading-config-jar --logcat-on-failure"
 ], 
 "PacketFragmenterTest#test_no_fragment_necessary,test_ble_fragment_necessary": [
-"atest_tradefed.sh template/atest_local_min --template:map test=atest --include-filter net_test_hci --atest-include-filter net_test_hci:PacketFragmenterTest.test_ble_fragment_necessary:PacketFragmenterTest.test_no_fragment_necessary --log-level WARN"
+"atest_tradefed.sh template/atest_local_min --template:map test=atest --include-filter net_test_hci --atest-include-filter net_test_hci:PacketFragmenterTest.test_ble_fragment_necessary:PacketFragmenterTest.test_no_fragment_necessary --log-level WARN --skip-loading-config-jar --logcat-on-failure"
 ], 
 "CarMessengerRoboTests": [
 "./build/soong/soong_ui.bash --make-mode RunCarMessengerRoboTests"
diff --git a/common_util/com/android/tradefed/util/FileUtil.java b/common_util/com/android/tradefed/util/FileUtil.java
index 2034972..70ee95c 100644
--- a/common_util/com/android/tradefed/util/FileUtil.java
+++ b/common_util/com/android/tradefed/util/FileUtil.java
@@ -565,13 +565,30 @@
      * @throws FileNotFoundException
      */
     public static String readStringFromFile(File sourceFile) throws IOException {
-        FileInputStream is = null;
-        try {
-            // no need to buffer since StreamUtil does
-            is = new FileInputStream(sourceFile);
-            return StreamUtil.getStringFromStream(is);
-        } finally {
-            StreamUtil.close(is);
+        return readStringFromFile(sourceFile, 0, 0);
+    }
+
+    /**
+     * A helper method for reading partial string data from a file
+     *
+     * @param sourceFile the file to read from
+     * @param startOffset the start offset to read from the file.
+     * @param length the number of bytes to read of the file.
+     * @throws IOException
+     * @throws FileNotFoundException
+     */
+    public static String readStringFromFile(File sourceFile, long startOffset, long length)
+            throws IOException {
+        try (FileInputStream is = new FileInputStream(sourceFile)) {
+            if (startOffset < 0) {
+                startOffset = 0;
+            }
+            long fileLength = sourceFile.length();
+            is.skip(startOffset);
+            if (length <= 0 || fileLength <= startOffset + length) {
+                return StreamUtil.getStringFromStream(is);
+            }
+            return StreamUtil.getStringFromStream(is, length);
         }
     }
 
diff --git a/common_util/com/android/tradefed/util/StreamUtil.java b/common_util/com/android/tradefed/util/StreamUtil.java
index f6fce21..a8d84bb 100644
--- a/common_util/com/android/tradefed/util/StreamUtil.java
+++ b/common_util/com/android/tradefed/util/StreamUtil.java
@@ -114,11 +114,28 @@
      * @throws IOException if failure occurred reading the stream
      */
     public static String getStringFromStream(InputStream stream) throws IOException {
+        return getStringFromStream(stream, 0);
+    }
+
+    /**
+     * Retrieves a {@link String} from a character stream.
+     *
+     * @param stream the {@link InputStream}
+     * @param length the size of the content to read, set to 0 to read all contents
+     * @return a {@link String} containing the stream contents
+     * @throws IOException if failure occurred reading the stream
+     */
+    public static String getStringFromStream(InputStream stream, long length) throws IOException {
         int irChar = -1;
         StringBuilder builder = new StringBuilder();
         try (Reader ir = new BufferedReader(new InputStreamReader(stream))) {
+            long count = 0;
             while ((irChar = ir.read()) != -1) {
                 builder.append((char) irChar);
+                count++;
+                if (length > 0 && count >= length) {
+                    break;
+                }
             }
         }
         return builder.toString();
diff --git a/device_build_interfaces/com/android/tradefed/device/contentprovider/ContentProviderHandler.java b/device_build_interfaces/com/android/tradefed/device/contentprovider/ContentProviderHandler.java
index b6babf9..33420fc 100644
--- a/device_build_interfaces/com/android/tradefed/device/contentprovider/ContentProviderHandler.java
+++ b/device_build_interfaces/com/android/tradefed/device/contentprovider/ContentProviderHandler.java
@@ -312,12 +312,17 @@
             return true;
         }
 
+        CLog.d("Received from content provider:\n%s", listCommandResult);
         String[] listResult = listCommandResult.split("[\\r\\n]+");
 
         for (String row : listResult) {
             HashMap<String, String> columnValues = parseQueryResultRow(row);
             boolean isDirectory = Boolean.valueOf(columnValues.get(COLUMN_DIRECTORY));
             String name = columnValues.get(COLUMN_NAME);
+            if (name == null) {
+                CLog.w("Output from the content provider doesn't seem well formatted:\n%s", row);
+                return false;
+            }
             String path = columnValues.get(COLUMN_ABSOLUTE_PATH);
 
             File localChild = new File(localDir, name);
diff --git a/res/config/atest.xml b/res/config/atest.xml
index e2afcc6..2dbfed5 100644
--- a/res/config/atest.xml
+++ b/res/config/atest.xml
@@ -14,5 +14,9 @@
      limitations under the License.
 -->
 <configuration description="Points to the TF entry point runner for atest.">
+  <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer">
+    <option name="force-root" value="false" />
+  </target_preparer>
+
   <test class="com.android.tradefed.testtype.suite.AtestRunner" />
 </configuration>
diff --git a/res/config/suite/test_mapping_suite.xml b/res/config/suite/test_mapping_suite.xml
index e22fb7d..823f00f 100644
--- a/res/config/suite/test_mapping_suite.xml
+++ b/res/config/suite/test_mapping_suite.xml
@@ -20,6 +20,10 @@
     <system_checker class="com.android.tradefed.suite.checker.SystemServerStatusChecker" />
     <system_checker class="com.android.tradefed.suite.checker.SystemServerFileDescriptorChecker" />
 
+    <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer">
+        <option name="force-root" value="false" />
+    </target_preparer>
+
     <test class="com.android.tradefed.testtype.suite.TestMappingSuiteRunner"/>
 
     <!-- Tell all AndroidJUnitTests to exclude certain annotations -->
diff --git a/src/com/android/tradefed/command/CommandOptions.java b/src/com/android/tradefed/command/CommandOptions.java
index ee4daff..c5c6a33 100644
--- a/src/com/android/tradefed/command/CommandOptions.java
+++ b/src/com/android/tradefed/command/CommandOptions.java
@@ -22,7 +22,6 @@
 import com.android.tradefed.config.OptionUpdateRule;
 import com.android.tradefed.device.metric.AutoLogCollector;
 import com.android.tradefed.log.LogUtil.CLog;
-import com.android.tradefed.retry.RetryStrategy;
 import com.android.tradefed.util.UniqueMultiMap;
 
 import java.util.LinkedHashSet;
@@ -208,33 +207,6 @@
     )
     private String mHostLogSuffix = null;
 
-    // [Options related to auto-retry]
-    @Deprecated
-    @Option(
-        name = "max-testcase-run-count",
-        description =
-                "If the IRemoteTest can have its testcases run multiple times, "
-                        + "the max number of runs for each testcase."
-    )
-    private int mMaxRunLimit = 1;
-
-    @Deprecated
-    @Option(
-        name = "retry-strategy",
-        description =
-                "The retry strategy to be used when re-running some tests with "
-                        + "--max-testcase-run-count"
-    )
-    private RetryStrategy mRetryStrategy = RetryStrategy.NO_RETRY;
-
-    @Deprecated
-    @Option(
-        name = "auto-retry",
-        description =
-                "Whether or not to enable the new auto-retry. This is a feature flag for testing."
-    )
-    private boolean mEnableAutoRetry = false;
-
     /**
      * Set the help mode for the config.
      * <p/>
diff --git a/src/com/android/tradefed/device/metric/FilePullerDeviceMetricCollector.java b/src/com/android/tradefed/device/metric/FilePullerDeviceMetricCollector.java
index 96b5f34..5fe4e1e 100644
--- a/src/com/android/tradefed/device/metric/FilePullerDeviceMetricCollector.java
+++ b/src/com/android/tradefed/device/metric/FilePullerDeviceMetricCollector.java
@@ -137,6 +137,7 @@
 
     }
 
+
     private Entry<String, File> pullMetricFile(
             String pattern, final Map<String, String> currentMetrics) {
         Pattern p = Pattern.compile(pattern);
@@ -148,7 +149,7 @@
                         continue;
                     }
                     try {
-                        File attemptPull = device.pullFile(entry.getValue());
+                        File attemptPull = retrieveFile(device, entry.getValue());
                         if (attemptPull != null) {
                             if (mCleanUp) {
                                 device.deleteFile(entry.getValue());
@@ -171,6 +172,19 @@
     }
 
     /**
+     * Pull the file from the specified path in the device.
+     *
+     * @param device which has the file.
+     * @param remoteFilePath location in the device.
+     * @return File retrieved from the given path in the device.
+     * @throws DeviceNotAvailableException
+     */
+    protected File retrieveFile(ITestDevice device, String remoteFilePath)
+            throws DeviceNotAvailableException {
+        return device.pullFile(remoteFilePath);
+    }
+
+    /**
      * Pulls the directory and all its content from the device and save it in the
      * host under the host_tmp folder.
      *
diff --git a/src/com/android/tradefed/invoker/TestInvocation.java b/src/com/android/tradefed/invoker/TestInvocation.java
index 36a1564..2bc4e4e 100644
--- a/src/com/android/tradefed/invoker/TestInvocation.java
+++ b/src/com/android/tradefed/invoker/TestInvocation.java
@@ -214,7 +214,8 @@
             ITestInvocationListener listener,
             boolean devicePreSetupDone)
             throws Throwable {
-
+        ReportHostLog reportThread = new ReportHostLog(listener, config);
+        Runtime.getRuntime().addShutdownHook(reportThread);
         boolean resumed = false;
         String bugreportName = null;
         long startTime = System.currentTimeMillis();
@@ -357,6 +358,8 @@
                     PrettyPrintDelimiter.printStageDelimiter(message);
                 }
                 reportHostLog(listener, config);
+                // If host_log is reported, remove the hook
+                Runtime.getRuntime().removeShutdownHook(reportThread);
 
                 elapsedTime = System.currentTimeMillis() - startTime;
                 if (!resumed) {
@@ -966,4 +969,22 @@
         }
         return testTag;
     }
+
+    /** Helper Thread that ensures host_log is reported in case of killed JVM */
+    private class ReportHostLog extends Thread {
+
+        private ITestInvocationListener mListener;
+        private IConfiguration mConfiguration;
+
+        public ReportHostLog(ITestInvocationListener listener, IConfiguration config) {
+            mListener = listener;
+            mConfiguration = config;
+        }
+
+        @Override
+        public void run() {
+            // Report all the logs that always be reported anyway.
+            reportHostLog(mListener, mConfiguration);
+        }
+    }
 }
diff --git a/src/com/android/tradefed/result/suite/SuiteResultHolder.java b/src/com/android/tradefed/result/suite/SuiteResultHolder.java
index 63b80e2..8c6f1dc 100644
--- a/src/com/android/tradefed/result/suite/SuiteResultHolder.java
+++ b/src/com/android/tradefed/result/suite/SuiteResultHolder.java
@@ -38,4 +38,5 @@
     public long failedTests;
     public long startTime;
     public long endTime;
+    public String hostName;
 }
diff --git a/src/com/android/tradefed/result/suite/XmlSuiteResultFormatter.java b/src/com/android/tradefed/result/suite/XmlSuiteResultFormatter.java
index 1aa9997..67df6bf 100644
--- a/src/com/android/tradefed/result/suite/XmlSuiteResultFormatter.java
+++ b/src/com/android/tradefed/result/suite/XmlSuiteResultFormatter.java
@@ -127,6 +127,10 @@
     public static final class RunHistory {
         public long startTime;
         public long endTime;
+        public long passedTests;
+        public long failedTests;
+        public String commandLineArgs;
+        public String hostName;
     }
 
     /**
@@ -256,6 +260,10 @@
                 serializer.startTag(NS, RUN_TAG);
                 serializer.attribute(NS, START_TIME_ATTR, String.valueOf(runHistory.startTime));
                 serializer.attribute(NS, END_TIME_ATTR, String.valueOf(runHistory.endTime));
+                serializer.attribute(NS, PASS_ATTR, Long.toString(runHistory.passedTests));
+                serializer.attribute(NS, FAILED_ATTR, Long.toString(runHistory.failedTests));
+                serializer.attribute(NS, COMMAND_LINE_ARGS, runHistory.commandLineArgs);
+                serializer.attribute(NS, HOST_NAME_ATTR, runHistory.hostName);
                 serializer.endTag(NS, RUN_TAG);
             }
             serializer.endTag(NS, RUN_HISTORY_TAG);
@@ -458,6 +466,7 @@
             parser.require(XmlPullParser.START_TAG, NS, RESULT_TAG);
             invocation.startTime = (Long.valueOf(parser.getAttributeValue(NS, START_TIME_ATTR)));
             invocation.endTime = (Long.valueOf(parser.getAttributeValue(NS, END_TIME_ATTR)));
+            invocation.hostName = parser.getAttributeValue(NS, HOST_NAME_ATTR);
             context.addInvocationAttribute(
                     COMMAND_LINE_ARGS, parser.getAttributeValue(NS, COMMAND_LINE_ARGS));
             parseSuiteAttributes(parser, context);
diff --git a/src/com/android/tradefed/retry/BaseRetryDecision.java b/src/com/android/tradefed/retry/BaseRetryDecision.java
index c0187bc..7513ab5 100644
--- a/src/com/android/tradefed/retry/BaseRetryDecision.java
+++ b/src/com/android/tradefed/retry/BaseRetryDecision.java
@@ -39,6 +39,8 @@
  */
 public class BaseRetryDecision implements IRetryDecision {
 
+    private static final int ABORT_MAX_FAILURES = 50;
+
     @Option(
         name = "reboot-at-last-retry",
         description = "Reboot the device at the last retry attempt."
@@ -195,7 +197,16 @@
         Set<TestDescription> previousFailedTests = getFailedTestCases(previousResults);
         if (!mPreviouslyFailing.isEmpty()) {
             previousFailedTests.retainAll(mPreviouslyFailing);
+            mPreviouslyFailing.retainAll(previousFailedTests);
         }
+        // Abort if number of failures is high for a given one test
+        if (previousFailedTests.size() > ABORT_MAX_FAILURES) {
+            CLog.d(
+                    "Found %s failures, skipping auto-retry to avoid large overhead.",
+                    previousFailedTests.size());
+            return false;
+        }
+
         if (!previousFailedTests.isEmpty()) {
             CLog.d("Retrying the test case failure.");
             addRetriedTestsToIncludeFilters(test, previousFailedTests);
diff --git a/src/com/android/tradefed/testtype/SubprocessTfLauncher.java b/src/com/android/tradefed/testtype/SubprocessTfLauncher.java
index b64a017..80dd8f6 100644
--- a/src/com/android/tradefed/testtype/SubprocessTfLauncher.java
+++ b/src/com/android/tradefed/testtype/SubprocessTfLauncher.java
@@ -142,7 +142,10 @@
                             "google-tradefed.jar",
                             "google-tradefed-tests.jar",
                             // Google contrib jars
-                            "google-tradefed-contrib.jar"));
+                            "google-tradefed-contrib.jar",
+                            // Older jar required for coverage tests
+                            "jack-jacoco-reporter.jar",
+                            "emmalib.jar"));
 
     /** Timeout to wait for the events received from subprocess to finish being processed.*/
     private static final long EVENT_THREAD_JOIN_TIMEOUT_MS = 30 * 1000;
diff --git a/src/com/android/tradefed/testtype/suite/BaseTestSuite.java b/src/com/android/tradefed/testtype/suite/BaseTestSuite.java
index b283546..f43c7af 100644
--- a/src/com/android/tradefed/testtype/suite/BaseTestSuite.java
+++ b/src/com/android/tradefed/testtype/suite/BaseTestSuite.java
@@ -184,6 +184,7 @@
     private SuiteModuleLoader mModuleRepo;
     private Map<String, List<SuiteTestFilter>> mIncludeFiltersParsed = new HashMap<>();
     private Map<String, List<SuiteTestFilter>> mExcludeFiltersParsed = new HashMap<>();
+    private List<File> mConfigPaths = new ArrayList<>();
 
     /** {@inheritDoc} */
     @Override
@@ -304,6 +305,12 @@
     public LinkedHashMap<String, IConfiguration> loadingStrategy(
             Set<IAbi> abis, List<File> testsDirs, String suitePrefix, String suiteTag) {
         LinkedHashMap<String, IConfiguration> loadedConfigs = new LinkedHashMap<>();
+        // Load and return directly the specific config files.
+        if (!mConfigPaths.isEmpty()) {
+            CLog.d("Loading the specified configs and skip loading from the resources.");
+            return getModuleLoader().loadConfigsFromSpecifiedPaths(mConfigPaths, abis, suiteTag);
+        }
+
         // Load configs that are part of the resources
         if (!mSkipJarLoading) {
             loadedConfigs.putAll(
@@ -360,6 +367,11 @@
         mModuleArgs.addAll(moduleArgs);
     }
 
+    /** Clear the stored module args out */
+    void clearModuleArgs() {
+        mModuleArgs.clear();
+    }
+
     /** Add config patterns */
     public void addConfigPatterns(List<String> patterns) {
         mConfigPatterns.addAll(patterns);
@@ -463,6 +475,21 @@
         mExcludeFiltersParsed.clear();
     }
 
+    /**
+     * Add the config path for {@link SuiteModuleLoader} to limit the search loading
+     * configurations.
+     *
+     * @param configPath A {@code File} with the absolute path of the configuration.
+     */
+    void addConfigPaths(File configPath) {
+        mConfigPaths.add(configPath);
+    }
+
+    /** Clear the stored config paths out. */
+    void clearConfigPaths() {
+        mConfigPaths.clear();
+    }
+
     /* Helper method designed to remove filters in a list not applicable to the given module */
     private static void checkFilters(Set<String> filters, String moduleName) {
         Set<String> cleanedFilters = new HashSet<String>();
diff --git a/src/com/android/tradefed/testtype/suite/ITestSuite.java b/src/com/android/tradefed/testtype/suite/ITestSuite.java
index f5a20ac..7ff6fab 100644
--- a/src/com/android/tradefed/testtype/suite/ITestSuite.java
+++ b/src/com/android/tradefed/testtype/suite/ITestSuite.java
@@ -337,6 +337,7 @@
 
     // Current modules to run, null if not started to run yet.
     private List<ModuleDefinition> mRunModules = null;
+    private ModuleDefinition mModuleInProgress = null;
     // Logger to be used to files.
     private ITestLogger mCurrentLogger = null;
     // Whether or not we are currently in split
@@ -656,6 +657,7 @@
                             .addDeviceBuildInfo(deviceName, mContext.getBuildInfo(deviceName));
                 }
                 listener.testModuleStarted(module.getModuleInvocationContext());
+                mModuleInProgress = module;
                 // Trigger module start on module level listener too
                 new ResultForwarder(moduleListeners)
                         .testModuleStarted(module.getModuleInvocationContext());
@@ -667,6 +669,7 @@
                     // clear out module invocation context since we are now done with module
                     // execution
                     listener.testModuleEnded();
+                    mModuleInProgress = null;
                 }
                 // Module isolation routine
                 moduleIsolation(mContext, listener);
@@ -1113,6 +1116,16 @@
             runModules = createExecutionList();
         }
 
+        if (mModuleInProgress != null) {
+            // TODO: Ensure in-progress data make sense
+            String inProgressMessage =
+                    String.format(
+                            "Module %s was interrupted after starting. Results might not be "
+                                    + "accurate or complete.",
+                            mModuleInProgress.getId());
+            mModuleInProgress.reportNotExecuted(listener, inProgressMessage);
+        }
+
         while (!runModules.isEmpty()) {
             ModuleDefinition module = runModules.remove(0);
             module.reportNotExecuted(listener, message);
@@ -1376,4 +1389,9 @@
     void disableAutoRetryTimeReporting() {
         mDisableAutoRetryTimeReporting = true;
     }
+
+    @VisibleForTesting
+    void setModuleInProgress(ModuleDefinition moduleInProgress) {
+        mModuleInProgress = moduleInProgress;
+    }
 }
diff --git a/src/com/android/tradefed/testtype/suite/ModuleDefinition.java b/src/com/android/tradefed/testtype/suite/ModuleDefinition.java
index 385e06a..f80e2d1 100644
--- a/src/com/android/tradefed/testtype/suite/ModuleDefinition.java
+++ b/src/com/android/tradefed/testtype/suite/ModuleDefinition.java
@@ -136,6 +136,7 @@
     private long mElapsedTearDown = 0l;
 
     private long mStartTestTime = 0l;
+    private Long mStartModuleRunDate = null;
 
     // Tracking of retry performance
     private List<RetryStatistics> mRetryStats = new ArrayList<>();
@@ -326,6 +327,7 @@
             TestFailureListener failureListener,
             int maxRunLimit)
             throws DeviceNotAvailableException {
+        mStartModuleRunDate = System.currentTimeMillis();
         // Load extra configuration for the module from module_controller
         // TODO: make module_controller a full TF object
         boolean skipTestCases = false;
@@ -929,7 +931,9 @@
 
     /** Report completely not executed modules. */
     public final void reportNotExecuted(ITestInvocationListener listener, String message) {
-        listener.testModuleStarted(getModuleInvocationContext());
+        if (mStartModuleRunDate == null) {
+            listener.testModuleStarted(getModuleInvocationContext());
+        }
         listener.testRunStarted(getId(), 0, 0, System.currentTimeMillis());
         listener.testRunFailed(message);
         listener.testRunEnded(0, new HashMap<String, Metric>());
diff --git a/src/com/android/tradefed/testtype/suite/SuiteModuleLoader.java b/src/com/android/tradefed/testtype/suite/SuiteModuleLoader.java
index 08dd626..1884504 100644
--- a/src/com/android/tradefed/testtype/suite/SuiteModuleLoader.java
+++ b/src/com/android/tradefed/testtype/suite/SuiteModuleLoader.java
@@ -117,6 +117,20 @@
         mExcludedModuleParameters = excludedParams;
     }
 
+    /** Main loading of configurations, looking into the specified files */
+    public LinkedHashMap<String, IConfiguration> loadConfigsFromSpecifiedPaths(
+            List<File> listConfigFiles,
+            Set<IAbi> abis,
+            String suiteTag) {
+        LinkedHashMap<String, IConfiguration> toRun = new LinkedHashMap<>();
+        for (File configFile : listConfigFiles) {
+            toRun.putAll(
+                    loadOneConfig(
+                            configFile.getName(), configFile.getAbsolutePath(), abis, suiteTag));
+        }
+        return toRun;
+    }
+
     /** Main loading of configurations, looking into a folder */
     public LinkedHashMap<String, IConfiguration> loadConfigsFromDirectory(
             List<File> testsDirs,
@@ -130,11 +144,7 @@
                 ConfigurationUtil.getConfigNamesFileFromDirs(suitePrefix, testsDirs, patterns));
         // Ensure stable initial order of configurations.
         Collections.sort(listConfigFiles);
-        for (File configFile : listConfigFiles) {
-            toRun.putAll(
-                    loadOneConfig(
-                            configFile.getName(), configFile.getAbsolutePath(), abis, suiteTag));
-        }
+        toRun.putAll(loadConfigsFromSpecifiedPaths(listConfigFiles, abis, suiteTag));
         return toRun;
     }
 
diff --git a/src/com/android/tradefed/testtype/suite/TestMappingSuiteRunner.java b/src/com/android/tradefed/testtype/suite/TestMappingSuiteRunner.java
index b13ffa7..89d4545 100644
--- a/src/com/android/tradefed/testtype/suite/TestMappingSuiteRunner.java
+++ b/src/com/android/tradefed/testtype/suite/TestMappingSuiteRunner.java
@@ -19,12 +19,14 @@
 import com.android.tradefed.config.IConfiguration;
 import com.android.tradefed.config.Option;
 import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.testtype.IRemoteTest;
 import com.android.tradefed.util.testmapping.TestInfo;
 import com.android.tradefed.util.testmapping.TestMapping;
 import com.android.tradefed.util.testmapping.TestOption;
+import com.google.common.annotations.VisibleForTesting;
 
+import java.io.File;
 import java.util.ArrayList;
-import java.util.HashMap;
 import java.util.HashSet;
 import java.util.LinkedHashMap;
 import java.util.List;
@@ -89,10 +91,10 @@
      */
     @Override
     public LinkedHashMap<String, IConfiguration> loadTests() {
-        // Map between test names and a list of test sources for each test.
-        Map<String, List<String>> testsInTestMapping = new HashMap<>();
-
         Set<String> includeFilter = getIncludeFilter();
+        // Name of the tests
+        Set<String> testNames = new HashSet<>();
+        Set<TestInfo> testInfosToRun = new HashSet<>();
         if (mTestGroup == null && includeFilter.isEmpty()) {
             throw new RuntimeException(
                     "At least one of the options, --test-mapping-test-group or --include-filter, "
@@ -114,96 +116,184 @@
         }
 
         if (mTestGroup != null) {
-            Set<TestInfo> testsToRun =
+            testInfosToRun =
                     TestMapping.getTests(
                             getBuildInfo(), mTestGroup, getPrioritizeHostConfig(), mKeywords);
             if (!mTestModulesForced.isEmpty()) {
                 CLog.i("Filtering tests for the given names: %s", mTestModulesForced);
-                testsToRun =
-                        testsToRun
+                testInfosToRun =
+                        testInfosToRun
                                 .stream()
                                 .filter(testInfo -> mTestModulesForced.contains(testInfo.getName()))
                                 .collect(Collectors.toSet());
             }
-            if (testsToRun.isEmpty()) {
+            if (testInfosToRun.isEmpty()) {
                 throw new RuntimeException(
                         String.format("No test found for the given group: %s.", mTestGroup));
             }
-
-            // Name of the tests
-            Set<String> testNames = new HashSet<>();
-
-            Set<String> mappingIncludeFilters = new HashSet<>();
-            Set<String> mappingExcludeFilters = new HashSet<>();
-
-            // module-arg options compiled from test options for each test.
-            Set<String> moduleArgs = new HashSet<>();
-            for (TestInfo test : testsToRun) {
-                boolean hasIncludeFilters = false;
-                for (TestOption option : test.getOptions()) {
-                    switch (option.getName()) {
-                            // Handle include and exclude filter at the suite level to hide each
-                            // test runner specific implementation and option names related to filtering
-                        case TEST_MAPPING_INCLUDE_FILTER:
-                            hasIncludeFilters = true;
-                            mappingIncludeFilters.add(
-                                    String.format("%s %s", test.getName(), option.getValue()));
-                            break;
-                        case TEST_MAPPING_EXCLUDE_FILTER:
-                            mappingExcludeFilters.add(
-                                    String.format("%s %s", test.getName(), option.getValue()));
-                            break;
-                        default:
-                            String moduleArg =
-                                    String.format("%s:%s", test.getName(), option.getName());
-                            if (option.getValue() != null && !option.getValue().isEmpty()) {
-                                moduleArg = String.format("%s:%s", moduleArg, option.getValue());
-                            }
-                            moduleArgs.add(moduleArg);
-                            break;
-                    }
-                }
-                if (!hasIncludeFilters) {
-                    testNames.add(test.getName());
-                }
+            for (TestInfo testInfo : testInfosToRun) {
+                testNames.add(testInfo.getName());
             }
-
-            if (mappingIncludeFilters.isEmpty()) {
-                setIncludeFilter(testNames);
-            } else {
-                mappingIncludeFilters.addAll(testNames);
-                setIncludeFilter(mappingIncludeFilters);
-            }
-            if (!mappingExcludeFilters.isEmpty()) {
-                setExcludeFilter(mappingExcludeFilters);
-            }
-            addModuleArgs(moduleArgs);
-
-            for (TestInfo test : testsToRun) {
-                List<String> testSources = null;
-                // TODO(b/117880789): tests may not be grouped by name once that bug is fixed.
-                // Update the dictionary with better keys.
-                if (testsInTestMapping.containsKey(test.getName())) {
-                    testSources = testsInTestMapping.get(test.toString());
-                } else {
-                    testSources = new ArrayList<String>();
-                    testsInTestMapping.put(test.getName(), testSources);
-                }
-                testSources.addAll(test.getSources());
-            }
+            setIncludeFilter(testNames);
         }
 
+        // load all the configurations with include-filter injected.
         LinkedHashMap<String, IConfiguration> testConfigs = super.loadTests();
+
+        // Create and inject individual tests by calling super.loadTests() with each test info.
         for (Map.Entry<String, IConfiguration> entry : testConfigs.entrySet()) {
+            List<IRemoteTest> allTests = new ArrayList<>();
+            IConfiguration moduleConfig = entry.getValue();
             ConfigurationDescriptor configDescriptor =
-                    entry.getValue().getConfigurationDescription();
-            if (testsInTestMapping.containsKey(configDescriptor.getModuleName())) {
-                configDescriptor.addMetaData(
-                        TestMapping.TEST_SOURCES,
-                        testsInTestMapping.get(configDescriptor.getModuleName()));
+                    moduleConfig.getConfigurationDescription();
+            String moduleName = configDescriptor.getModuleName();
+            String configPath = moduleConfig.getName();
+            Set<TestInfo> testInfos = getTestInfos(testInfosToRun, moduleName);
+            allTests.addAll(
+                    createIndividualTests(
+                            testInfos, configPath));
+            if (!allTests.isEmpty()) {
+                // Set back to IConfiguration only if IRemoteTests are created.
+                moduleConfig.setTests(allTests);
+                // Set test sources to ConfigurationDescriptor.
+                List<String> testSources = getTestSources(testInfos);
+                configDescriptor.addMetaData(TestMapping.TEST_SOURCES, testSources);
+            }
+        }
+        return testConfigs;
+    }
+
+    /**
+     * Create individual tests with test infos for a module.
+     *
+     * @param testInfos A {@code Set<TestInfo>} containing multiple test options.
+     * @param configPath A {@code String} of configuration path.
+     * @return The {@link List<IRemoteTest>} that are injected with the test options.
+     */
+    @VisibleForTesting
+    List<IRemoteTest> createIndividualTests(Set<TestInfo> testInfos, String configPath) {
+        List<IRemoteTest> tests = new ArrayList<>();
+        if (configPath == null) {
+            throw new RuntimeException(String.format("Configuration path is null."));
+        }
+        File configFie = new File(configPath);
+        if (!configFie.exists()) {
+            throw new RuntimeException(
+                    String.format("Configuration path: %s doesn't exits.", configPath));
+        }
+        // De-duplicate test infos so that there won't be duplicate test options.
+        testInfos = dedupTestInfos(testInfos);
+        for (TestInfo testInfo : testInfos) {
+            // Clean up all the test options injected in SuiteModuleLoader.
+            super.cleanUpSuiteSetup();
+            super.clearModuleArgs();
+            clearConfigPaths();
+            // Set config path to BaseTestSuite to limit the search.
+            addConfigPaths(configFie);
+            // Inject the test options from each test info to SuiteModuleLoader.
+            parseOptions(testInfo);
+            LinkedHashMap<String, IConfiguration> config = super.loadTests();
+            for (Map.Entry<String, IConfiguration> entry : config.entrySet()) {
+                tests.addAll(entry.getValue().getTests());
+            }
+        }
+        return tests;
+    }
+
+    /**
+     * Get a list of path of TEST_MAPPING for a module.
+     *
+     * @param testInfos A {@code Set<TestInfo>} containing multiple test options.
+     * @return A {@code List<String>} of TEST_MAPPING path.
+     */
+    @VisibleForTesting
+    List<String> getTestSources(Set<TestInfo> testInfos) {
+        List<String> testSources = new ArrayList<>();
+        for (TestInfo testInfo : testInfos) {
+            testSources.addAll(testInfo.getSources());
+        }
+        return testSources;
+    }
+
+    /**
+     * Parse the test options for the test info.
+     *
+     * @param testInfo A {@code Set<TestInfo>} containing multiple test options.
+     */
+    @VisibleForTesting
+    void parseOptions(TestInfo testInfo) {
+        Set<String> mappingIncludeFilters = new HashSet<>();
+        Set<String> mappingExcludeFilters = new HashSet<>();
+        // module-arg options compiled from test options for each test.
+        Set<String> moduleArgs = new HashSet<>();
+        Set<String> testNames = new HashSet<>();
+        for (TestOption option : testInfo.getOptions()) {
+            switch (option.getName()) {
+                // Handle include and exclude filter at the suite level to hide each
+                // test runner specific implementation and option names related to filtering
+                case TEST_MAPPING_INCLUDE_FILTER:
+                    mappingIncludeFilters.add(
+                            String.format("%s %s", testInfo.getName(), option.getValue()));
+                    break;
+                case TEST_MAPPING_EXCLUDE_FILTER:
+                    mappingExcludeFilters.add(
+                            String.format("%s %s", testInfo.getName(), option.getValue()));
+                    break;
+                default:
+                    String moduleArg =
+                            String.format("%s:%s", testInfo.getName(), option.getName());
+                    if (option.getValue() != null && !option.getValue().isEmpty()) {
+                        moduleArg = String.format("%s:%s", moduleArg, option.getValue());
+                    }
+                    moduleArgs.add(moduleArg);
+                    break;
             }
         }
 
-        return testConfigs;
+        if (mappingIncludeFilters.isEmpty()) {
+            testNames.add(testInfo.getName());
+            setIncludeFilter(testNames);
+        } else {
+            setIncludeFilter(mappingIncludeFilters);
+        }
+        if (!mappingExcludeFilters.isEmpty()) {
+            setExcludeFilter(mappingExcludeFilters);
+        }
+        addModuleArgs(moduleArgs);
+    }
+
+    /**
+     * De-duplicate test infos with the same test options.
+     *
+     * @param testInfos A {@code Set<TestInfo>} containing multiple test options.
+     * @return A {@code Set<TestInfo>} of tests without duplicated test options.
+     */
+    @VisibleForTesting
+    Set<TestInfo> dedupTestInfos(Set<TestInfo> testInfos) {
+        Set<String> nameOptions = new HashSet<>();
+        Set<TestInfo> dedupTestInfos = new HashSet<>();
+        for (TestInfo testInfo : testInfos) {
+            String nameOption = testInfo.getName() + testInfo.getOptions().toString();
+            if (!nameOptions.contains(nameOption)) {
+                dedupTestInfos.add(testInfo);
+                nameOptions.add(nameOption);
+            }
+        }
+        return dedupTestInfos;
+    }
+
+    /**
+     * Get the test infos for the given module name.
+     *
+     * @param testInfos A {@code Set<TestInfo>} containing multiple test options.
+     * @param moduleName A {@code String} name of a test module.
+     * @return A {@code Set<TestInfo>} of tests for a module.
+     */
+    @VisibleForTesting
+    Set<TestInfo> getTestInfos(Set<TestInfo> testInfos, String moduleName) {
+        return testInfos
+                .stream()
+                .filter(testInfo -> moduleName.equals(testInfo.getName()))
+                .collect(Collectors.toSet());
     }
 }
diff --git a/src/com/android/tradefed/util/JavaCodeCoverageFlusher.java b/src/com/android/tradefed/util/JavaCodeCoverageFlusher.java
index 9586e6e..5377163 100644
--- a/src/com/android/tradefed/util/JavaCodeCoverageFlusher.java
+++ b/src/com/android/tradefed/util/JavaCodeCoverageFlusher.java
@@ -32,7 +32,7 @@
  * A utility class that resets and forces a flush of Java code coverage measurements from processes
  * running on the device.
  */
-public final class JavaCodeCoverageFlusher {
+public class JavaCodeCoverageFlusher {
 
     private static final String COVERAGE_RESET_FORMAT =
             "am attach-agent %s /system/lib/libdumpcoverage.so=reset";
diff --git a/src/com/android/tradefed/util/NativeCodeCoverageFlusher.java b/src/com/android/tradefed/util/NativeCodeCoverageFlusher.java
index b53542a..7c0ae3b 100644
--- a/src/com/android/tradefed/util/NativeCodeCoverageFlusher.java
+++ b/src/com/android/tradefed/util/NativeCodeCoverageFlusher.java
@@ -35,18 +35,21 @@
     private static final String CLEAR_NATIVE_COVERAGE_FILES = "rm -rf /data/misc/trace/*";
 
     private final ITestDevice mDevice;
+    private final List<String> mProcessNames;
 
-    public NativeCodeCoverageFlusher(ITestDevice device) {
+    public NativeCodeCoverageFlusher(ITestDevice device, List<String> processNames) {
         mDevice = device;
+        mProcessNames = processNames;
     }
 
     /**
-     * Clears coverage measurements from disk on the device. Device must be in adb root.
+     * Resets native coverage counters for processes running on the device and clears any existing
+     * coverage measurements from disk. Device must be in adb root.
      *
      * @throws DeviceNotAvailableException
      */
-    public void clearCoverageMeasurements() throws DeviceNotAvailableException {
-        checkState(mDevice.isAdbRoot(), "adb root is required to clear coverage files.");
+    public void resetCoverage() throws DeviceNotAvailableException {
+        forceCoverageFlush();
         mDevice.executeShellCommand(CLEAR_NATIVE_COVERAGE_FILES);
     }
 
@@ -54,20 +57,18 @@
      * Forces a flush of native coverage data from processes running on the device. Device must be
      * in adb root.
      *
-     * @param processNames the name of processes to target for flushing; if empty, flushes from all
-     *     running native processes on the device.
      * @throws DeviceNotAvailableException
      */
-    public void forceCoverageFlush(List<String> processNames) throws DeviceNotAvailableException {
+    public void forceCoverageFlush() throws DeviceNotAvailableException {
         checkState(mDevice.isAdbRoot(), "adb root is required to flush native coverage data.");
 
-        if ((processNames == null) || processNames.isEmpty()) {
+        if ((mProcessNames == null) || mProcessNames.isEmpty()) {
             // Use the special pid -1 to trigger a coverage flush of all running processes.
             mDevice.executeShellCommand(String.format(COVERAGE_FLUSH_COMMAND_FORMAT, "-1"));
         } else {
             // Look up the pid of the processes to send them the coverage flush signal.
             StringJoiner pidString = new StringJoiner(" ");
-            for (String processName : processNames) {
+            for (String processName : mProcessNames) {
                 String pid = mDevice.getProcessPid(processName);
                 if (pid == null) {
                     CLog.w("Did not find pid for process \"%s\".", processName);
diff --git a/src/com/android/tradefed/util/SubprocessEventHelper.java b/src/com/android/tradefed/util/SubprocessEventHelper.java
index 50028d9..be182b1 100644
--- a/src/com/android/tradefed/util/SubprocessEventHelper.java
+++ b/src/com/android/tradefed/util/SubprocessEventHelper.java
@@ -546,7 +546,7 @@
                         mModuleContext
                                 .getAttributes()
                                 .getUniqueMap()
-                                .get(ModuleDefinition.MODULE_NAME);
+                                .get(ModuleDefinition.MODULE_ID);
                 if (moduleName != null) {
                     tags.put(MODULE_NAME, moduleName);
                 }
diff --git a/src/com/android/tradefed/util/testmapping/TestMapping.java b/src/com/android/tradefed/util/testmapping/TestMapping.java
index 8221d1e..1b02e29 100644
--- a/src/com/android/tradefed/util/testmapping/TestMapping.java
+++ b/src/com/android/tradefed/util/testmapping/TestMapping.java
@@ -281,7 +281,7 @@
             FileUtil.recursiveDelete(testMappingsDir);
         }
 
-        return TestMapping.mergeTests(tests);
+        return tests;
     }
 
     /**
diff --git a/test_framework/com/android/tradefed/device/metric/PerfettoPullerMetricCollector.java b/test_framework/com/android/tradefed/device/metric/PerfettoPullerMetricCollector.java
index 0f03c0f..1086d9e 100644
--- a/test_framework/com/android/tradefed/device/metric/PerfettoPullerMetricCollector.java
+++ b/test_framework/com/android/tradefed/device/metric/PerfettoPullerMetricCollector.java
@@ -19,6 +19,9 @@
 import com.android.annotations.VisibleForTesting;
 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.device.LargeOutputReceiver;
 import com.android.tradefed.log.LogUtil.CLog;
 import com.android.tradefed.metrics.proto.MetricMeasurement.DataType;
 import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
@@ -30,10 +33,17 @@
 import com.android.tradefed.util.FileUtil;
 import com.android.tradefed.util.Pair;
 import com.android.tradefed.util.RunUtil;
+import com.android.tradefed.util.StreamUtil;
 
 import com.google.common.base.Joiner;
 
+import java.io.ByteArrayOutputStream;
 import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
@@ -53,15 +63,38 @@
     private static final String EXTRACTOR_FAILURE = "0";
     private static final String EXTRACTOR_RUNTIME = "trace_extractor_runtime";
 
+    @Option(name = "compress-perfetto",
+            description = "If enabled retrieves the perfetto compressed content,"
+                    + "decompress for processing and upload the compressed file. If"
+                    + "this flag is not enabled uncompressed version of perfetto file is"
+                    + "pulled, processed and uploaded.")
+    private boolean mCompressPerfetto = false;
+
+    @Option(name = "max-compressed-file-size", description = "Max size of the compressed"
+            + " perfetto file. If the compressed file size exceeds the max then"
+            + " post processing and uploading the compressed file will not happen.")
+    private long mMaxCompressedFileSize = 10000L * 1024 * 1024;
+
+    @Option(
+            name = "compressed-trace-shell-timeout",
+            description = "Timeout for retrieving compressed trace content through shell",
+            isTimeVal = true)
+    private long mCompressedTimeoutMs = TimeUnit.MINUTES.toMillis(20);
+
+    @Option(
+            name = "decompress-perfetto-timeout",
+            description = "Timeout to decompress perfetto compressed file.",
+            isTimeVal = true)
+    private long mDecompressTimeoutMs = TimeUnit.MINUTES.toMillis(20);
+
     @Option(
             name = "perfetto-binary-path",
             description = "Path to the script files used to analyze the trace files.")
     private List<File> mScriptFiles = new ArrayList<>();
 
     @Option(
-        name = "perfetto-binary-args",
-        description = "Extra arguments to be passed to the binaries."
-    )
+            name = "perfetto-binary-args",
+            description = "Extra arguments to be passed to the binaries.")
     private List<String> mPerfettoBinaryArgs = new ArrayList<>();
 
     @Option(
@@ -71,24 +104,23 @@
 
     // List of process names passed to perfetto binary.
     @Option(
-        name = "process-name",
-        description =
-                "Process names to be passed in perfetto script."
-    )
+            name = "process-name",
+            description =
+            "Process names to be passed in perfetto script.")
     private Collection<String> mProcessNames = new ArrayList<String>();
 
     // Timeout for the script to process the trace files.
     // The default is arbitarily chosen to be 5 mins to prevent the test spending more time in
     // processing the files.
     @Option(
-        name = "perfetto-script-timeout",
-        description = "Timeout for the perfetto script.",
-        isTimeVal = true
-    )
+            name = "perfetto-script-timeout",
+            description = "Timeout for the perfetto script.",
+            isTimeVal = true)
     private long mScriptTimeoutMs = TimeUnit.MINUTES.toMillis(5);
 
     /**
      * Process the perfetto trace file for the additional metrics and add it to final metrics.
+     * Decompress the perfetto file for processing if the compression was enabled.
      *
      * @param key the option key associated to the file that was pulled from the device.
      * @param metricFile the {@link File} pulled from the device matching the option key.
@@ -97,72 +129,206 @@
     @Override
     public void processMetricFile(String key, File metricFile,
             DeviceMetricData data) {
-        // Extract the metrics from the trace file.
-        for (File scriptFile : mScriptFiles) {
-            // Apply necessary execute permissions to the script.
-            FileUtil.chmodGroupRWX(scriptFile);
+        File processSrcFile = metricFile;
+        if (mCompressPerfetto) {
+            processSrcFile = decompressFile(metricFile);
+        }
 
-            List<String> commandArgsList = new ArrayList<String>();
-            commandArgsList.add(scriptFile.getAbsolutePath());
-            commandArgsList.addAll(mPerfettoBinaryArgs);
-            commandArgsList.add("-trace_file");
-            commandArgsList.add(metricFile.getAbsolutePath());
+        if (processSrcFile != null) {
+            // Extract the metrics from the trace file.
+            for (File scriptFile : mScriptFiles) {
+                // Apply necessary execute permissions to the script.
+                FileUtil.chmodGroupRWX(scriptFile);
 
-            if (!mProcessNames.isEmpty()) {
-                commandArgsList.add("-process_names");
-                commandArgsList.add(Joiner.on(",").join(mProcessNames));
-            }
+                List<String> commandArgsList = new ArrayList<String>();
+                commandArgsList.add(scriptFile.getAbsolutePath());
+                commandArgsList.addAll(mPerfettoBinaryArgs);
+                commandArgsList.add("-trace_file");
+                commandArgsList.add(processSrcFile.getAbsolutePath());
 
-            String traceExtractorStatus = EXTRACTOR_SUCCESS;
-
-            double scriptDuration = 0;
-            double scriptStartTime = System.currentTimeMillis();
-            CommandResult cr = runHostCommand(commandArgsList.toArray(new String[commandArgsList
-                    .size()]));
-            scriptDuration = System.currentTimeMillis() - scriptStartTime;
-
-            // Update the script duration metrics.
-            Metric.Builder metricDurationBuilder = Metric.newBuilder();
-            metricDurationBuilder.getMeasurementsBuilder().setSingleDouble(scriptDuration);
-            data.addMetric(
-                    String.format("%s_%s", mMetricPrefix, EXTRACTOR_RUNTIME),
-                    metricDurationBuilder.setType(DataType.RAW));
-
-            if (CommandStatus.SUCCESS.equals(cr.getStatus())) {
-                String[] metrics = cr.getStdout().split(LINE_SEPARATOR);
-                for (String metric : metrics) {
-                    Pair<String, String> kv = splitKeyValue(metric);
-
-                    if (kv != null) {
-                        Metric.Builder metricBuilder = Metric.newBuilder();
-                        metricBuilder.getMeasurementsBuilder().setSingleString(kv.second);
-                        data.addMetric(
-                                String.format("%s_%s", mMetricPrefix, kv.first),
-                                metricBuilder.setType(DataType.RAW));
-                    } else {
-                        CLog.e("Output %s not in the expected format.", metric);
-                    }
+                if (!mProcessNames.isEmpty()) {
+                    commandArgsList.add("-process_names");
+                    commandArgsList.add(Joiner.on(",").join(mProcessNames));
                 }
-                CLog.i(cr.getStdout());
-            } else {
-                traceExtractorStatus = EXTRACTOR_FAILURE;
-                CLog.e("Unable to parse the trace file %s due to %s - Status - %s ",
-                        metricFile.getName(), cr.getStderr(), cr.getStatus());
-            }
 
-            Metric.Builder metricStatusBuilder = Metric.newBuilder();
-            metricStatusBuilder.getMeasurementsBuilder().setSingleString(traceExtractorStatus);
-            data.addMetric(
-                    String.format("%s_%s", mMetricPrefix, EXTRACTOR_STATUS),
-                    metricStatusBuilder.setType(DataType.RAW));
+                String traceExtractorStatus = EXTRACTOR_SUCCESS;
+
+                double scriptDuration = 0;
+                double scriptStartTime = System.currentTimeMillis();
+                CommandResult cr = runHostCommand(mScriptTimeoutMs,
+                        commandArgsList.toArray(new String[commandArgsList.size()]), null, null);
+                scriptDuration = System.currentTimeMillis() - scriptStartTime;
+
+                // Update the script duration metrics.
+                Metric.Builder metricDurationBuilder = Metric.newBuilder();
+                metricDurationBuilder.getMeasurementsBuilder().setSingleDouble(scriptDuration);
+                data.addMetric(
+                        String.format("%s_%s", mMetricPrefix, EXTRACTOR_RUNTIME),
+                        metricDurationBuilder.setType(DataType.RAW));
+
+                if (CommandStatus.SUCCESS.equals(cr.getStatus())) {
+                    String[] metrics = cr.getStdout().split(LINE_SEPARATOR);
+                    for (String metric : metrics) {
+                        Pair<String, String> kv = splitKeyValue(metric);
+
+                        if (kv != null) {
+                            Metric.Builder metricBuilder = Metric.newBuilder();
+                            metricBuilder.getMeasurementsBuilder().setSingleString(kv.second);
+                            data.addMetric(
+                                    String.format("%s_%s", mMetricPrefix, kv.first),
+                                    metricBuilder.setType(DataType.RAW));
+                        } else {
+                            CLog.e("Output %s not in the expected format.", metric);
+                        }
+                    }
+                    CLog.i(cr.getStdout());
+                } else {
+                    traceExtractorStatus = EXTRACTOR_FAILURE;
+                    CLog.e("Unable to parse the trace file %s due to %s - Status - %s ",
+                            processSrcFile.getName(), cr.getStderr(), cr.getStatus());
+                }
+
+                if (mCompressPerfetto) {
+                    processSrcFile.delete();
+                }
+                Metric.Builder metricStatusBuilder = Metric.newBuilder();
+                metricStatusBuilder.getMeasurementsBuilder().setSingleString(traceExtractorStatus);
+                data.addMetric(
+                        String.format("%s_%s", mMetricPrefix, EXTRACTOR_STATUS),
+                        metricStatusBuilder.setType(DataType.RAW));
+            }
         }
 
         // Upload and delete the host trace file.
         try (InputStreamSource source = new FileInputStreamSource(metricFile, true)) {
-            testLog(metricFile.getName(), LogDataType.PB, source);
+            if (mCompressPerfetto) {
+                if (processSrcFile != null) {
+                    testLog(metricFile.getName(), LogDataType.GZIP, source);
+                } else {
+                    metricFile.delete();
+                }
+
+            } else {
+                testLog(metricFile.getName(), LogDataType.PB, source);
+            }
         }
     }
 
+    /**
+     * Pull the file from the specified path in the device. Pull the compressed content of the
+     * perfetto file if the compress perfetto option is enabled.
+     *
+     * @param device which has the file.
+     * @param remoteFilePath location in the device.
+     * @return compressed or decompressed version of perfetto file based on mCompressPerfetto
+     *         option is set or not.
+     * @throws DeviceNotAvailableException
+     */
+    @Override
+    protected File retrieveFile(ITestDevice device, String remoteFilePath)
+            throws DeviceNotAvailableException {
+        if (!mCompressPerfetto) {
+            return super.retrieveFile(device, remoteFilePath);
+        }
+        File perfettoCompressedFile = null;
+        try {
+            String filePathInDevice = remoteFilePath;
+            CLog.i("Retrieving the compressed perfetto trace content from device.");
+            LargeOutputReceiver compressedOutputReceiver = new LargeOutputReceiver(
+                    "perfetto_compressed_temp",
+                    device.getSerialNumber(), mMaxCompressedFileSize);
+            device.executeShellCommand(
+                    String.format("gzip -c %s", filePathInDevice),
+                    compressedOutputReceiver,
+                    mCompressedTimeoutMs, 10000, TimeUnit.MILLISECONDS, 1);
+            compressedOutputReceiver.flush();
+            compressedOutputReceiver.cancel();
+
+            // Copy to temp file which will be used for decompression, perfetto
+            // metrics extraction and uploading the file later.
+            try (InputStreamSource largeStreamSrc = compressedOutputReceiver.getData();
+                    InputStream inputStream = largeStreamSrc.createInputStream()) {
+                perfettoCompressedFile = FileUtil.createTempFile(
+                        "perfetto_compressed", ".gz");
+                FileOutputStream outStream = new FileOutputStream(
+                        perfettoCompressedFile);
+                byte[] buffer = new byte[4096];
+                int bytesRead = -1;
+                while ((bytesRead = inputStream.read(buffer)) > -1) {
+                    outStream.write(buffer, 0, bytesRead);
+                }
+                StreamUtil.close(outStream);
+                CLog.i("Successfully copied the compressed content from device to"
+                        + " host.");
+            } catch (IOException e) {
+                if (perfettoCompressedFile != null) {
+                    perfettoCompressedFile.delete();
+                }
+                CLog.e("Failed to copy compressed perfetto to temporary file.");
+                CLog.e(e);
+            } finally {
+                compressedOutputReceiver.delete();
+            }
+        } catch (DeviceNotAvailableException e) {
+            CLog.e(
+                    "Exception when retrieveing compressed perfetto trace file '%s' "
+                            + "from %s", remoteFilePath, device.getSerialNumber());
+            CLog.e(e);
+        }
+        return perfettoCompressedFile;
+    }
+
+    /**
+     * Decompress the file to a temporary file in the host.
+     *
+     * @param compressedFile file to be decompressed.
+     * @return decompressed file used for postprocessing.
+     */
+    private File decompressFile(File compressedFile) {
+        File decompressedFile = null;
+        try {
+            decompressedFile = FileUtil.createTempFile("perfetto_decompressed", ".pb");
+        } catch (IOException e) {
+            CLog.e("Not able to create decompressed perfetto file.");
+            CLog.e(e);
+            return null;
+        }
+        // Keep the original file for uploading.
+        List<String> decompressArgsList = new ArrayList<String>();
+        decompressArgsList.add("gzip");
+        decompressArgsList.add("-k");
+        decompressArgsList.add("-c");
+        decompressArgsList.add("-d");
+        decompressArgsList.add(compressedFile.getAbsolutePath());
+
+        // Decompress perfetto trace file.
+        CLog.i("Start decompressing the perfetto trace file.");
+        try (FileOutputStream outStream = new FileOutputStream(decompressedFile);
+                ByteArrayOutputStream errStream = new ByteArrayOutputStream()) {
+            CommandResult decompressResult = runHostCommand(mDecompressTimeoutMs,
+                    decompressArgsList.toArray(new String[decompressArgsList
+                            .size()]), outStream, errStream);
+
+            if (!CommandStatus.SUCCESS.equals(decompressResult.getStatus())) {
+                CLog.e("Unable decompress the metric file %s due to %s - Status - %s ",
+                        compressedFile.getName(), errStream.toString(),
+                        decompressResult.getStatus());
+                decompressedFile.delete();
+                return null;
+            }
+        } catch (FileNotFoundException e) {
+            CLog.e("Not able to find the decompressed file to copy the"
+                    + " decompressed contents.");
+            CLog.e(e);
+            return null;
+        } catch (IOException e1) {
+            CLog.e("Unable to close the streams.");
+            CLog.e(e1);
+        }
+        CLog.i("Successfully decompressed the perfetto trace file.");
+        return decompressedFile;
+    }
+
     @Override
     public void processMetricDirectory(String key, File metricDirectory, DeviceMetricData runData) {
         // Implement if all the files under specific directory have to be post processed.
@@ -172,11 +338,17 @@
      * Run a host command with the given array of command args.
      *
      * @param commandArgs args to be used to construct the host command.
+     * @param stdout output of the command.
+     * @param stderr error message if any from the command.
      * @return return the command results.
      */
     @VisibleForTesting
-    CommandResult runHostCommand(String[] commandArgs) {
-        return RunUtil.getDefault().runTimedCmd(mScriptTimeoutMs, commandArgs);
+    CommandResult runHostCommand(long timeOut, String[] commandArgs, OutputStream stdout,
+            OutputStream stderr) {
+        if (stdout != null && stderr != null) {
+            return RunUtil.getDefault().runTimedCmd(timeOut, stdout, stderr, commandArgs);
+        }
+        return RunUtil.getDefault().runTimedCmd(timeOut, commandArgs);
     }
 
     @VisibleForTesting
diff --git a/test_framework/com/android/tradefed/targetprep/multi/MixImageZipPreparer.java b/test_framework/com/android/tradefed/targetprep/multi/MixImageZipPreparer.java
index e3b7195..4074e04 100644
--- a/test_framework/com/android/tradefed/targetprep/multi/MixImageZipPreparer.java
+++ b/test_framework/com/android/tradefed/targetprep/multi/MixImageZipPreparer.java
@@ -157,8 +157,17 @@
 
             Map<String, InputStreamFactory> dummyFiles =
                     createDummyInputStreamFactories(mDummyFileNames);
-            dummyFiles = replaceExistingEntries(dummyFiles, files);
-            filesNotInDeviceBuild.putAll(dummyFiles);
+            Map<String, InputStreamFactory> dummyFilesNotInDeviceBuild =
+                    replaceExistingEntries(dummyFiles, files);
+            // The purpose of the dummy files is to make fastboot shrink product partition.
+            // Some devices don't have product partition and image. If the dummy file names are not
+            // found in device build, they are ignored so that devices with and without product
+            // partition can share configurations.
+            if (!dummyFilesNotInDeviceBuild.isEmpty()) {
+                CLog.w(
+                        "Skip creating dummy images: %s",
+                        String.join(",", dummyFilesNotInDeviceBuild.keySet()));
+            }
 
             if (resourceBuildInfo != null) {
                 // Get specified files from resource build and replace those in device build.
diff --git a/test_framework/com/android/tradefed/testtype/GTest.java b/test_framework/com/android/tradefed/testtype/GTest.java
index 642dbe9..7668e78 100644
--- a/test_framework/com/android/tradefed/testtype/GTest.java
+++ b/test_framework/com/android/tradefed/testtype/GTest.java
@@ -394,17 +394,18 @@
         }
         // Insert the coverage listener if code coverage collection is enabled.
         listener = addNativeCoverageListenerIfEnabled(listener);
-        NativeCodeCoverageFlusher flusher = new NativeCodeCoverageFlusher(mDevice);
+        NativeCodeCoverageFlusher flusher =
+                new NativeCodeCoverageFlusher(mDevice, getCoverageOptions().getCoverageProcesses());
 
         Throwable throwable = null;
         try {
             if (getCoverageOptions().isCoverageEnabled()) {
+                flusher.resetCoverage();
+
                 // Clang will no longer create directories that are part of the GCOV_PREFIX
                 // environment variable. Force create the /data/misc/trace/testcoverage dir to
                 // prevent "No such file or directory" errors when writing test coverage to disk.
                 mDevice.executeShellCommand("mkdir /data/misc/trace/testcoverage");
-                flusher.forceCoverageFlush(getCoverageOptions().getCoverageProcesses());
-                flusher.clearCoverageMeasurements();
             }
             doRunAllTestsInSubdirectory(testPath, mDevice, listener);
         } catch (Throwable t) {
diff --git a/test_framework/com/android/tradefed/testtype/InstrumentationTest.java b/test_framework/com/android/tradefed/testtype/InstrumentationTest.java
index e97fb90..3596d20 100644
--- a/test_framework/com/android/tradefed/testtype/InstrumentationTest.java
+++ b/test_framework/com/android/tradefed/testtype/InstrumentationTest.java
@@ -50,10 +50,14 @@
 import com.android.tradefed.result.ddmlib.DefaultRemoteAndroidTestRunner;
 import com.android.tradefed.retry.IRetryDecision;
 import com.android.tradefed.retry.RetryStrategy;
+import com.android.tradefed.testtype.coverage.CoverageOptions;
+import com.android.tradefed.testtype.coverage.CoverageOptions.Toolchain;
 import com.android.tradefed.util.AbiFormatter;
 import com.android.tradefed.util.ArrayUtil;
+import com.android.tradefed.util.JavaCodeCoverageFlusher;
 import com.android.tradefed.util.ListInstrumentationParser;
 import com.android.tradefed.util.ListInstrumentationParser.InstrumentationTarget;
+import com.android.tradefed.util.NativeCodeCoverageFlusher;
 import com.android.tradefed.util.StringEscapeUtils;
 
 import com.google.common.annotations.VisibleForTesting;
@@ -913,6 +917,24 @@
             mRunner.addInstrumentationArg("listener", ArrayUtil.join(",", mExtraDeviceListener));
         }
 
+        // Clear coverage measurements on the device before running.
+        if (mConfiguration != null
+                && mConfiguration.getCoverageOptions().isCoverageFlushEnabled()) {
+            CoverageOptions options = mConfiguration.getCoverageOptions();
+
+            if (options.getCoverageToolchains().contains(Toolchain.GCOV)) {
+                NativeCodeCoverageFlusher flusher =
+                        new NativeCodeCoverageFlusher(mDevice, options.getCoverageProcesses());
+                flusher.resetCoverage();
+            }
+
+            if (options.getCoverageToolchains().contains(Toolchain.JACOCO)) {
+                JavaCodeCoverageFlusher flusher =
+                        new JavaCodeCoverageFlusher(mDevice, options.getCoverageProcesses());
+                flusher.resetCoverage();
+            }
+        }
+
         if (testsToRun == null) {
             // Failed to collect the tests or collection is off. Just try to run them all.
             mDevice.runInstrumentationTests(mRunner, listener);
@@ -958,8 +980,11 @@
         }
         if (mConfiguration.getCoverageOptions().isCoverageEnabled()
                 && mConfiguration.getCoverageOptions().getCoverageToolchains().contains(JACOCO)) {
-            // TODO(b/137857876): Pass cross-process coverage options to the Java listener.
-            return new JavaCodeCoverageListener(getDevice(), mMergeCoverageMeasurements, listener);
+            return new JavaCodeCoverageListener(
+                    getDevice(),
+                    mConfiguration.getCoverageOptions(),
+                    mMergeCoverageMeasurements,
+                    listener);
         }
         return listener;
     }
diff --git a/test_framework/com/android/tradefed/testtype/JavaCodeCoverageListener.java b/test_framework/com/android/tradefed/testtype/JavaCodeCoverageListener.java
index c58e3c6..bc9929d 100644
--- a/test_framework/com/android/tradefed/testtype/JavaCodeCoverageListener.java
+++ b/test_framework/com/android/tradefed/testtype/JavaCodeCoverageListener.java
@@ -17,6 +17,7 @@
 package com.android.tradefed.testtype;
 
 import static com.google.common.base.Verify.verifyNotNull;
+import static com.google.common.io.Files.getNameWithoutExtension;
 
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.device.ITestDevice;
@@ -25,7 +26,12 @@
 import com.android.tradefed.result.ITestInvocationListener;
 import com.android.tradefed.result.LogDataType;
 import com.android.tradefed.result.ResultForwarder;
+import com.android.tradefed.testtype.coverage.CoverageOptions;
 import com.android.tradefed.util.FileUtil;
+import com.android.tradefed.util.JavaCodeCoverageFlusher;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableList;
 
 import org.jacoco.core.tools.ExecFileLoader;
 
@@ -43,18 +49,31 @@
     public static final String COVERAGE_MEASUREMENT_KEY = "coverageFilePath";
 
     private final ITestDevice mDevice;
+    private final CoverageOptions mCoverageOptions;
 
     private final boolean mMergeCoverageMeasurements;
 
     private final ExecFileLoader mExecFileLoader = new ExecFileLoader();
 
+    private JavaCodeCoverageFlusher mFlusher;
     private String mCurrentRunName;
 
     public JavaCodeCoverageListener(
-            ITestDevice device, boolean mergeMeasurements, ITestInvocationListener... listeners) {
+            ITestDevice device,
+            CoverageOptions options,
+            boolean mergeMeasurements,
+            ITestInvocationListener... listeners) {
         super(listeners);
         mDevice = device;
+        mCoverageOptions = options;
         mMergeCoverageMeasurements = mergeMeasurements;
+
+        mFlusher = new JavaCodeCoverageFlusher(device, options.getCoverageProcesses());
+    }
+
+    @VisibleForTesting
+    public void setCoverageFlusher(JavaCodeCoverageFlusher flusher) {
+        mFlusher = flusher;
     }
 
     @Override
@@ -76,10 +95,7 @@
                 mExecFileLoader.save(mergedMeasurements, false);
 
                 // Save the merged measurement as a test log.
-                try (FileInputStreamSource source =
-                        new FileInputStreamSource(mergedMeasurements, true)) {
-                    testLog("merged_runtime_coverage", LogDataType.COVERAGE, source);
-                }
+                logCoverageMeasurement("merged_runtime_coverage", mergedMeasurements);
             } catch (IOException e) {
                 throw new RuntimeException(e);
             } finally {
@@ -93,37 +109,55 @@
                 super.testRunEnded(elapsedTime, runMetrics);
                 return;
             }
-            String devicePath = devicePathMetric.getMeasurements().getSingleString();
-            if (devicePath == null) {
+            String testCoveragePath = devicePathMetric.getMeasurements().getSingleString();
+            if (testCoveragePath == null) {
                 super.testRunFailed("No coverage measurement.");
                 super.testRunEnded(elapsedTime, runMetrics);
                 return;
             }
 
+            ImmutableList.Builder<String> devicePaths = ImmutableList.builder();
+            devicePaths.add(testCoveragePath);
+
             File coverageFile = null;
             try {
-                coverageFile = mDevice.pullFile(devicePath);
-                verifyNotNull(coverageFile, "Failed to pull the coverage file from %s", devicePath);
+                if (mCoverageOptions.isCoverageFlushEnabled()) {
+                    devicePaths.addAll(mFlusher.forceCoverageFlush());
+                }
 
-                // When merging, load the measurement data. Otherwise log the measurement
-                // immediately.
-                if (mMergeCoverageMeasurements) {
-                    mExecFileLoader.load(coverageFile);
-                } else {
-                    try (FileInputStreamSource source =
-                            new FileInputStreamSource(coverageFile, true)) {
-                        testLog(
-                                mCurrentRunName + "_runtime_coverage",
-                                LogDataType.COVERAGE,
-                                source);
+                for (String devicePath : devicePaths.build()) {
+                    coverageFile = mDevice.pullFile(devicePath);
+                    verifyNotNull(
+                            coverageFile, "Failed to pull the coverage file from %s", devicePath);
+
+                    // When merging, load the measurement data. Otherwise log the measurement
+                    // immediately.
+                    try {
+                        if (mMergeCoverageMeasurements) {
+                            mExecFileLoader.load(coverageFile);
+                        } else {
+                            logCoverageMeasurement(
+                                    mCurrentRunName
+                                            + "_"
+                                            + getNameWithoutExtension(devicePath)
+                                            + "_runtime_coverage",
+                                    coverageFile);
+                        }
+                    } finally {
+                        FileUtil.deleteFile(coverageFile);
                     }
                 }
             } catch (DeviceNotAvailableException | IOException e) {
                 throw new RuntimeException(e);
             } finally {
-                FileUtil.deleteFile(coverageFile);
                 super.testRunEnded(elapsedTime, runMetrics);
             }
         }
     }
+
+    private void logCoverageMeasurement(String name, File coverageFile) throws IOException {
+        try (FileInputStreamSource source = new FileInputStreamSource(coverageFile, true)) {
+            testLog(name, LogDataType.COVERAGE, source);
+        }
+    }
 }
diff --git a/test_framework/com/android/tradefed/testtype/NativeCodeCoverageListener.java b/test_framework/com/android/tradefed/testtype/NativeCodeCoverageListener.java
index 6b045ae..37fed05 100644
--- a/test_framework/com/android/tradefed/testtype/NativeCodeCoverageListener.java
+++ b/test_framework/com/android/tradefed/testtype/NativeCodeCoverageListener.java
@@ -40,7 +40,6 @@
 import java.nio.file.Paths;
 import java.util.Arrays;
 import java.util.HashMap;
-import java.util.List;
 
 /**
  * A {@link ResultForwarder} that will pull native coverage measurements off of the device and log
@@ -53,7 +52,6 @@
             String.format("find %s -name '*.gcda'", NATIVE_COVERAGE_DEVICE_PATH);
 
     private final boolean mFlushCoverage;
-    private final List<String> mCoverageProcesses;
     private final ITestDevice mDevice;
     private final NativeCodeCoverageFlusher mFlusher;
 
@@ -63,8 +61,7 @@
         super(listeners);
         mDevice = device;
         mFlushCoverage = false;
-        mCoverageProcesses = ImmutableList.of();
-        mFlusher = new NativeCodeCoverageFlusher(mDevice);
+        mFlusher = new NativeCodeCoverageFlusher(mDevice, ImmutableList.of());
     }
 
     public NativeCodeCoverageListener(
@@ -74,8 +71,7 @@
         super(listeners);
         mDevice = device;
         mFlushCoverage = coverageOptions.isCoverageFlushEnabled();
-        mCoverageProcesses = coverageOptions.getCoverageProcesses();
-        mFlusher = new NativeCodeCoverageFlusher(mDevice);
+        mFlusher = new NativeCodeCoverageFlusher(mDevice, coverageOptions.getCoverageProcesses());
     }
 
     @Override
@@ -98,7 +94,7 @@
 
             // Flush cross-process coverage.
             if (mFlushCoverage) {
-                mFlusher.forceCoverageFlush(mCoverageProcesses);
+                mFlusher.forceCoverageFlush();
             }
 
             // List native coverage files on the device and pull them.
diff --git a/tests/src/com/android/tradefed/config/ConfigurationTest.java b/tests/src/com/android/tradefed/config/ConfigurationTest.java
index 747cf03..c5785c0 100644
--- a/tests/src/com/android/tradefed/config/ConfigurationTest.java
+++ b/tests/src/com/android/tradefed/config/ConfigurationTest.java
@@ -807,12 +807,12 @@
                         "<cmd_options class=\"com.android.tradefed.command.CommandOptions\" />"));
 
         OptionSetter setter = new OptionSetter(options1);
-        setter.setOptionValue("retry-strategy", "ITERATIONS");
+        setter.setOptionValue("test-tag", "tag-value");
         sw = new StringWriter();
         pw = new PrintWriter(sw);
         one.dumpXml(pw, new ArrayList<>(), true, false);
         String withOption = sw.toString();
-        assertTrue(withOption.contains("<option name=\"retry-strategy\" value=\"ITERATIONS\" />"));
+        assertTrue(withOption.contains("<option name=\"test-tag\" value=\"tag-value\" />"));
 
         CommandOptions options2 = new CommandOptions();
         one.setCommandOptions(options2);
diff --git a/tests/src/com/android/tradefed/device/contentprovider/ContentProviderHandlerTest.java b/tests/src/com/android/tradefed/device/contentprovider/ContentProviderHandlerTest.java
index 88b0d45..1ab0c71 100644
--- a/tests/src/com/android/tradefed/device/contentprovider/ContentProviderHandlerTest.java
+++ b/tests/src/com/android/tradefed/device/contentprovider/ContentProviderHandlerTest.java
@@ -239,6 +239,19 @@
         }
     }
 
+    @Test
+    public void testPullDir_failedDevice() throws Exception {
+        File pullTo = FileUtil.createTempDir("content-provider-test");
+
+        doReturn("Something crashed").when(mMockDevice).executeShellCommand(anyString());
+
+        try {
+            assertFalse(mProvider.pullDir("path/somewhere", pullTo));
+        } finally {
+            FileUtil.recursiveDelete(pullTo);
+        }
+    }
+
     /**
      * Test {@link ContentProviderHandler#pullDir(String, File)} to pull a directory that contains
      * one text file.
diff --git a/tests/src/com/android/tradefed/device/metric/PerfettoPullerMetricCollectorTest.java b/tests/src/com/android/tradefed/device/metric/PerfettoPullerMetricCollectorTest.java
index 98ca711..164039d 100644
--- a/tests/src/com/android/tradefed/device/metric/PerfettoPullerMetricCollectorTest.java
+++ b/tests/src/com/android/tradefed/device/metric/PerfettoPullerMetricCollectorTest.java
@@ -17,6 +17,7 @@
 package com.android.tradefed.device.metric;
 
 import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.times;
 
 import com.android.tradefed.config.OptionSetter;
 import com.android.tradefed.device.ITestDevice;
@@ -30,10 +31,7 @@
 import com.android.tradefed.util.CommandStatus;
 import com.android.tradefed.util.Pair;
 import com.android.tradefed.util.proto.TfMetricProtoUtil;
-import java.io.File;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
+
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
@@ -44,6 +42,11 @@
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
+import java.io.File;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+
 /** Unit tests for {@link PerfettoPullerMetricCollector}. */
 @RunWith(JUnit4.class)
 public class PerfettoPullerMetricCollectorTest {
@@ -55,7 +58,6 @@
     private ITestDevice mMockDevice;
     private IInvocationContext mContext;
 
-
     @Before
     public void setUp() {
 
@@ -100,12 +102,14 @@
         cr.setStatus(CommandStatus.SUCCESS);
         cr.setStdout("abc:efg");
 
-        Mockito.doReturn(cr).when(mPerfettoMetricCollector).runHostCommand(Mockito.any());
+        Mockito.doReturn(cr).when(mPerfettoMetricCollector).runHostCommand(Mockito.anyLong(),
+                Mockito.any(), Mockito.any(), Mockito.any());
 
         mPerfettoMetricCollector.testStarted(testDesc);
         mPerfettoMetricCollector.testEnded(testDesc, currentMetrics);
 
-        Mockito.verify(mPerfettoMetricCollector).runHostCommand(Mockito.any());
+        Mockito.verify(mPerfettoMetricCollector).runHostCommand(Mockito.anyLong(),
+                Mockito.any(), Mockito.any(), Mockito.any());
         Mockito.verify(mMockListener)
                 .testLog(Mockito.eq("trace"), Mockito.eq(LogDataType.PB), Mockito.any());
         assertTrue("Expected two metrics that includes success status",
@@ -117,6 +121,41 @@
     }
 
     @Test
+    public void testCompressedProcessingFlow() throws Exception {
+        OptionSetter setter = new OptionSetter(mPerfettoMetricCollector);
+        setter.setOptionValue("pull-pattern-keys", "perfettofile");
+        setter.setOptionValue("perfetto-binary-path", "trx");
+        setter.setOptionValue("compress-perfetto", "true");
+        HashMap<String, Metric> currentMetrics = new HashMap<>();
+        currentMetrics.put("perfettofile", TfMetricProtoUtil.stringToMetric("/data/trace.pb"));
+        Mockito.when(mMockDevice.pullFile(Mockito.eq("/data/trace.pb")))
+                .thenReturn(null);
+
+        TestDescription testDesc = new TestDescription("xyz", "abc");
+        CommandResult cr = new CommandResult();
+        cr.setStatus(CommandStatus.SUCCESS);
+        cr.setStdout("abc:efg");
+
+        Mockito.doReturn(cr).when(mPerfettoMetricCollector).runHostCommand(Mockito.anyLong(),
+                Mockito.any(), Mockito.any(), Mockito.any());
+
+        mPerfettoMetricCollector.testStarted(testDesc);
+        mPerfettoMetricCollector.testEnded(testDesc, currentMetrics);
+
+        Mockito.verify(mMockDevice, times(0)).pullFile(Mockito.eq("/data/trace.pb"));
+        Mockito.verify(mPerfettoMetricCollector, times(2)).runHostCommand(Mockito.anyLong(),
+                Mockito.any(), Mockito.any(), Mockito.any());
+        Mockito.verify(mMockListener).testLog(Mockito.contains("compressed"),
+                Mockito.eq(LogDataType.GZIP), Mockito.any());
+        assertTrue("Expected two metrics that includes success status",
+                currentMetrics.get("perfetto_trace_extractor_status").getMeasurements()
+                        .getSingleString().equals("1"));
+        assertTrue("Trace duration metrics not available but expected.",
+                currentMetrics.get("perfetto_trace_extractor_runtime").getMeasurements()
+                        .getSingleDouble() >= 0);
+    }
+
+    @Test
     public void testScriptFailureStatus() throws Exception {
 
         OptionSetter setter = new OptionSetter(mPerfettoMetricCollector);
@@ -132,12 +171,14 @@
         cr.setStatus(CommandStatus.FAILED);
         cr.setStdout("abc:efg");
 
-        Mockito.doReturn(cr).when(mPerfettoMetricCollector).runHostCommand(Mockito.any());
+        Mockito.doReturn(cr).when(mPerfettoMetricCollector).runHostCommand(Mockito.anyLong(),
+                Mockito.any(), Mockito.any(), Mockito.any());
 
         mPerfettoMetricCollector.testStarted(testDesc);
         mPerfettoMetricCollector.testEnded(testDesc, currentMetrics);
 
-        Mockito.verify(mPerfettoMetricCollector).runHostCommand(Mockito.any());
+        Mockito.verify(mPerfettoMetricCollector).runHostCommand(Mockito.anyLong(),
+                Mockito.any(), Mockito.any(), Mockito.any());
         Mockito.verify(mMockListener)
                 .testLog(Mockito.eq("trace"), Mockito.eq(LogDataType.PB), Mockito.any());
         assertTrue("Expected two metrics that includes failure status",
@@ -166,13 +207,15 @@
         cr.setStatus(CommandStatus.SUCCESS);
         cr.setStdout("abc:efg");
 
-        Mockito.doReturn(cr).when(mPerfettoMetricCollector).runHostCommand(Mockito.any());
+        Mockito.doReturn(cr).when(mPerfettoMetricCollector).runHostCommand(Mockito.anyLong(),
+                Mockito.any(), Mockito.any(), Mockito.any());
 
         mPerfettoMetricCollector.testStarted(testDesc);
         mPerfettoMetricCollector.testEnded(testDesc, currentMetrics);
 
         ArgumentCaptor<String[]> captor = ArgumentCaptor.forClass(String[].class);
-        Mockito.verify(mPerfettoMetricCollector).runHostCommand(captor.capture());
+        Mockito.verify(mPerfettoMetricCollector).runHostCommand(Mockito.anyLong(),
+                captor.capture(), Mockito.any(), Mockito.any());
         List<String> args = Arrays.asList(captor.getValue());
         Assert.assertTrue(args.contains("--uno"));
         Assert.assertTrue(args.contains("--dos"));
diff --git a/tests/src/com/android/tradefed/result/suite/XmlSuiteResultFormatterTest.java b/tests/src/com/android/tradefed/result/suite/XmlSuiteResultFormatterTest.java
index 8f61d29..e73813d 100644
--- a/tests/src/com/android/tradefed/result/suite/XmlSuiteResultFormatterTest.java
+++ b/tests/src/com/android/tradefed/result/suite/XmlSuiteResultFormatterTest.java
@@ -506,8 +506,13 @@
     @Test
     public void testRunHistoryReporting() throws Exception {
         final String RUN_HISTORY =
-                "[{\"startTime\":10000000000000,\"endTime\":10000000100000},"
-                        + "{\"startTime\":10000000200000,\"endTime\":10000000300000}]";
+                "[{\"startTime\":10000000000000,\"endTime\":10000000100000,\"passedTests\":10,"
+                        + "\"failedTests\":5,\"commandLineArgs\":\"cts\","
+                        + "\"hostName\":\"user.android.com\"},"
+                        + "{\"startTime\":10000000200000,\"endTime\":10000000300000,"
+                        + "\"passedTests\":3,\"failedTests\":2,"
+                        + "\"commandLineArgs\":\"cts\","
+                        + "\"hostName\":\"user.android.com\"}]";
         mResultHolder.context = mContext;
         mResultHolder.context.addInvocationAttribute("run_history", RUN_HISTORY);
 
@@ -532,8 +537,18 @@
         assertXmlContainsNode(content, "Result/RunHistory");
         assertXmlContainsAttribute(content, "Result/RunHistory/Run", "start", "10000000000000");
         assertXmlContainsAttribute(content, "Result/RunHistory/Run", "end", "10000000100000");
+        assertXmlContainsAttribute(content, "Result/RunHistory/Run", "pass", "10");
+        assertXmlContainsAttribute(content, "Result/RunHistory/Run", "failed", "5");
+        assertXmlContainsAttribute(content, "Result/RunHistory/Run", "command_line_args", "cts");
+        assertXmlContainsAttribute(
+                content, "Result/RunHistory/Run", "host_name", "user.android.com");
         assertXmlContainsAttribute(content, "Result/RunHistory/Run", "start", "10000000200000");
         assertXmlContainsAttribute(content, "Result/RunHistory/Run", "end", "10000000300000");
+        assertXmlContainsAttribute(content, "Result/RunHistory/Run", "pass", "3");
+        assertXmlContainsAttribute(content, "Result/RunHistory/Run", "failed", "2");
+        assertXmlContainsAttribute(content, "Result/RunHistory/Run", "command_line_args", "cts");
+        assertXmlContainsAttribute(
+                content, "Result/RunHistory/Run", "host_name", "user.android.com");
         // Test that we can read back the information.
         SuiteResultHolder holder = mFormatter.parseResults(mResultDir, false);
         assertEquals(RUN_HISTORY, holder.context.getAttributes().getUniqueMap().get("run_history"));
diff --git a/tests/src/com/android/tradefed/retry/BaseRetryDecisionTest.java b/tests/src/com/android/tradefed/retry/BaseRetryDecisionTest.java
index 617a56e..2c91df1 100644
--- a/tests/src/com/android/tradefed/retry/BaseRetryDecisionTest.java
+++ b/tests/src/com/android/tradefed/retry/BaseRetryDecisionTest.java
@@ -168,6 +168,22 @@
         assertFalse(res3);
     }
 
+    /** Ensure we abort the retry if there are too many failed test cases. */
+    @Test
+    public void testAbortTooManyFailures() throws Exception {
+        TestRunResult result = new TestRunResult();
+        result.testRunStarted("TEST", 60);
+        for (int i = 0; i < 60; i++) {
+            TestDescription test = new TestDescription("class", "method" + i);
+            result.testStarted(test);
+            result.testFailed(test, "failure" + i);
+            result.testEnded(test, new HashMap<String, Metric>());
+        }
+        result.testRunEnded(500, new HashMap<String, Metric>());
+        boolean res = mRetryDecision.shouldRetry(mTestClass, 0, Arrays.asList(result));
+        assertFalse(res);
+    }
+
     private TestRunResult createResult(boolean failure1, boolean failure2) {
         TestRunResult result = new TestRunResult();
         result.testRunStarted("TEST", 2);
diff --git a/tests/src/com/android/tradefed/targetprep/multi/MixImageZipPreparerTest.java b/tests/src/com/android/tradefed/targetprep/multi/MixImageZipPreparerTest.java
index de542dc..2fa5601 100644
--- a/tests/src/com/android/tradefed/targetprep/multi/MixImageZipPreparerTest.java
+++ b/tests/src/com/android/tradefed/targetprep/multi/MixImageZipPreparerTest.java
@@ -134,6 +134,7 @@
         mPreparer = new MixImageZipPreparer();
         mPreparer.addSystemFileName(SYSTEM_IMAGE_NAME);
         mPreparer.addDummyFileName(PRODUCT_IMAGE_NAME);
+        mPreparer.addDummyFileName("not_in_device_build.img");
     }
 
     private void setUpResource() throws IOException {
diff --git a/tests/src/com/android/tradefed/testtype/GTestTest.java b/tests/src/com/android/tradefed/testtype/GTestTest.java
index ebd1f4d..a47d6d7 100644
--- a/tests/src/com/android/tradefed/testtype/GTestTest.java
+++ b/tests/src/com/android/tradefed/testtype/GTestTest.java
@@ -466,7 +466,6 @@
                 .andReturn("");
         EasyMock.expect(mMockITestDevice.isAdbRoot()).andReturn(true);
         EasyMock.expect(mMockITestDevice.executeShellCommand("kill -37 -1")).andReturn("");
-        EasyMock.expect(mMockITestDevice.isAdbRoot()).andReturn(true);
         EasyMock.expect(mMockITestDevice.executeShellCommand("rm -rf /data/misc/trace/*"))
                 .andReturn("");
         EasyMock.expect(mMockITestDevice.doesFileExist(nativeTestPath)).andReturn(true);
@@ -529,7 +528,6 @@
         EasyMock.expect(mMockITestDevice.executeShellCommand("kill -37 1 1000")).andReturn("");
 
         // Clear the coverage data.
-        EasyMock.expect(mMockITestDevice.isAdbRoot()).andReturn(true);
         EasyMock.expect(mMockITestDevice.executeShellCommand("rm -rf /data/misc/trace/*"))
                 .andReturn("");
         EasyMock.expect(mMockITestDevice.doesFileExist(nativeTestPath)).andReturn(true);
diff --git a/tests/src/com/android/tradefed/testtype/JavaCodeCoverageListenerTest.java b/tests/src/com/android/tradefed/testtype/JavaCodeCoverageListenerTest.java
index 68f7a3b..58aef9b 100644
--- a/tests/src/com/android/tradefed/testtype/JavaCodeCoverageListenerTest.java
+++ b/tests/src/com/android/tradefed/testtype/JavaCodeCoverageListenerTest.java
@@ -28,15 +28,20 @@
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 
+import com.android.tradefed.config.ConfigurationException;
+import com.android.tradefed.config.OptionSetter;
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
 import com.android.tradefed.result.ITestInvocationListener;
 import com.android.tradefed.result.InputStreamSource;
 import com.android.tradefed.result.LogDataType;
+import com.android.tradefed.testtype.coverage.CoverageOptions;
+import com.android.tradefed.util.JavaCodeCoverageFlusher;
 import com.android.tradefed.util.proto.TfMetricProtoUtil;
 
 import com.google.common.base.VerifyException;
+import com.google.common.collect.ImmutableList;
 import com.google.protobuf.ByteString;
 
 import org.jacoco.core.tools.ExecFileLoader;
@@ -63,6 +68,7 @@
 import java.io.OutputStream;
 import java.util.Arrays;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
 /** Unit tests for {@link JavaCodeCoverageListener}. */
@@ -82,17 +88,25 @@
     @Rule public TemporaryFolder folder = new TemporaryFolder();
 
     @Mock ITestDevice mMockDevice;
+    @Mock JavaCodeCoverageFlusher mMockFlusher;
 
     @Spy LogFileReader mFakeListener = new LogFileReader();
 
     /** Object under test. */
     JavaCodeCoverageListener mCodeCoverageListener;
 
+    CoverageOptions mCoverageOptions = null;
+    OptionSetter mCoverageOptionsSetter = null;
+
     @Before
-    public void setUp() {
+    public void setUp() throws ConfigurationException {
         MockitoAnnotations.initMocks(this);
 
-        mCodeCoverageListener = new JavaCodeCoverageListener(mMockDevice, false, mFakeListener);
+        mCoverageOptions = new CoverageOptions();
+        mCoverageOptionsSetter = new OptionSetter(mCoverageOptions);
+
+        mCodeCoverageListener =
+                new JavaCodeCoverageListener(mMockDevice, mCoverageOptions, false, mFakeListener);
     }
 
     @Test
@@ -170,7 +184,8 @@
             measurement.writeTo(out);
         }
 
-        mCodeCoverageListener = new JavaCodeCoverageListener(mMockDevice, true, mFakeListener);
+        mCodeCoverageListener =
+                new JavaCodeCoverageListener(mMockDevice, mCoverageOptions, true, mFakeListener);
 
         Map<String, String> metric = new HashMap<>();
         metric.put("coverageFilePath", DEVICE_PATH);
@@ -207,6 +222,42 @@
                 .isEqualTo(partiallyCovered);
     }
 
+    @Test
+    public void testCoverageFlush_producesMultipleMeasurements() throws Exception {
+        List<String> coverageFileList =
+                ImmutableList.of(
+                        "/data/misc/trace/com.android.test1.ec",
+                        "/data/misc/trace/com.android.test2.ec",
+                        "/data/misc/trace/com.google.test3.ec");
+
+        mCoverageOptionsSetter.setOptionValue("coverage-flush", "true");
+
+        // Setup mocks.
+        File coverageFile = folder.newFile("coverage.ec");
+        try (OutputStream out = new FileOutputStream(coverageFile)) {
+            COVERAGE_MEASUREMENT.writeTo(out);
+        }
+        doReturn(coverageFile).when(mMockDevice).pullFile(DEVICE_PATH);
+
+        for (String additionalFile : coverageFileList) {
+            File coverage = folder.newFile();
+            try (OutputStream out = new FileOutputStream(coverage)) {
+                COVERAGE_MEASUREMENT.writeTo(out);
+            }
+            doReturn(coverage).when(mMockDevice).pullFile(additionalFile);
+        }
+
+        doReturn(coverageFileList).when(mMockFlusher).forceCoverageFlush();
+
+        mCodeCoverageListener.setCoverageFlusher(mMockFlusher);
+
+        // Simulate a test run.
+        mCodeCoverageListener.testRunStarted(RUN_NAME, TEST_COUNT);
+        Map<String, String> metric = new HashMap<>();
+        metric.put("coverageFilePath", DEVICE_PATH);
+        mCodeCoverageListener.testRunEnded(ELAPSED_TIME, TfMetricProtoUtil.upgradeConvert(metric));
+    }
+
     private static <T> String vmName(Class<T> clazz) {
         return clazz.getName().replace('.', '/');
     }
diff --git a/tests/src/com/android/tradefed/testtype/suite/ITestSuiteTest.java b/tests/src/com/android/tradefed/testtype/suite/ITestSuiteTest.java
index 13df991..05326a5 100644
--- a/tests/src/com/android/tradefed/testtype/suite/ITestSuiteTest.java
+++ b/tests/src/com/android/tradefed/testtype/suite/ITestSuiteTest.java
@@ -1804,4 +1804,57 @@
         mTestSuite.run(mMockListener);
         EasyMock.verify(mockBuildInfo);
     }
+
+    /** Test for {@link ITestSuite#reportNotExecuted(ITestInvocationListener, String)}. */
+    @Test
+    public void testReportNotExecuted() {
+        mMockListener.testModuleStarted(EasyMock.anyObject());
+        mMockListener.testRunStarted(
+                EasyMock.eq(TEST_CONFIG_NAME), EasyMock.eq(0), EasyMock.eq(0), EasyMock.anyLong());
+        mMockListener.testRunFailed("Injected message");
+        mMockListener.testRunEnded(
+                EasyMock.anyLong(), EasyMock.<HashMap<String, Metric>>anyObject());
+        mMockListener.testModuleEnded();
+
+        EasyMock.replay(mMockListener);
+        mTestSuite.reportNotExecuted(mMockListener, "Injected message");
+        EasyMock.verify(mMockListener);
+    }
+
+    /**
+     * Test for {@link ITestSuite#reportNotExecuted(ITestInvocationListener, String)} with a module
+     * in progress.
+     */
+    @Test
+    public void testReportNotExecuted_moduleInProgress() {
+        ModuleDefinition m =
+                new ModuleDefinition(
+                        "in-progress",
+                        new ArrayList<>(),
+                        new HashMap<>(),
+                        new ArrayList<>(),
+                        new Configuration("", ""));
+        mTestSuite.setModuleInProgress(m);
+        mMockListener.testModuleStarted(EasyMock.anyObject());
+        mMockListener.testRunStarted(
+                EasyMock.eq("in-progress"), EasyMock.eq(0), EasyMock.eq(0), EasyMock.anyLong());
+        mMockListener.testRunFailed(
+                "Module in-progress was interrupted after starting. Results might not be "
+                        + "accurate or complete.");
+        mMockListener.testRunEnded(
+                EasyMock.anyLong(), EasyMock.<HashMap<String, Metric>>anyObject());
+        mMockListener.testModuleEnded();
+        // The non-executed module gets reported too.
+        mMockListener.testModuleStarted(EasyMock.anyObject());
+        mMockListener.testRunStarted(
+                EasyMock.eq(TEST_CONFIG_NAME), EasyMock.eq(0), EasyMock.eq(0), EasyMock.anyLong());
+        mMockListener.testRunFailed("Injected message");
+        mMockListener.testRunEnded(
+                EasyMock.anyLong(), EasyMock.<HashMap<String, Metric>>anyObject());
+        mMockListener.testModuleEnded();
+
+        EasyMock.replay(mMockListener);
+        mTestSuite.reportNotExecuted(mMockListener, "Injected message");
+        EasyMock.verify(mMockListener);
+    }
 }
diff --git a/tests/src/com/android/tradefed/testtype/suite/SuiteModuleLoaderTest.java b/tests/src/com/android/tradefed/testtype/suite/SuiteModuleLoaderTest.java
index b5bbd81..e2a23ae 100644
--- a/tests/src/com/android/tradefed/testtype/suite/SuiteModuleLoaderTest.java
+++ b/tests/src/com/android/tradefed/testtype/suite/SuiteModuleLoaderTest.java
@@ -17,6 +17,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
 import com.android.tradefed.config.IConfiguration;
@@ -287,4 +288,83 @@
         assertEquals(
                 "NativeDnsAsyncTest#Async_Cancel", stubTest.getExcludeFilters().iterator().next());
     }
+
+    /**
+     * Test that the configuration can be found if specifying specific path.
+     */
+    @Test
+    public void testLoadConfigsFromSpecifiedPaths_OneModule() throws Exception {
+        createModuleConfig("module1");
+        File module1 = new File(mTestsDir, "module1" + SuiteModuleLoader.CONFIG_EXT);
+
+        mRepo =
+                new SuiteModuleLoader(
+                        new LinkedHashMap<String, List<SuiteTestFilter>>(),
+                        new LinkedHashMap<String, List<SuiteTestFilter>>(),
+                        new ArrayList<>(),
+                        new ArrayList<>());
+
+        LinkedHashMap<String, IConfiguration> res =
+            mRepo.loadConfigsFromSpecifiedPaths(
+                Arrays.asList(module1), mAbis, null);
+        assertEquals(1, res.size());
+        assertNotNull(res.get("armeabi-v7a module1"));
+    }
+
+    /**
+     * Test that multiple configurations can be found if specifying specific paths.
+     */
+    @Test
+    public void testLoadConfigsFromSpecifiedPaths_MultipleModules() throws Exception {
+        createModuleConfig("module1");
+        File module1 = new File(mTestsDir, "module1" + SuiteModuleLoader.CONFIG_EXT);
+        createModuleConfig("module2");
+        File module2 = new File(mTestsDir, "module2" + SuiteModuleLoader.CONFIG_EXT);
+
+        mRepo =
+                new SuiteModuleLoader(
+                        new LinkedHashMap<String, List<SuiteTestFilter>>(),
+                        new LinkedHashMap<String, List<SuiteTestFilter>>(),
+                        new ArrayList<>(),
+                        new ArrayList<>());
+
+        LinkedHashMap<String, IConfiguration> res =
+            mRepo.loadConfigsFromSpecifiedPaths(
+                Arrays.asList(module1, module2), mAbis, null);
+        assertEquals(2, res.size());
+        assertNotNull(res.get("armeabi-v7a module1"));
+        assertNotNull(res.get("armeabi-v7a module2"));
+    }
+
+    /**
+     * Test that configuration can be found correctly if specifying specific paths but someone is
+     * excluded.
+     */
+    @Test
+    public void testLoadConfigsFromSpecifiedPaths_WithExcludeFilter() throws Exception {
+        createModuleConfig("module1");
+        File module1 = new File(mTestsDir, "module1" + SuiteModuleLoader.CONFIG_EXT);
+        createModuleConfig("module2");
+        File module2 = new File(mTestsDir, "module2" + SuiteModuleLoader.CONFIG_EXT);
+
+        Map<String, List<SuiteTestFilter>> excludeFilters = new LinkedHashMap<>();
+        SuiteTestFilter filter =
+            SuiteTestFilter.createFrom(
+                "armeabi-v7a module2");
+        excludeFilters.put("armeabi-v7a module2", Arrays.asList(filter));
+
+        mRepo =
+            new SuiteModuleLoader(
+                new LinkedHashMap<String, List<SuiteTestFilter>>(),
+                excludeFilters,
+                new ArrayList<>(),
+                new ArrayList<>());
+
+        LinkedHashMap<String, IConfiguration> res =
+            mRepo.loadConfigsFromSpecifiedPaths(
+                Arrays.asList(module1, module2), mAbis, null);
+        assertEquals(1, res.size());
+        assertNotNull(res.get("armeabi-v7a module1"));
+        assertNull(res.get("armeabi-v7a module2"));
+    }
 }
diff --git a/tests/src/com/android/tradefed/testtype/suite/TestMappingSuiteRunnerTest.java b/tests/src/com/android/tradefed/testtype/suite/TestMappingSuiteRunnerTest.java
index b2028d8..48b6600 100644
--- a/tests/src/com/android/tradefed/testtype/suite/TestMappingSuiteRunnerTest.java
+++ b/tests/src/com/android/tradefed/testtype/suite/TestMappingSuiteRunnerTest.java
@@ -20,6 +20,8 @@
 
 import com.android.tradefed.build.BuildInfoKey.BuildInfoFileKey;
 import com.android.tradefed.build.IDeviceBuildInfo;
+import com.android.tradefed.config.ConfigurationException;
+import com.android.tradefed.config.ConfigurationFactory;
 import com.android.tradefed.config.IConfiguration;
 import com.android.tradefed.config.OptionSetter;
 import com.android.tradefed.device.DeviceNotAvailableException;
@@ -31,8 +33,11 @@
 import com.android.tradefed.util.AbiUtils;
 import com.android.tradefed.util.FileUtil;
 import com.android.tradefed.util.ZipUtil;
+import com.android.tradefed.util.testmapping.TestInfo;
 import com.android.tradefed.util.testmapping.TestMapping;
 
+import com.android.tradefed.util.testmapping.TestOption;
+import java.util.ArrayList;
 import org.easymock.EasyMock;
 import org.junit.Before;
 import org.junit.Test;
@@ -57,14 +62,17 @@
 
     private static final String ABI_1 = "arm64-v8a";
     private static final String ABI_2 = "armeabi-v7a";
+    private static final String DISABLED_PRESUBMIT_TESTS = "disabled-presubmit-tests";
+    private static final String EMPTY_CONFIG = "empty";
     private static final String NON_EXISTING_DIR = "non-existing-dir";
+    private static final String TEST_CONFIG_NAME = "test";
     private static final String TEST_DATA_DIR = "testdata";
     private static final String TEST_MAPPING = "TEST_MAPPING";
     private static final String TEST_MAPPINGS_ZIP = "test_mappings.zip";
-    private static final String DISABLED_PRESUBMIT_TESTS = "disabled-presubmit-tests";
 
     private TestMappingSuiteRunner mRunner;
     private OptionSetter mOptionSetter;
+    private TestMappingSuiteRunner mRunner2;
     private IDeviceBuildInfo mBuildInfo;
     private ITestDevice mMockDevice;
 
@@ -79,8 +87,13 @@
         mOptionSetter = new OptionSetter(mRunner);
         mOptionSetter.setOptionValue("suite-config-prefix", "suite");
 
-        EasyMock.expect(mBuildInfo.getFile(BuildInfoFileKey.TARGET_LINKED_DIR)).andReturn(null);
-        EasyMock.expect(mBuildInfo.getTestsDir()).andReturn(new File(NON_EXISTING_DIR));
+        mRunner2 = new FakeTestMappingSuiteRunner();
+        mRunner2.setBuild(mBuildInfo);
+        mRunner2.setDevice(mMockDevice);
+
+        EasyMock.expect(mBuildInfo.getFile(BuildInfoFileKey.TARGET_LINKED_DIR)).andReturn(null)
+                .anyTimes();
+        EasyMock.expect(mBuildInfo.getTestsDir()).andReturn(new File(NON_EXISTING_DIR)).anyTimes();
         EasyMock.expect(mMockDevice.getProperty(EasyMock.anyObject())).andReturn(ABI_1);
         EasyMock.expect(mMockDevice.getProperty(EasyMock.anyObject())).andReturn(ABI_2);
         EasyMock.replay(mBuildInfo, mMockDevice);
@@ -98,6 +111,43 @@
             abis.add(new Abi(ABI_2, AbiUtils.getBitness(ABI_2)));
             return abis;
         }
+
+        @Override
+        List<IRemoteTest> createIndividualTests(Set<TestInfo> testInfos, String configPath) {
+            IRemoteTest fakeTest = EasyMock.createMock(IRemoteTest.class);
+            return new ArrayList<>(Arrays.asList(fakeTest));
+        }
+    }
+
+    /**
+     * Test TestMappingSuiteRunner that create a fake IConfiguration with fake a test object.
+     */
+    public static class FakeTestMappingSuiteRunner extends TestMappingSuiteRunner {
+        @Override
+        public Set<IAbi> getAbis(ITestDevice device) throws DeviceNotAvailableException {
+            Set<IAbi> abis = new HashSet<>();
+            abis.add(new Abi(ABI_1, AbiUtils.getBitness(ABI_1)));
+            abis.add(new Abi(ABI_2, AbiUtils.getBitness(ABI_2)));
+            return abis;
+        }
+
+        @Override
+        public LinkedHashMap<String, IConfiguration> loadingStrategy(Set<IAbi> abis,
+            List<File> testsDirs, String suitePrefix, String suiteTag) {
+            LinkedHashMap<String, IConfiguration> testConfig = new LinkedHashMap<>();
+            try {
+                IConfiguration config =
+                        ConfigurationFactory.getInstance()
+                                .createConfigurationFromArgs(new String[] {EMPTY_CONFIG});
+                config.setTest(new StubTest());
+                config.getConfigurationDescription().setModuleName(TEST_CONFIG_NAME);
+                testConfig.put(TEST_CONFIG_NAME, config);
+
+            } catch (ConfigurationException e) {
+                throw new RuntimeException(e);
+            }
+            return testConfig;
+        }
     }
 
     /**
@@ -174,13 +224,7 @@
             assertTrue(mRunner.getIncludeFilter().contains("test2"));
             assertTrue(mRunner.getIncludeFilter().contains("instrument"));
             assertTrue(mRunner.getIncludeFilter().contains("suite/stub1"));
-            // Filters are applied directly
-            assertTrue(mRunner.getExcludeFilter().contains("suite/stub1 filter.com"));
-            assertTrue(mRunner.getIncludeFilter().contains("suite/stub2 filter.com"));
-
-            // Check module-arg work as expected.
-            StubTest test = (StubTest) configMap.get("arm64-v8a suite/stub2").getTests().get(0);
-            assertTrue(test.getRunTest());
+            assertTrue(mRunner.getIncludeFilter().contains("suite/stub2"));
 
             assertEquals(4, configMap.size());
             assertTrue(configMap.containsKey(ABI_1 + " suite/stub1"));
@@ -575,4 +619,141 @@
             FileUtil.recursiveDelete(tempDir);
         }
     }
+
+    /**
+     * Test for {@link TestMappingSuiteRunner#getTestInfos(Set, String)} that when a module is
+     * specified, tests would be still found correctly.
+     */
+    @Test
+    public void testGetTestInfos() throws Exception {
+        Set<TestInfo> testInfos = new HashSet<>();
+        testInfos.add(createTestInfo("test", "path"));
+        testInfos.add(createTestInfo("test", "path2"));
+        testInfos.add(createTestInfo("test2", "path2"));
+
+        assertEquals(2, mRunner.getTestInfos(testInfos, "test").size());
+        assertEquals(1, mRunner.getTestInfos(testInfos, "test2").size());
+    }
+
+    /**
+     * Test for {@link TestMappingSuiteRunner#dedupTestInfos(Set)} that tests with the same test
+     * options would be filtered out.
+     */
+    @Test
+    public void testDedupTestInfos() throws Exception {
+        Set<TestInfo> testInfos = new HashSet<>();
+        testInfos.add(createTestInfo("test", "path"));
+        testInfos.add(createTestInfo("test", "path2"));
+        assertEquals(1, mRunner.dedupTestInfos(testInfos).size());
+
+        TestInfo anotherInfo = new TestInfo("test", "folder3", false);
+        anotherInfo.addOption(new TestOption("include-filter", "value1"));
+        testInfos.add(anotherInfo);
+        assertEquals(2, mRunner.dedupTestInfos(testInfos).size());
+    }
+
+    /**
+     * Test for {@link TestMappingSuiteRunner#getTestSources(Set)} that test sources would be found
+     * correctly.
+     */
+    @Test
+    public void testGetTestSources() throws Exception {
+        Set<TestInfo> testInfos = new HashSet<>();
+        testInfos.add(createTestInfo("test", "path"));
+        testInfos.add(createTestInfo("test", "path2"));
+        List<String> results = mRunner.getTestSources(testInfos);
+        assertEquals(2, results.size());
+    }
+
+    /**
+     * Test for {@link TestMappingSuiteRunner#parseOptions(TestInfo)} that the test options are
+     * injected correctly.
+     */
+    @Test
+    public void testParseOptions() throws Exception {
+        TestInfo info = createTestInfo("test", "path");
+        mRunner.parseOptions(info);
+        assertEquals(1, mRunner.getIncludeFilter().size());
+        assertEquals(1, mRunner.getExcludeFilter().size());
+    }
+
+    /**
+     * Test for {@link TestMappingSuiteRunner#createIndividualTests(Set, String)} that IRemoteTest
+     * object are created according to the test infos with different test options.
+     */
+    @Test
+    public void testCreateIndividualTestsWithDifferentTestInfos() throws Exception {
+        File tempDir = null;
+        try {
+            tempDir = FileUtil.createTempDir("tmp");
+            File moduleConfig = new File(tempDir, "module_name.config");
+            moduleConfig.createNewFile();
+            Set<TestInfo> testInfos = new HashSet<>();
+            testInfos.add(createTestInfo("test", "path"));
+            testInfos.add(createTestInfo("test2", "path"));
+            String configPath = moduleConfig.getAbsolutePath();
+            assertEquals(2, mRunner2.createIndividualTests(testInfos, configPath).size());
+            assertEquals(1, mRunner2.getIncludeFilter().size());
+            assertEquals(1, mRunner2.getExcludeFilter().size());
+        } finally {
+            FileUtil.recursiveDelete(tempDir);
+        }
+    }
+
+    /**
+     * Test for {@link TestMappingSuiteRunner#createIndividualTests(Set, String)} that IRemoteTest
+     * object are created according to the test infos with multiple test options.
+     */
+    @Test
+    public void testCreateIndividualTestsWithDifferentTestOptions() throws Exception {
+        File tempDir = null;
+        try {
+            tempDir = FileUtil.createTempDir("tmp");
+            File moduleConfig = new File(tempDir, "module_name.config");
+            moduleConfig.createNewFile();
+            Set<TestInfo> testInfos = new HashSet<>();
+            testInfos.add(createTestInfo("test", "path"));
+            TestInfo info = new TestInfo("test", "path", false);
+            info.addOption(new TestOption("include-filter", "include-filter"));
+            testInfos.add(info);
+            String configPath = moduleConfig.getAbsolutePath();
+            assertEquals(2, mRunner2.createIndividualTests(testInfos, configPath).size());
+            assertEquals(1, mRunner2.getIncludeFilter().size());
+            assertEquals(0, mRunner2.getExcludeFilter().size());
+        } finally {
+            FileUtil.recursiveDelete(tempDir);
+        }
+    }
+
+    /**
+     * Test for {@link TestMappingSuiteRunner#createIndividualTests(Set, String)} that IRemoteTest
+     * object are created according to the test infos with the same test options and name.
+     */
+    @Test
+    public void testCreateIndividualTestsWithSameTestInfos() throws Exception {
+        File tempDir = null;
+        try {
+            tempDir = FileUtil.createTempDir("tmp");
+            File moduleConfig = new File(tempDir, "module_name.config");
+            moduleConfig.createNewFile();
+            String configPath = moduleConfig.getAbsolutePath();
+            Set<TestInfo> testInfos = new HashSet<>();
+            testInfos.add(createTestInfo("test", "path"));
+            testInfos.add(createTestInfo("test", "path"));
+            assertEquals(1, mRunner2.createIndividualTests(testInfos, configPath).size());
+            assertEquals(1, mRunner2.getIncludeFilter().size());
+            assertEquals(1, mRunner2.getExcludeFilter().size());
+        } finally {
+            FileUtil.recursiveDelete(tempDir);
+        }
+    }
+
+    /** Helper to create specific test infos. */
+    private TestInfo createTestInfo(String name, String source) {
+        TestInfo info = new TestInfo(name, source, false);
+        info.addOption(new TestOption("include-filter", name));
+        info.addOption(new TestOption("exclude-filter", name));
+        info.addOption(new TestOption("other", name));
+        return info;
+    }
 }
diff --git a/tests/src/com/android/tradefed/util/FileUtilTest.java b/tests/src/com/android/tradefed/util/FileUtilTest.java
index e0f8ff6..687e783 100644
--- a/tests/src/com/android/tradefed/util/FileUtilTest.java
+++ b/tests/src/com/android/tradefed/util/FileUtilTest.java
@@ -535,4 +535,28 @@
             FileUtil.recursiveDelete(tmpDir);
         }
     }
+
+    @Test
+    public void testReadPartialStringFromFile() throws IOException {
+        File tmpFile = FileUtil.createTempFile("test", ".txt");
+        try {
+            StringBuilder content = new StringBuilder();
+            StringBuilder partialContent = new StringBuilder();
+            for (int i = 0; i < 1024; i++) {
+                content.append('A');
+            }
+            for (int i = 0; i < 1024; i++) {
+                content.append('C');
+                partialContent.append('C');
+            }
+            FileUtil.writeToFile(content.toString(), tmpFile);
+            // Test to read the whole file with `length` greater than the file length.
+            assertEquals(content.toString(), FileUtil.readStringFromFile(tmpFile, -1024, 4096 * 2));
+            // Test to read the tailing part of the file.
+            assertEquals(
+                    partialContent.toString(), FileUtil.readStringFromFile(tmpFile, 1024, 4096));
+        } finally {
+            FileUtil.deleteFile(tmpFile);
+        }
+    }
 }
diff --git a/tests/src/com/android/tradefed/util/NativeCodeCoverageFlusherTest.java b/tests/src/com/android/tradefed/util/NativeCodeCoverageFlusherTest.java
index 523655c..d5f627f 100644
--- a/tests/src/com/android/tradefed/util/NativeCodeCoverageFlusherTest.java
+++ b/tests/src/com/android/tradefed/util/NativeCodeCoverageFlusherTest.java
@@ -34,7 +34,6 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
-import java.lang.IllegalStateException;
 import java.util.List;
 
 @RunWith(JUnit4.class)
@@ -48,15 +47,14 @@
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
-
-        mFlusher = new NativeCodeCoverageFlusher(mMockDevice);
     }
 
     @Test
     public void testClearCoverageMeasurements_rmCommandCalled() throws DeviceNotAvailableException {
         doReturn(true).when(mMockDevice).isAdbRoot();
 
-        mFlusher.clearCoverageMeasurements();
+        mFlusher = new NativeCodeCoverageFlusher(mMockDevice, ImmutableList.of());
+        mFlusher.resetCoverage();
 
         // Verify that the rm command was executed.
         verify(mMockDevice).executeShellCommand("rm -rf /data/misc/trace/*");
@@ -67,7 +65,8 @@
         doReturn(false).when(mMockDevice).isAdbRoot();
 
         try {
-            mFlusher.clearCoverageMeasurements();
+            mFlusher = new NativeCodeCoverageFlusher(mMockDevice, ImmutableList.of());
+            mFlusher.resetCoverage();
             fail("Should have thrown an exception");
         } catch (IllegalStateException e) {
             // Expected
@@ -82,7 +81,8 @@
             throws DeviceNotAvailableException {
         doReturn(true).when(mMockDevice).isAdbRoot();
 
-        mFlusher.forceCoverageFlush(ImmutableList.of());
+        mFlusher = new NativeCodeCoverageFlusher(mMockDevice, ImmutableList.of());
+        mFlusher.forceCoverageFlush();
 
         // Verify that the flush command for all processes was called.
         verify(mMockDevice).executeShellCommand("kill -37 -1");
@@ -97,7 +97,8 @@
         doReturn("12").when(mMockDevice).getProcessPid(processes.get(0));
         doReturn("789").when(mMockDevice).getProcessPid(processes.get(1));
 
-        mFlusher.forceCoverageFlush(processes);
+        mFlusher = new NativeCodeCoverageFlusher(mMockDevice, processes);
+        mFlusher.forceCoverageFlush();
 
         // Verify that the flush command for the specific processes was called.
         verify(mMockDevice).executeShellCommand("kill -37 12 789");
@@ -108,7 +109,8 @@
         doReturn(false).when(mMockDevice).isAdbRoot();
 
         try {
-            mFlusher.forceCoverageFlush(ImmutableList.of("mediaserver"));
+            mFlusher = new NativeCodeCoverageFlusher(mMockDevice, ImmutableList.of("mediaserver"));
+            mFlusher.forceCoverageFlush();
             fail("Should have thrown an exception");
         } catch (IllegalStateException e) {
             // Expected
diff --git a/tests/src/com/android/tradefed/util/StreamUtilTest.java b/tests/src/com/android/tradefed/util/StreamUtilTest.java
index f2e774c..312c45d 100644
--- a/tests/src/com/android/tradefed/util/StreamUtilTest.java
+++ b/tests/src/com/android/tradefed/util/StreamUtilTest.java
@@ -110,8 +110,8 @@
     }
 
     /**
-     * Verify that {@link com.android.tradefed.util.StreamUtil#getStringFromStream} works as
-     * expected.
+     * Verify that {@link com.android.tradefed.util.StreamUtil#getStringFromStream(InputStream)}
+     * works as expected.
      */
     public void testGetStringFromStream() throws Exception {
         final String contents = "this is a string";
@@ -121,6 +121,17 @@
     }
 
     /**
+     * Verify that {@link com.android.tradefed.util.StreamUtil#getStringFromStream(InputStream,
+     * long)} works as expected.
+     */
+    public void testGetStringFromStream_withLength() throws Exception {
+        final String contents = "this is a string";
+        final String output =
+                StreamUtil.getStringFromStream(new ByteArrayInputStream(contents.getBytes()), 5);
+        assertEquals("this ", output);
+    }
+
+    /**
      * Verify that {@link com.android.tradefed.util.StreamUtil#calculateCrc32(InputStream)} works as
      * expected.
      *
diff --git a/tests/src/com/android/tradefed/util/testmapping/TestMappingTest.java b/tests/src/com/android/tradefed/util/testmapping/TestMappingTest.java
index 8c53c17..0d69264 100644
--- a/tests/src/com/android/tradefed/util/testmapping/TestMappingTest.java
+++ b/tests/src/com/android/tradefed/util/testmapping/TestMappingTest.java
@@ -169,12 +169,10 @@
             assertEquals(0, tests.size());
 
             tests = TestMapping.getTests(mockBuildInfo, "presubmit", true, null);
-            assertEquals(1, tests.size());
+            assertEquals(2, tests.size());
             Set<String> names = new HashSet<String>();
             for (TestInfo test : tests) {
                 names.add(test.getName());
-                // Make sure the tests for `test1` are merged and no option is kept.
-                assertTrue(test.getOptions().isEmpty());
                 if (test.getName().equals("test1")) {
                     assertTrue(test.getHostOnly());
                 } else {
diff --git a/tests/test-apps/Android.mk b/tests/test-apps/Android.mk
deleted file mode 100644
index e25764f..0000000
--- a/tests/test-apps/Android.mk
+++ /dev/null
@@ -1,21 +0,0 @@
-# Copyright (C) 2010 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-
-# Build the test APKs using their own makefiles
-include $(call all-makefiles-under,$(LOCAL_PATH))
-
diff --git a/tests/test-apps/NativeTestSampleApp/Android.bp b/tests/test-apps/NativeTestSampleApp/Android.bp
new file mode 100644
index 0000000..12fa9c6
--- /dev/null
+++ b/tests/test-apps/NativeTestSampleApp/Android.bp
@@ -0,0 +1,22 @@
+// Copyright (C) 2010 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.
+
+// Makefile to build sample static library that has native tests.
+
+cc_library_static {
+    name: "tfnativetestsamplelib",
+    // All source files for the library
+    srcs: ["src/TradeFedNativeTestSampleLib.cpp"],
+    local_include_dirs: ["include"],
+}
diff --git a/tests/test-apps/NativeTestSampleApp/Android.mk b/tests/test-apps/NativeTestSampleApp/Android.mk
deleted file mode 100644
index f1ef111..0000000
--- a/tests/test-apps/NativeTestSampleApp/Android.mk
+++ /dev/null
@@ -1,19 +0,0 @@
-# Copyright (C) 2010 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH := $(call my-dir)
-include $(CLEAR_VARS)
-
-include $(call all-makefiles-under,$(LOCAL_PATH))
-
diff --git a/tests/test-apps/NativeTestSampleApp/src/Android.mk b/tests/test-apps/NativeTestSampleApp/src/Android.mk
deleted file mode 100644
index 6100eb5..0000000
--- a/tests/test-apps/NativeTestSampleApp/src/Android.mk
+++ /dev/null
@@ -1,32 +0,0 @@
-# Copyright (C) 2010 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.
-
-# Makefile to build sample static library that has native tests.
-
-LOCAL_PATH := $(call my-dir)
-include $(CLEAR_VARS)
-
-#All source files for the library
-LOCAL_SRC_FILES := TradeFedNativeTestSampleLib.cpp
-
-LOCAL_CFLAGS := -Wall -Werror
-
-LOCAL_C_INCLUDES := \
-	$(LOCAL_PATH)/../include
-
-LOCAL_MODULE_TAGS := tests
-
-LOCAL_MODULE := tfnativetestsamplelib
-
-include $(BUILD_STATIC_LIBRARY)
diff --git a/tests/test-apps/TradeFedNativeTestApp/Android.bp b/tests/test-apps/TradeFedNativeTestApp/Android.bp
new file mode 100644
index 0000000..badb9b9
--- /dev/null
+++ b/tests/test-apps/TradeFedNativeTestApp/Android.bp
@@ -0,0 +1,21 @@
+// Copyright (C) 2010 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.
+
+// Makefile to build device-based native tests.
+
+cc_test {
+    name: "tfnativetests",
+    // All source files will be bundled into one test module
+    srcs: ["TradeFedNativeTestApp_test.cpp"],
+}
diff --git a/tests/test-apps/TradeFedNativeTestApp/Android.mk b/tests/test-apps/TradeFedNativeTestApp/Android.mk
deleted file mode 100644
index 551b9e2..0000000
--- a/tests/test-apps/TradeFedNativeTestApp/Android.mk
+++ /dev/null
@@ -1,37 +0,0 @@
-# Copyright (C) 2010 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.
-
-# Makefile to build device-based native tests.
-
-# GTest does not build on the simulator because it depends on STLport.
-ifneq ($(TARGET_SIMULATOR),true)
-
-LOCAL_PATH := $(call my-dir)
-include $(CLEAR_VARS)
-
-# All source files will be bundled into one test module
-LOCAL_SRC_FILES := TradeFedNativeTestApp_test.cpp
-
-LOCAL_CFLAGS := -Wall -Werror
-
-# All gtests in all files should be compiled into one binary
-# The standard naming should conform to: <module_being_tested>tests
-# For example, for libjingle, use libjingletests
-LOCAL_MODULE := tfnativetests
-
-LOCAL_MODULE_TAGS := optional
-
-include $(BUILD_NATIVE_TEST)
-
-endif
diff --git a/tests/test-apps/TradeFedTestApp/Android.bp b/tests/test-apps/TradeFedTestApp/Android.bp
new file mode 100644
index 0000000..1611186
--- /dev/null
+++ b/tests/test-apps/TradeFedTestApp/Android.bp
@@ -0,0 +1,19 @@
+// Copyright (C) 2010 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.
+
+android_test {
+    name: "TradeFedTestApp",
+    srcs: ["src/**/*.java"],
+    sdk_version: "4",
+}
diff --git a/tests/test-apps/TradeFedTestApp/Android.mk b/tests/test-apps/TradeFedTestApp/Android.mk
deleted file mode 100644
index 657e2b0..0000000
--- a/tests/test-apps/TradeFedTestApp/Android.mk
+++ /dev/null
@@ -1,27 +0,0 @@
-# Copyright (C) 2010 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH:= $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := tests
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_SDK_VERSION := 4
-
-LOCAL_PACKAGE_NAME := TradeFedTestApp
-
-include $(BUILD_PACKAGE)
diff --git a/tests/test-apps/UITestApp/Android.bp b/tests/test-apps/UITestApp/Android.bp
new file mode 100644
index 0000000..d359c36
--- /dev/null
+++ b/tests/test-apps/UITestApp/Android.bp
@@ -0,0 +1,19 @@
+// Copyright (C) 2010 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.
+
+android_test {
+    name: "TradeFedUiTestApp",
+    srcs: ["src/**/*.java"],
+    sdk_version: "8",
+}
diff --git a/tests/test-apps/UITestApp/Android.mk b/tests/test-apps/UITestApp/Android.mk
deleted file mode 100644
index 137c129..0000000
--- a/tests/test-apps/UITestApp/Android.mk
+++ /dev/null
@@ -1,27 +0,0 @@
-# Copyright (C) 2010 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH:= $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := tests
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_SDK_VERSION := 8
-
-LOCAL_PACKAGE_NAME := TradeFedUiTestApp
-
-include $(BUILD_PACKAGE)
diff --git a/util-apps/Android.mk b/util-apps/Android.mk
deleted file mode 100644
index a799cb0..0000000
--- a/util-apps/Android.mk
+++ /dev/null
@@ -1,21 +0,0 @@
-# Copyright (C) 2012 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-
-# Build the test APKs using their own makefiles
-include $(call all-makefiles-under,$(LOCAL_PATH))
-
diff --git a/util-apps/DeviceSetupUtil/Android.bp b/util-apps/DeviceSetupUtil/Android.bp
new file mode 100644
index 0000000..05884de
--- /dev/null
+++ b/util-apps/DeviceSetupUtil/Android.bp
@@ -0,0 +1,19 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+android_test_helper_app {
+    name: "DeviceSetupUtil",
+    srcs: ["src/**/*.java"],
+    sdk_version: "8",
+}
diff --git a/util-apps/DeviceSetupUtil/Android.mk b/util-apps/DeviceSetupUtil/Android.mk
deleted file mode 100644
index 79b6a4c..0000000
--- a/util-apps/DeviceSetupUtil/Android.mk
+++ /dev/null
@@ -1,26 +0,0 @@
-# Copyright (C) 2012 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := optional
-LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-LOCAL_SDK_VERSION := 8
-LOCAL_PACKAGE_NAME := DeviceSetupUtil
-
-include $(BUILD_PACKAGE)
-
diff --git a/util-apps/WifiUtil/Android.bp b/util-apps/WifiUtil/Android.bp
new file mode 100644
index 0000000..7345a0b
--- /dev/null
+++ b/util-apps/WifiUtil/Android.bp
@@ -0,0 +1,19 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+android_test_helper_app {
+    name: "WifiUtil",
+    srcs: ["src/**/*.java"],
+    min_sdk_version: "7",
+}
diff --git a/util-apps/WifiUtil/Android.mk b/util-apps/WifiUtil/Android.mk
deleted file mode 100644
index 98db113..0000000
--- a/util-apps/WifiUtil/Android.mk
+++ /dev/null
@@ -1,26 +0,0 @@
-# Copyright (C) 2012 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := tests
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-LOCAL_MIN_SDK_VERSION := 7
-LOCAL_SDK_VERSION := current
-LOCAL_PACKAGE_NAME := WifiUtil
-
-include $(BUILD_PACKAGE)
-