Snap for 8680285 from abc522322b1326ab2985cb14b971b3bb6b504ffc to mainline-go-odp-release

Change-Id: I962e304b5565a84f54c73eb4bdae8c92a549f2d2
diff --git a/libraries/audio-test-harness/tradefed/src/main/java/com/android/media/audiotestharness/tradefed/AudioTestHarnessHermeticServerManagingMetricCollector.java b/libraries/audio-test-harness/tradefed/src/main/java/com/android/media/audiotestharness/tradefed/AudioTestHarnessHermeticServerManagingMetricCollector.java
index b9ef1d6..1814b14 100644
--- a/libraries/audio-test-harness/tradefed/src/main/java/com/android/media/audiotestharness/tradefed/AudioTestHarnessHermeticServerManagingMetricCollector.java
+++ b/libraries/audio-test-harness/tradefed/src/main/java/com/android/media/audiotestharness/tradefed/AudioTestHarnessHermeticServerManagingMetricCollector.java
@@ -100,8 +100,6 @@
 
     @Override
     public void onTestRunStart(DeviceMetricData runData) {
-        super.onTestRunStart(runData);
-
         LogUtil.CLog.i("Starting Audio Test Harness...");
 
         // Use the default configuration if no devices are specified, otherwise, create a
@@ -147,8 +145,6 @@
     @Override
     public void onTestRunEnd(
             DeviceMetricData runData, Map<String, MetricMeasurement.Metric> currentRunMetrics) {
-        super.onTestRunEnd(runData, currentRunMetrics);
-
         LogUtil.CLog.i("Stopping Audio Test Harness...");
 
         mAudioTestHarnessGrpcServer.close();
diff --git a/libraries/collectors-helper/memory/src/com/android/helpers/HeapDumpHelper.java b/libraries/collectors-helper/memory/src/com/android/helpers/HeapDumpHelper.java
index b777544..4559090 100644
--- a/libraries/collectors-helper/memory/src/com/android/helpers/HeapDumpHelper.java
+++ b/libraries/collectors-helper/memory/src/com/android/helpers/HeapDumpHelper.java
@@ -16,9 +16,9 @@
 
 package com.android.helpers;
 
-import android.os.Debug;
 import android.util.Log;
 import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.uiautomator.UiDevice;
 
 import java.io.File;
 import java.io.IOException;
@@ -31,77 +31,78 @@
  */
 public class HeapDumpHelper implements ICollectorHelper<String> {
     private static final String TAG = HeapDumpHelper.class.getSimpleName();
-    private static final String HEAPDUMP_OUTPUT_FILE_METRIC_NAME = "heapdump_file";
+    private static final String HEAPDUMP_OUTPUT_FILE_METRIC_NAME = "heapdump_file_";
+    private static final String HEAPDUMP_CMD = "am dumpheap %s %s";
 
-    boolean mIsEnabled = false;
     String mId = null;
     File mResultsFile = null;
+    private String[] mProcessNames = null;
+    private String mTestOutputDir = null;
+    private UiDevice mUiDevice;
+    HashMap<String, String> mHeapDumpFinalMap;
 
     @Override
     public boolean startCollecting() {
         return true;
     }
 
+    public void setUp(String testOutputDir, String... processNames) {
+        mProcessNames = processNames;
+        mTestOutputDir = testOutputDir;
+        mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+      }
+
     @Override
-    public boolean startCollecting(boolean isEnabled, String id) {
-        mIsEnabled = isEnabled;
+    public boolean startCollecting(String id) {
         mId = id;
+        mHeapDumpFinalMap = new HashMap<>();
         if (!collectHeapDump()) {
             return false;
         }
-        return createHeapDumpEmptyFile(id);
+        return true;
     }
 
     @Override
     public Map<String, String> getMetrics() {
 
-        HashMap<String, String> heapDumpFinalMap = new HashMap<>();
-        if (mIsEnabled) {
-            Log.i(TAG, "Metric collector enabled. Dumping the hprof.");
+        Log.i(TAG, "Metric collector enabled. Dumping the hprof.");
+        int processCount = 0;
+        for (String processName : mProcessNames) {
             if (mId != null && !mId.isEmpty()) {
                 try {
-                    Debug.dumpHprofData(mResultsFile.getAbsolutePath());
-                    heapDumpFinalMap.put(HEAPDUMP_OUTPUT_FILE_METRIC_NAME,
-                            mResultsFile.getAbsolutePath());
+                    processCount++;
+                    String finalHeapDumpPath = String.format("%s%s_%s.hprof", mTestOutputDir,
+                            processName.replace("/", "#"), mId);
+                    execHeapDump(processName, finalHeapDumpPath);
+                    mHeapDumpFinalMap.put(HEAPDUMP_OUTPUT_FILE_METRIC_NAME + processCount,
+                            finalHeapDumpPath);
                 } catch (Throwable e) {
-                    Log.e(TAG, "dumpHprofData failed", e);
+                    Log.e(TAG, "dumpheap command failed", e);
                 }
             } else {
                 Log.e(TAG, "Metric collector is enabled but the heap dump file id is not valid.");
             }
-        } else {
-            Log.i(TAG, "Metric collector is disabled.");
         }
-        return heapDumpFinalMap;
+        return mHeapDumpFinalMap;
     }
 
-    /**
-     * Create an empty file that will be used for dumping the heap profile.
-     *
-     * @param fileName name of the empty file.
-     * @return true if the file creation is successful.
-     */
-    private boolean createHeapDumpEmptyFile(String fileName) {
+    private String execHeapDump(String processName, String filePath) throws IOException {
         try {
-            mResultsFile =
-                    File.createTempFile(
-                            fileName,
-                            ".hprof",
-                            InstrumentationRegistry.getInstrumentation()
-                                    .getContext()
-                                    .getExternalFilesDir(null));
+            Log.i(TAG, "Running heapdump command :" + String.format(HEAPDUMP_CMD,
+                    processName, filePath));
+            return mUiDevice.executeShellCommand(String.format(HEAPDUMP_CMD,
+                    processName, filePath));
         } catch (IOException e) {
-            e.printStackTrace();
-            return false;
+            throw new RuntimeException(
+                    String.format("Unable to execute heapdump command for %s ", processName), e);
         }
-        return true;
     }
 
     /**
      * Returns true if heap dump collection is enabled and heap dump file name is valid.
      */
     private boolean collectHeapDump() {
-        if (!mIsEnabled || mId == null || mId.isEmpty()) {
+        if (mId == null || mId.isEmpty()) {
             return false;
         }
         return true;
@@ -109,12 +110,11 @@
 
     @Override
     public boolean stopCollecting() {
-        mIsEnabled = false;
         mId = null;
         return true;
     }
 
-    public File getResultsFile() {
-        return mResultsFile;
+    public Map<String,String> getFinalResultsMap() {
+        return mHeapDumpFinalMap;
     }
 }
diff --git a/libraries/collectors-helper/memory/test/src/com/android/helpers/tests/HeapDumpHelperTest.java b/libraries/collectors-helper/memory/test/src/com/android/helpers/tests/HeapDumpHelperTest.java
index b299edb..f04f550 100644
--- a/libraries/collectors-helper/memory/test/src/com/android/helpers/tests/HeapDumpHelperTest.java
+++ b/libraries/collectors-helper/memory/test/src/com/android/helpers/tests/HeapDumpHelperTest.java
@@ -19,7 +19,9 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
+import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
+import androidx.test.uiautomator.UiDevice;
 import com.android.helpers.HeapDumpHelper;
 import java.util.Map;
 
@@ -28,6 +30,8 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.io.IOException;
+
 /**
  * Android Unit tests for {@link HeapDumpHelper}.
  *
@@ -45,39 +49,44 @@
     }
 
     @After
-    public void tearDown() {
-        if (mHeapDumpHelper.getResultsFile() != null) {
-            mHeapDumpHelper.getResultsFile().delete();
+    public void tearDown() throws IOException {
+        UiDevice uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+        for (Map.Entry<String, String> entry : mHeapDumpHelper.getMetrics().entrySet()) {
+            uiDevice.executeShellCommand(String.format("rm %s", entry.getValue()));
         }
     }
 
     @Test
     public void testSuccessfulHeapDumpCollection() {
-        assertTrue(mHeapDumpHelper.startCollecting(true, "sample-heapdump-1-"));
+        mHeapDumpHelper.setUp("/data/local/tmp/", "com.android.systemui");
+        assertTrue(mHeapDumpHelper.startCollecting("sample-heapdump-1"));
         Map<String, String> metrics = mHeapDumpHelper.getMetrics();
         assertTrue(metrics.size() == 1);
-        assertTrue(
-                mHeapDumpHelper.getResultsFile() != null
-                        && mHeapDumpHelper.getResultsFile().length() > 0);
     }
 
     @Test
-    public void testHeapDumpCollectionDisabled() {
-        assertFalse(mHeapDumpHelper.startCollecting(false, "sample-heapdump-2-"));
+    public void testHeapCollectionProcessWithSpecialChars() {
+        mHeapDumpHelper.setUp("/data/local/tmp/", "/system/bin/surfaceflinger");
+        assertTrue(mHeapDumpHelper.startCollecting("sample-heapdump-1"));
         Map<String, String> metrics = mHeapDumpHelper.getMetrics();
-        assertTrue(metrics.size() == 0);
+        assertTrue(metrics.size() == 1);
+        assertTrue(metrics.get("heapdump_file_1").equalsIgnoreCase(
+                "/data/local/tmp/#system#bin#surfaceflinger_sample-heapdump-1.hprof"));
+    }
+
+    @Test
+    public void testSuccessfulHeapDumpCollectionForTwoProcesses() {
+        String[] processNames = new String[] { "com.android.systemui", "system_server" };
+        mHeapDumpHelper.setUp("/data/local/tmp/", processNames);
+        assertTrue(mHeapDumpHelper.startCollecting("sample-heapdump-2"));
+        Map<String, String> metrics = mHeapDumpHelper.getMetrics();
+        assertTrue(metrics.size() == 2);
     }
 
     @Test
     public void testHeapDumpNotCollectedWithEmptyId() {
-        assertFalse(mHeapDumpHelper.startCollecting(true, ""));
-        Map<String, String> metrics = mHeapDumpHelper.getMetrics();
-        assertTrue(metrics.size() == 0);
-    }
-
-    @Test
-    public void testHeapDumpNotCollectedWithNullId() {
-        assertFalse(mHeapDumpHelper.startCollecting(true, null));
+        mHeapDumpHelper.setUp("/data/local/tmp/", "com.android.systemui");
+        assertFalse(mHeapDumpHelper.startCollecting(""));
         Map<String, String> metrics = mHeapDumpHelper.getMetrics();
         assertTrue(metrics.size() == 0);
     }
diff --git a/libraries/collectors-helper/utilities/src/com/android/helpers/ICollectorHelper.java b/libraries/collectors-helper/utilities/src/com/android/helpers/ICollectorHelper.java
index ec48565..d737cf6 100644
--- a/libraries/collectors-helper/utilities/src/com/android/helpers/ICollectorHelper.java
+++ b/libraries/collectors-helper/utilities/src/com/android/helpers/ICollectorHelper.java
@@ -11,10 +11,10 @@
     boolean startCollecting();
 
     /**
-     * This method will take args which includes a flag and an identifier for the helper.
+     * This method will take args which passes an identifier for the helper.
      * The default implementation is to invoke {@link #startCollecting()} directly.
      */
-    default boolean startCollecting(boolean isEnabled, String id) {
+    default boolean startCollecting(String id) {
         return startCollecting();
     }
 
diff --git a/libraries/device-collectors/src/main/java/android/device/collectors/BaseCollectionListener.java b/libraries/device-collectors/src/main/java/android/device/collectors/BaseCollectionListener.java
index cd7bc2f..fe6a093 100644
--- a/libraries/device-collectors/src/main/java/android/device/collectors/BaseCollectionListener.java
+++ b/libraries/device-collectors/src/main/java/android/device/collectors/BaseCollectionListener.java
@@ -63,10 +63,6 @@
 
     @Override
     public void onTestRunStart(DataRecord runData, Description description) {
-        Bundle args = getArgsBundle();
-        mIsCollectPerRun = "true".equals(args.getString(COLLECT_PER_RUN));
-        // By default this flag is set to false to collect the metrics on test failure.
-        mSkipTestFailureMetrics = "true".equals(args.getString(SKIP_TEST_FAILURE_METRICS));
 
         if (mIsCollectPerRun) {
             Function<String, Boolean> filter = getFilter(description);
@@ -74,6 +70,15 @@
         }
     }
 
+    @Override
+    protected void parseArguments() {
+        super.parseArguments();
+        Bundle args = getArgsBundle();
+        mIsCollectPerRun = "true".equals(args.getString(COLLECT_PER_RUN));
+        // By default this flag is set to false to collect the metrics on test failure.
+        mSkipTestFailureMetrics = "true".equals(args.getString(SKIP_TEST_FAILURE_METRICS));
+    }
+
     protected Function<String, Boolean> getFilter(Description description) {
         return null;
     }
@@ -93,7 +98,7 @@
     }
 
     @Override
-    public final void onTestEnd(DataRecord testData, Description description) {
+    public void onTestEnd(DataRecord testData, Description description) {
         if (!mIsCollectPerRun) {
             // Skip adding the metrics collected during the test failure
             // if the skip metrics on test failure flag is enabled and the
diff --git a/libraries/device-collectors/src/main/java/android/device/collectors/BaseMetricListener.java b/libraries/device-collectors/src/main/java/android/device/collectors/BaseMetricListener.java
index 9d4a132..0e6cae2 100644
--- a/libraries/device-collectors/src/main/java/android/device/collectors/BaseMetricListener.java
+++ b/libraries/device-collectors/src/main/java/android/device/collectors/BaseMetricListener.java
@@ -411,7 +411,7 @@
         return mArgsBundle;
     }
 
-    private void parseArguments() {
+    protected void parseArguments() {
         Bundle args = getArgsBundle();
         // First filter the arguments with the alias
         filterAlias(args);
diff --git a/libraries/device-collectors/src/main/java/android/device/collectors/HeapDumpListener.java b/libraries/device-collectors/src/main/java/android/device/collectors/HeapDumpListener.java
index bd8636b..698bdd0 100644
--- a/libraries/device-collectors/src/main/java/android/device/collectors/HeapDumpListener.java
+++ b/libraries/device-collectors/src/main/java/android/device/collectors/HeapDumpListener.java
@@ -42,21 +42,27 @@
     private static final String REPLACEMENT_CHAR = "#";
     private static final String FILE_ID_FORMAT = "%s_%d";
     private static final String FILE_NAME_PREFIX_FORMAT = "%s_%s";
+    @VisibleForTesting static final String DEFAULT_OUTPUT_DIR = "/data/local/tmp/";
+    @VisibleForTesting static final String OUTPUT_DIR_KEY = "heapdump-test-output-dir";
     @VisibleForTesting static final String ITERATION_SEPARATOR = ",";
     @VisibleForTesting static final String ENABLE_ITERATION_IDS = "enable-iteration-ids";
     @VisibleForTesting static final String ITERATION_ALL_ENABLE = "iteration-all-enable";
+    @VisibleForTesting static final String PROCESS_NAMES_KEY = "heapdump-process-names";
+    @VisibleForTesting static final String PROCESS_SEPARATOR = ",";
     Map<String, Integer> mTestIterationCount = new HashMap<String, Integer>();
     Set<Integer> mValidIterationIds;
     boolean mIsDisabled = false;
     boolean mIsEnabledForAll = false;
+    private HeapDumpHelper mHeapHelper = new HeapDumpHelper();
 
     public HeapDumpListener() {
-        createHelperInstance(new HeapDumpHelper());
+        createHelperInstance(mHeapHelper);
     }
 
     @VisibleForTesting
     public HeapDumpListener(Bundle args, HeapDumpHelper helper) {
         super(args, helper);
+        mHeapHelper = helper;
     }
 
     /** Process the test arguments */
@@ -67,6 +73,22 @@
         mIsEnabledForAll =
                 Boolean.parseBoolean(args.getString(ITERATION_ALL_ENABLE, String.valueOf(false)));
 
+        String testOutputDir = args.getString(OUTPUT_DIR_KEY, DEFAULT_OUTPUT_DIR);
+
+        // Collect for all processes if process list is empty or null.
+        String procsString = args.getString(PROCESS_NAMES_KEY);
+
+        String[] procs = null;
+        if (procsString != null && !procsString.isEmpty()) {
+          procs = procsString.split(PROCESS_SEPARATOR);
+        }
+
+        mHeapHelper.setUp(testOutputDir, procs);
+
+        if (mIsCollectPerRun) {
+            return;
+        }
+
         if (!mIsEnabledForAll) {
             String iterations = args.getString(ENABLE_ITERATION_IDS);
             if (iterations == null || iterations.isEmpty()) {
@@ -82,20 +104,23 @@
 
     @Override
     public void testStart(Function<String, Boolean> filter, Description description) {
-        if (mIsDisabled) {
-            mHelper.startCollecting(false, null);
-            return;
-        }
-
         updateIterationCount(description);
-
         if (mIsEnabledForAll) {
-            mHelper.startCollecting(true, getHeapDumpFileId(description));
-        } else {
-            if (mValidIterationIds.contains(mTestIterationCount.get(getTestFileName(description)))) {
-                mHelper.startCollecting(true, getHeapDumpFileId(description));
-            } else {
-                mHelper.startCollecting(false, null);
+            mHeapHelper.startCollecting(getHeapDumpFileId(description));
+        } else if (mIsCollectPerRun || mValidIterationIds
+                .contains(mTestIterationCount.get(getTestFileName(description)))) {
+            mHeapHelper.startCollecting(getHeapDumpFileId(description));
+        }
+    }
+
+    @Override
+    public final void onTestEnd(DataRecord testData, Description description) {
+        if (!mIsCollectPerRun) {
+            if (mIsEnabledForAll) {
+                super.onTestEnd(testData, description);
+            } else if (mValidIterationIds
+                    .contains(mTestIterationCount.get(getTestFileName(description)))) {
+                super.onTestEnd(testData, description);
             }
         }
     }
@@ -124,11 +149,19 @@
 
     /**
      * Returns the packagename.classname_methodname which has no spaces and used to create file
-     * names.
+     * names. If class name or method name is null then return the heapdump.
      */
     public static String getTestFileName(Description description) {
-        return String.format(FILE_NAME_PREFIX_FORMAT,
-                description.getClassName().replaceAll(SPACES_PATTERN, REPLACEMENT_CHAR).trim(),
-                description.getMethodName().replaceAll(SPACES_PATTERN, REPLACEMENT_CHAR).trim());
+        if (description.getClassName() != null && !description.getClassName().isEmpty()
+                && description.getMethodName() != null
+                && !description.getMethodName().isEmpty()) {
+            String[] className = description.getClassName().split("\\$");
+            return String.format(FILE_NAME_PREFIX_FORMAT,
+                    className[0].replaceAll(SPACES_PATTERN, REPLACEMENT_CHAR).trim(),
+                    description.getMethodName().replaceAll(SPACES_PATTERN, REPLACEMENT_CHAR)
+                            .trim());
+        } else {
+            return "heapdump";
+        }
     }
 }
diff --git a/libraries/device-collectors/src/test/java/android/device/collectors/BaseCollectionListenerTest.java b/libraries/device-collectors/src/test/java/android/device/collectors/BaseCollectionListenerTest.java
index af6d75b..008fa13 100644
--- a/libraries/device-collectors/src/test/java/android/device/collectors/BaseCollectionListenerTest.java
+++ b/libraries/device-collectors/src/test/java/android/device/collectors/BaseCollectionListenerTest.java
@@ -77,7 +77,7 @@
         b.putString(BaseCollectionListener.COLLECT_PER_RUN, "true");
         mListener = initListener(b);
 
-        mListener.onTestRunStart(mListener.createDataRecord(), FAKE_DESCRIPTION);
+        mListener.testRunStarted(FAKE_DESCRIPTION);
         verify(helper, times(1)).startCollecting();
         mListener.onTestStart(mListener.createDataRecord(), FAKE_TEST_DESCRIPTION);
         verify(helper, times(1)).startCollecting();
@@ -97,7 +97,7 @@
         b.putString(BaseCollectionListener.COLLECT_PER_RUN, "false");
         mListener = initListener(b);
 
-        mListener.onTestRunStart(mListener.createDataRecord(), FAKE_DESCRIPTION);
+        mListener.testRunStarted(FAKE_DESCRIPTION);
 
         verify(helper, times(0)).startCollecting();
         mListener.onTestStart(mListener.createDataRecord(), FAKE_TEST_DESCRIPTION);
@@ -123,7 +123,7 @@
         Bundle b = new Bundle();
         mListener = initListener(b);
 
-        mListener.onTestRunStart(mListener.createDataRecord(), FAKE_DESCRIPTION);
+        mListener.testRunStarted(FAKE_DESCRIPTION);
         verify(helper, times(0)).startCollecting();
         mListener.onTestStart(mListener.createDataRecord(), FAKE_TEST_DESCRIPTION);
         verify(helper, times(1)).startCollecting();
@@ -148,7 +148,7 @@
         b.putString(BaseCollectionListener.SKIP_TEST_FAILURE_METRICS, "false");
         mListener = initListener(b);
 
-        mListener.onTestRunStart(mListener.createDataRecord(), FAKE_DESCRIPTION);
+        mListener.testRunStarted(FAKE_DESCRIPTION);
         verify(helper, times(0)).startCollecting();
         mListener.testStarted(FAKE_TEST_DESCRIPTION);
         verify(helper, times(1)).startCollecting();
@@ -169,7 +169,7 @@
         b.putString(BaseCollectionListener.COLLECT_PER_RUN, "false");
         mListener = initListener(b);
 
-        mListener.onTestRunStart(mListener.createDataRecord(), FAKE_DESCRIPTION);
+        mListener.testRunStarted(FAKE_DESCRIPTION);
         verify(helper, times(0)).startCollecting();
         mListener.testStarted(FAKE_TEST_DESCRIPTION);
         verify(helper, times(1)).startCollecting();
@@ -193,7 +193,7 @@
         b.putString(BaseCollectionListener.SKIP_TEST_FAILURE_METRICS, "true");
         mListener = initListener(b);
 
-        mListener.onTestRunStart(mListener.createDataRecord(), FAKE_DESCRIPTION);
+        mListener.testRunStarted(FAKE_DESCRIPTION);
         verify(helper, times(0)).startCollecting();
         mListener.testStarted(FAKE_TEST_DESCRIPTION);
         verify(helper, times(1)).startCollecting();
@@ -217,7 +217,7 @@
         b.putString(BaseCollectionListener.SKIP_TEST_FAILURE_METRICS, "true");
         mListener = initListener(b);
 
-        mListener.onTestRunStart(mListener.createDataRecord(), FAKE_DESCRIPTION);
+        mListener.testRunStarted(FAKE_DESCRIPTION);
         verify(helper, times(0)).startCollecting();
         mListener.testStarted(FAKE_TEST_DESCRIPTION);
         verify(helper, times(1)).startCollecting();
diff --git a/libraries/device-collectors/src/test/java/android/device/collectors/HeapDumpListenerTest.java b/libraries/device-collectors/src/test/java/android/device/collectors/HeapDumpListenerTest.java
index 2d183e5..f4dda56 100644
--- a/libraries/device-collectors/src/test/java/android/device/collectors/HeapDumpListenerTest.java
+++ b/libraries/device-collectors/src/test/java/android/device/collectors/HeapDumpListenerTest.java
@@ -60,7 +60,7 @@
 
         collector.testRunStarted(RUN_DESCRIPTION);
         collector.testStarted(TEST_DESCRIPTION_1);
-        verify(mHelper, times(1)).startCollecting(true, "run_test_one_1");
+        verify(mHelper, times(1)).startCollecting("run_test_one_1");
     }
 
     /** Test heapdump collection force disable for all iterations.*/
@@ -73,7 +73,7 @@
 
         collector.testRunStarted(RUN_DESCRIPTION);
         collector.testStarted(TEST_DESCRIPTION_1);
-        verify(mHelper, times(0)).startCollecting(true, "run_test_one_1");
+        verify(mHelper, times(0)).startCollecting("run_test_one_1");
     }
 
     /** Test heapdump collection disabled for all iterations by default.*/
@@ -85,7 +85,7 @@
 
         collector.testRunStarted(RUN_DESCRIPTION);
         collector.testStarted(TEST_DESCRIPTION_1);
-        verify(mHelper, times(0)).startCollecting(true, "run_test_one_1");
+        verify(mHelper, times(0)).startCollecting("run_test_one_1");
     }
 
     /** Test heapdump collection enabled only for the 2nd and 3rd iterations.*/
@@ -101,9 +101,28 @@
         collector.testStarted(TEST_DESCRIPTION_1);
         collector.testStarted(TEST_DESCRIPTION_1);
         collector.testStarted(TEST_DESCRIPTION_1);
-        verify(mHelper, times(1)).startCollecting(false, null);
-        verify(mHelper, times(1)).startCollecting(true, "run_test_one_2");
-        verify(mHelper, times(1)).startCollecting(true, "run_test_one_3");
+        verify(mHelper, times(1)).startCollecting("run_test_one_2");
+        verify(mHelper, times(1)).startCollecting("run_test_one_3");
+    }
+
+    /** Test heapdump collection enabled only for the 2nd and 3rd iterations.*/
+    @Test
+    public void testHeapCollectionWithProcessNames() throws Exception {
+        Bundle enableSpecificIterationsBundle = new Bundle();
+        enableSpecificIterationsBundle.putString(
+                HeapDumpListener.ENABLE_ITERATION_IDS, "2,3");
+        enableSpecificIterationsBundle.putString(
+                HeapDumpListener.PROCESS_NAMES_KEY, "system_server");
+        HeapDumpListener collector = new HeapDumpListener(enableSpecificIterationsBundle, mHelper);
+        collector.setInstrumentation(mInstrumentation);
+
+        collector.testRunStarted(RUN_DESCRIPTION);
+        collector.testStarted(TEST_DESCRIPTION_1);
+        collector.testStarted(TEST_DESCRIPTION_1);
+        collector.testStarted(TEST_DESCRIPTION_1);
+        verify(mHelper, times(1)).setUp(HeapDumpListener.DEFAULT_OUTPUT_DIR,  "system_server");
+        verify(mHelper, times(1)).startCollecting("run_test_one_2");
+        verify(mHelper, times(1)).startCollecting("run_test_one_3");
     }
 
     /** Test heapdump collection enabled only for the 2nd iterations during multiple tests.*/
@@ -120,9 +139,8 @@
         collector.testStarted(TEST_DESCRIPTION_1);
         collector.testStarted(TEST_DESCRIPTION_2);
         collector.testStarted(TEST_DESCRIPTION_2);
-        verify(mHelper, times(2)).startCollecting(false, null);
-        verify(mHelper, times(1)).startCollecting(true, "run_test_one_2");
-        verify(mHelper, times(1)).startCollecting(true, "run_test_two_2");
+        verify(mHelper, times(1)).startCollecting("run_test_one_2");
+        verify(mHelper, times(1)).startCollecting("run_test_two_2");
     }
 
     /** Test heapdump collection enabled for all the iterations and overrides the
@@ -142,9 +160,62 @@
         collector.testStarted(TEST_DESCRIPTION_1);
         collector.testStarted(TEST_DESCRIPTION_2);
         collector.testStarted(TEST_DESCRIPTION_2);
-        verify(mHelper, times(1)).startCollecting(true, "run_test_one_1");
-        verify(mHelper, times(1)).startCollecting(true, "run_test_one_2");
-        verify(mHelper, times(1)).startCollecting(true, "run_test_two_1");
-        verify(mHelper, times(1)).startCollecting(true, "run_test_two_2");
+        verify(mHelper, times(1)).startCollecting("run_test_one_1");
+        verify(mHelper, times(1)).startCollecting("run_test_one_2");
+        verify(mHelper, times(1)).startCollecting("run_test_two_1");
+        verify(mHelper, times(1)).startCollecting("run_test_two_2");
+    }
+
+    /** Test to verify the iteration separator is handled from the test class name.*/
+    @Test
+    public void testHeapCollectionTestWithIterationSeparator() throws Exception {
+        Bundle enableAllBundle = new Bundle();
+        enableAllBundle.putString(HeapDumpListener.ITERATION_ALL_ENABLE, String.valueOf(true));
+        HeapDumpListener collector = new HeapDumpListener(enableAllBundle, mHelper);
+        collector.setInstrumentation(mInstrumentation);
+
+        collector.testRunStarted(RUN_DESCRIPTION);
+        collector.testStarted(Description.createTestDescription("run$1", "test_two"));
+        verify(mHelper, times(1)).startCollecting("run_test_two_1");
+    }
+
+    /** Test to verify per test run heapdump collection. */
+    @Test
+    public void testHeapCollectionOnlyForTestRun() throws Exception {
+        Bundle collectPerRunBundle = new Bundle();
+        collectPerRunBundle.putString(HeapDumpListener.COLLECT_PER_RUN, "true");
+        HeapDumpListener collector = new HeapDumpListener(collectPerRunBundle, mHelper);
+        collector.setInstrumentation(mInstrumentation);
+        collector.testRunStarted(RUN_DESCRIPTION);
+        verify(mHelper, times(1)).startCollecting("heapdump_1");
+    }
+
+    /** Test to verify per test run heap dump collection flag overrides the per test method flag.*/
+    @Test
+    public void testHeapCollectionForTestRunOverridesPerTest() throws Exception {
+        Bundle collectPerRunBundle = new Bundle();
+        collectPerRunBundle.putString(HeapDumpListener.COLLECT_PER_RUN, "true");
+        collectPerRunBundle.putString(HeapDumpListener.ITERATION_ALL_ENABLE, String.valueOf(true));
+        HeapDumpListener collector = new HeapDumpListener(collectPerRunBundle, mHelper);
+        collector.setInstrumentation(mInstrumentation);
+        collector.testRunStarted(RUN_DESCRIPTION);
+        collector.testStarted(Description.createTestDescription("run$1", "test_two"));
+        verify(mHelper, times(1)).startCollecting("heapdump_1");
+        verify(mHelper, times(0)).startCollecting("run_test_two_1");
+    }
+
+    /** Test to verify per test run heap dump collection flag overrides heapdump collection on
+     * specific iteration id's.*/
+    @Test
+    public void testHeapCollectionForTestRunOverridesPerTestIterations() throws Exception {
+        Bundle collectPerRunBundle = new Bundle();
+        collectPerRunBundle.putString(HeapDumpListener.COLLECT_PER_RUN, "true");
+        collectPerRunBundle.putString(HeapDumpListener.ENABLE_ITERATION_IDS, "1");
+        HeapDumpListener collector = new HeapDumpListener(collectPerRunBundle, mHelper);
+        collector.setInstrumentation(mInstrumentation);
+        collector.testRunStarted(RUN_DESCRIPTION);
+        collector.testStarted(Description.createTestDescription("run$1", "test_two"));
+        verify(mHelper, times(1)).startCollecting("heapdump_1");
+        verify(mHelper, times(0)).startCollecting("run_test_two_1");
     }
 }
diff --git a/libraries/screenshot/src/androidTest/java/platform/test/screenshot/ScreenshotTestRuleTest.kt b/libraries/screenshot/src/androidTest/java/platform/test/screenshot/ScreenshotTestRuleTest.kt
index 7ae14d6..44838ab 100644
--- a/libraries/screenshot/src/androidTest/java/platform/test/screenshot/ScreenshotTestRuleTest.kt
+++ b/libraries/screenshot/src/androidTest/java/platform/test/screenshot/ScreenshotTestRuleTest.kt
@@ -65,6 +65,7 @@
 
     @Test
     fun performDiff_sameSizes_default_noMatch() {
+        val imageExtension = ".png"
         val first = loadBitmap("round_rect_gray")
         val compStatistics = ScreenshotResultProto.DiffResult.ComparisonStatistics.newBuilder()
             .setNumberPixelsCompared(1504)
@@ -81,10 +82,25 @@
 
         val resultProto = rule.getPathOnDeviceFor(RESULT_PROTO)
         assertThat(resultProto.readText()).contains("FAILED")
-        assertThat(rule.getPathOnDeviceFor(IMAGE_ACTUAL).exists()).isTrue()
-        assertThat(rule.getPathOnDeviceFor(IMAGE_DIFF).exists()).isTrue()
-        assertThat(rule.getPathOnDeviceFor(IMAGE_EXPECTED).exists()).isTrue()
-        assertThat(rule.getPathOnDeviceFor(RESULT_BIN_PROTO).exists()).isTrue()
+
+        val actualImagePathOnDevice = rule.getPathOnDeviceFor(IMAGE_ACTUAL)
+        assertThat(actualImagePathOnDevice.exists()).isTrue()
+        assertThat(actualImagePathOnDevice.getName().contains("_actual")).isTrue()
+        assertThat(actualImagePathOnDevice.getName().contains(imageExtension)).isTrue()
+
+        val diffImagePathOnDevice = rule.getPathOnDeviceFor(IMAGE_DIFF)
+        assertThat(diffImagePathOnDevice.exists()).isTrue()
+        assertThat(diffImagePathOnDevice.getName().contains("_diff")).isTrue()
+        assertThat(diffImagePathOnDevice.getName().contains(imageExtension)).isTrue()
+
+        val expectedImagePathOnDevice = rule.getPathOnDeviceFor(IMAGE_EXPECTED)
+        assertThat(expectedImagePathOnDevice.exists()).isTrue()
+        assertThat(expectedImagePathOnDevice.getName().contains("_expected")).isTrue()
+        assertThat(expectedImagePathOnDevice.getName().contains(imageExtension)).isTrue()
+
+        val binProtoPathOnDevice = rule.getPathOnDeviceFor(RESULT_BIN_PROTO)
+        assertThat(binProtoPathOnDevice.exists()).isTrue()
+        assertThat(binProtoPathOnDevice.getName().contains("_goldResult")).isTrue()
     }
 
     @Test
diff --git a/libraries/screenshot/src/main/java/platform/test/screenshot/ScreenshotTestRule.kt b/libraries/screenshot/src/main/java/platform/test/screenshot/ScreenshotTestRule.kt
index 61d1ba8..b69329f 100644
--- a/libraries/screenshot/src/main/java/platform/test/screenshot/ScreenshotTestRule.kt
+++ b/libraries/screenshot/src/main/java/platform/test/screenshot/ScreenshotTestRule.kt
@@ -51,7 +51,8 @@
     val goldenImagePathManager: GoldenImagePathManager
 ) : TestRule {
 
-    private val resultBinaryProtoFileSuffix = ".pb"
+    private val imageExtension = ".png"
+    private val resultBinaryProtoFileSuffix = "goldResult.pb"
     // This is used in CI to identify the files.
     private val resultProtoFileSuffix = "goldResult.textproto"
 
@@ -246,11 +247,11 @@
     internal fun getPathOnDeviceFor(fileType: OutputFileType): File {
         val fileName = when (fileType) {
             OutputFileType.IMAGE_ACTUAL ->
-                "${testIdentifier}_actual$goldenImagePathManager.imageExtension"
+                "${testIdentifier}_actual_$goldenImagePathManager.$imageExtension"
             OutputFileType.IMAGE_EXPECTED ->
-                "${testIdentifier}_expected$goldenImagePathManager.imageExtension"
+                "${testIdentifier}_expected_$goldenImagePathManager.$imageExtension"
             OutputFileType.IMAGE_DIFF ->
-                "${testIdentifier}_diff$goldenImagePathManager.imageExtension"
+                "${testIdentifier}_diff_$goldenImagePathManager.$imageExtension"
             OutputFileType.RESULT_PROTO -> "${testIdentifier}_$resultProtoFileSuffix"
             OutputFileType.RESULT_BIN_PROTO -> "${testIdentifier}_$resultBinaryProtoFileSuffix"
         }
diff --git a/libraries/sts-common-util/host-side/Android.bp b/libraries/sts-common-util/host-side/Android.bp
index f151cd3..7a07ef4 100644
--- a/libraries/sts-common-util/host-side/Android.bp
+++ b/libraries/sts-common-util/host-side/Android.bp
@@ -24,6 +24,7 @@
 
     static_libs: [
         "sts-common-util-lib",
+        "xz-java",
     ],
 
     libs: [
diff --git a/libraries/sts-common-util/host-side/src/com/android/sts/common/FridaUtils.java b/libraries/sts-common-util/host-side/src/com/android/sts/common/FridaUtils.java
new file mode 100644
index 0000000..328fec4
--- /dev/null
+++ b/libraries/sts-common-util/host-side/src/com/android/sts/common/FridaUtils.java
@@ -0,0 +1,215 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sts.common;
+
+import static com.android.sts.common.CommandUtil.runAndCheck;
+import static java.util.stream.Collectors.toList;
+import static org.junit.Assert.assertEquals;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Stream;
+import org.tukaani.xz.XZInputStream;
+
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.util.FileUtil;
+import com.android.tradefed.util.RunUtil;
+
+public class FridaUtils implements AutoCloseable {
+    private static final String PRODUCT_CPU_ABI_KEY = "ro.product.cpu.abi";
+    private static final String PRODUCT_CPU_ABILIST_KEY = "ro.product.cpu.abilist";
+    private static final String FRIDA_PACKAGE = "frida-inject";
+    private static final String FRIDA_OS = "android";
+    private static final String TMP_PATH = "/data/local/tmp/";
+
+    private final ITestDevice device;
+    private final CompatibilityBuildHelper buildHelper;
+    private final String remoteFridaExeName;
+    private List<Integer> runningPids = new ArrayList<>();
+    private List<String> fridaFiles = new ArrayList<>();
+
+    private FridaUtils(ITestDevice device, IBuildInfo buildInfo, String fridaVersion)
+            throws DeviceNotAvailableException, UnsupportedOperationException, IOException {
+        this.device = device;
+        this.buildHelper = new CompatibilityBuildHelper(buildInfo);
+
+        // Figure out which Frida arch we should be using for our device
+        String fridaAbi = getFridaAbiFor(device);
+        String fridaExeName =
+                String.format("%s-%s-%s-%s", FRIDA_PACKAGE, fridaVersion, FRIDA_OS, fridaAbi);
+
+        // Download Frida if needed
+        File localFridaExe;
+        try {
+            localFridaExe = buildHelper.getTestFile(fridaExeName);
+            CLog.d("%s found at %s", fridaExeName, localFridaExe.getAbsolutePath());
+        } catch (FileNotFoundException e) {
+            String fridaUrl =
+                    String.format(
+                            "https://github.com/frida/frida/releases/download/%s/%s.xz",
+                            fridaVersion, fridaExeName);
+            CLog.d("%s not found. Downloading from %s", fridaExeName, fridaUrl);
+            try {
+                URL url = new URL(fridaUrl);
+                URLConnection conn = url.openConnection();
+                XZInputStream in = new XZInputStream(conn.getInputStream());
+                File tmpOutput = FileUtil.createTempFile("STS", fridaExeName);
+                FileUtil.writeToFile(in, tmpOutput);
+                localFridaExe = new File(buildHelper.getTestsDir(), fridaExeName);
+                FileUtil.copyFile(tmpOutput, localFridaExe);
+                tmpOutput.delete();
+            } catch (Exception e2) {
+                CLog.e(
+                        "Could not download Frida. Please manually download '%s' and extract to "
+                                + "'%s', renaming the file to '%s' as necessary.",
+                        fridaUrl, buildHelper.getTestsDir(), fridaExeName);
+                throw e2;
+            }
+        }
+
+        // Upload Frida binary to device
+        device.enableAdbRoot();
+        remoteFridaExeName = new File(TMP_PATH, localFridaExe.getName()).getAbsolutePath();
+        device.pushFile(localFridaExe, remoteFridaExeName);
+        runAndCheck(device, String.format("chmod a+x '%s'", remoteFridaExeName));
+        fridaFiles.add(remoteFridaExeName);
+        device.disableAdbRoot();
+    }
+
+    /**
+     * Find out which Frida binary we need and download it if needed.
+     *
+     * @param device device to use Frida on
+     * @param buildInfo test device build info (from test.getBuild())
+     * @return an AutoCloseable FridaUtils object that can be used to run Frida scripts with
+     */
+    public static FridaUtils withFrida(
+            ITestDevice device, IBuildInfo buildInfo, String fridaVersion)
+            throws DeviceNotAvailableException, UnsupportedOperationException, IOException {
+        return new FridaUtils(device, buildInfo, fridaVersion);
+    }
+
+    /**
+     * Upload and run frida script on given process.
+     *
+     * @param fridaJsScriptContent Content of the Frida JS script. Note: this is not a file name
+     * @param pid PID of the process to attach Frida to
+     * @return ByteArrayOutputStream containing stdout and stderr of frida command
+     */
+    public ByteArrayOutputStream withFridaScript(final String fridaJsScriptContent, int pid)
+            throws DeviceNotAvailableException, FileNotFoundException, IOException,
+                    TimeoutException, InterruptedException {
+        // Upload Frida script to device
+        device.enableAdbRoot();
+        String uuid = UUID.randomUUID().toString();
+        String remoteFridaJsScriptName =
+                new File(TMP_PATH, "frida_" + uuid + ".js").getAbsolutePath();
+        device.pushString(fridaJsScriptContent, remoteFridaJsScriptName);
+        fridaFiles.add(remoteFridaJsScriptName);
+
+        // Execute Frida, binding to given PID, in the background
+        List<String> cmd =
+                List.of(
+                        "adb",
+                        "-s",
+                        device.getSerialNumber(),
+                        "shell",
+                        remoteFridaExeName,
+                        "-p",
+                        String.valueOf(pid),
+                        "-s",
+                        remoteFridaJsScriptName,
+                        "--runtime=v8");
+        ByteArrayOutputStream output = new ByteArrayOutputStream();
+        RunUtil.getDefault().runCmdInBackground(cmd, output);
+
+        // frida can fail to attach after a short pause so wait for that
+        TimeUnit.SECONDS.sleep(5);
+        try {
+            Map<Integer, String> pids =
+                    ProcessUtil.waitProcessRunning(device, "^" + remoteFridaExeName);
+            assertEquals("Unexpected Frida processes with the same name", 1, pids.size());
+            runningPids.add(pids.keySet().iterator().next());
+        } catch (Exception e) {
+            CLog.e(e);
+            CLog.e("Frida attach output: %s", output.toString(StandardCharsets.UTF_8));
+            throw e;
+        }
+        device.disableAdbRoot();
+        return output;
+    }
+
+    @Override
+    /** Kill all running Frida processes and delete all files uploaded. */
+    public void close() throws DeviceNotAvailableException, TimeoutException {
+        device.enableAdbRoot();
+        for (Integer pid : runningPids) {
+            ProcessUtil.killPid(device, pid.intValue(), 10_000L);
+        }
+        for (String file : fridaFiles) {
+            device.deleteFile(file);
+        }
+        device.disableAdbRoot();
+    }
+
+    /**
+     * Return the best ABI of Frida that we should download for given device.
+     *
+     * <p>Throw UnsupportedOperationException if Frida does not support device's ABI.
+     */
+    private String getFridaAbiFor(ITestDevice device)
+            throws DeviceNotAvailableException, UnsupportedOperationException {
+        for (String abi : getSupportedAbis(device)) {
+            if (abi.startsWith("arm64")) {
+                return "arm64";
+            } else if (abi.startsWith("armeabi")) {
+                return "arm";
+            } else if (abi.startsWith("x86_64")) {
+                return "x86_64";
+            } else if (abi.startsWith("x86")) {
+                return "x86";
+            }
+        }
+        throw new UnsupportedOperationException(
+                String.format("Device %s is not supported by Frida", device.getSerialNumber()));
+    }
+
+    /** Return a list of supported ABIs by the device in order of preference. */
+    private List<String> getSupportedAbis(ITestDevice device) throws DeviceNotAvailableException {
+        String primaryAbi = device.getProperty(PRODUCT_CPU_ABI_KEY);
+        String[] supportedAbis = device.getProperty(PRODUCT_CPU_ABILIST_KEY).split(",");
+        return Stream.concat(Stream.of(primaryAbi), Arrays.stream(supportedAbis))
+                .distinct()
+                .collect(toList());
+    }
+}
diff --git a/libraries/sts-common-util/host-side/src/com/android/sts/common/RootcanalUtils.java b/libraries/sts-common-util/host-side/src/com/android/sts/common/RootcanalUtils.java
index c76ed5e..ea22885 100644
--- a/libraries/sts-common-util/host-side/src/com/android/sts/common/RootcanalUtils.java
+++ b/libraries/sts-common-util/host-side/src/com/android/sts/common/RootcanalUtils.java
@@ -33,6 +33,7 @@
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.concurrent.TimeoutException;
 import java.util.concurrent.TimeUnit;
 import java.util.List;
@@ -326,6 +327,7 @@
          * @param packet raw packet data to send to device
          */
         public void sendHciPacket(byte[] packet) throws IOException {
+            CLog.d("sending HCI: %s", Arrays.toString(packet));
             hciSocket.getOutputStream().write(packet);
         }