Merge "Deprecate FILE_NAME_TAG"
diff --git a/libraries/annotations/src/android/platform/test/annotations/PlatinumTest.java b/libraries/annotations/src/android/platform/test/annotations/PlatinumTest.java
new file mode 100644
index 0000000..6ec6dad
--- /dev/null
+++ b/libraries/annotations/src/android/platform/test/annotations/PlatinumTest.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2023 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 android.platform.test.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Marks a test that should run as part of the Platinum.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD, ElementType.TYPE})
+public @interface PlatinumTest {
+    /** Defines the Platinum test focus area which the test associates to. */
+    String focusArea() default "";
+}
\ No newline at end of file
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 9a9a350..764ede7 100644
--- a/libraries/collectors-helper/memory/src/com/android/helpers/HeapDumpHelper.java
+++ b/libraries/collectors-helper/memory/src/com/android/helpers/HeapDumpHelper.java
@@ -18,11 +18,15 @@
 
 import android.util.Log;
 
+import androidx.annotation.VisibleForTesting;
 import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.uiautomator.UiDevice;
 
 import java.io.File;
 import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
 import java.util.HashMap;
 import java.util.Map;
 
@@ -36,11 +40,21 @@
     private static final String HEAPDUMP_NATIVE_OUTPUT_FILE_METRIC_NAME = "native_heapdump_file_";
     private static final String HEAPDUMP_CMD = "am dumpheap %s %s";
     private static final String NATIVE_HEAPDUMP_CMD = "am dumpheap -n %s %s";
+    private static final String PIDOF_CMD = "pidof %s";
+    private static final String MV_CMD = "mv %s %s";
+
+    @VisibleForTesting
+    static final String MANAGED_HEAPDUMP_EMPTY_FILES_COUNT_METRIC =
+            "managed_heapdump_empty_files_count";
+
+    @VisibleForTesting
+    static final String NATIVE_HEAPDUMP_EMPTY_FILES_COUNT_METRIC =
+            "native_heapdump_empty_files_count";
 
     String mId = null;
     File mResultsFile = null;
     private String[] mProcessNames = null;
-    private String mTestOutputDir = null;
+    private Path mTestOutputDir;
     private boolean mNativeHeapDumpEnabled = false;
     private UiDevice mUiDevice;
     HashMap<String, String> mHeapDumpFinalMap;
@@ -52,7 +66,7 @@
 
     public void setUp(String testOutputDir, String... processNames) {
         mProcessNames = processNames;
-        mTestOutputDir = testOutputDir;
+        mTestOutputDir = Paths.get(testOutputDir);
         mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
       }
 
@@ -68,38 +82,53 @@
 
     @Override
     public Map<String, String> getMetrics() {
-
         Log.i(TAG, "Metric collector enabled. Dumping the hprof.");
         int processCount = 0;
+        int managedEmptyFilesCount = 0;
+        int nativeEmptyFilesCount = 0;
         for (String processName : mProcessNames) {
+            String pid = getPid(processName);
+            // If the PID doesn't exist, we don't need to run the dumpheap command
+            if (pid.isEmpty()) {
+                continue;
+            }
             if (mId != null && !mId.isEmpty()) {
                 try {
                     processCount++;
-                    String finalHeapDumpPath =
+                    String fileName =
                             String.format(
-                                    "%s%s%s_%s.hprof",
-                                    mTestOutputDir,
+                                    "%s%s_%s.hprof",
                                     HEAPDUMP_MANAGED_OUTPUT_FILE_METRIC_NAME,
                                     processName.replace("/", "#"),
                                     mId);
-                    execHeapDump(processName, finalHeapDumpPath, false);
+                    String finalHeapDumpPath = mTestOutputDir.resolve(fileName).toString();
+                    execHeapDump(pid, processName, finalHeapDumpPath, false);
+                    if (isEmptyFile(finalHeapDumpPath)) {
+                        managedEmptyFilesCount++;
+                        finalHeapDumpPath = renameEmptyFile(mTestOutputDir, fileName);
+                    }
                     mHeapDumpFinalMap.put(
                             HEAPDUMP_MANAGED_OUTPUT_FILE_METRIC_NAME + processCount,
                             finalHeapDumpPath);
                     if (mNativeHeapDumpEnabled) {
-                        String finalNativeHeapDumpPath =
+                        String nativeFileName =
                                 String.format(
-                                        "%s%s%s_%s.txt",
-                                        mTestOutputDir,
+                                        "%s%s_%s.txt",
                                         HEAPDUMP_NATIVE_OUTPUT_FILE_METRIC_NAME,
                                         processName.replace("/", "#"),
                                         mId);
-                        execHeapDump(processName, finalNativeHeapDumpPath, true);
+                        String finalNativeHeapDumpPath =
+                                mTestOutputDir.resolve(nativeFileName).toString();
+                        execHeapDump(pid, processName, finalNativeHeapDumpPath, true);
+                        if (isEmptyFile(finalNativeHeapDumpPath)) {
+                            nativeEmptyFilesCount++;
+                            finalNativeHeapDumpPath =
+                                    renameEmptyFile(mTestOutputDir, nativeFileName);
+                        }
                         mHeapDumpFinalMap.put(
                                 HEAPDUMP_NATIVE_OUTPUT_FILE_METRIC_NAME + processCount,
                                 finalNativeHeapDumpPath);
                     }
-
                 } catch (Throwable e) {
                     Log.e(TAG, "dumpheap command failed", e);
                 }
@@ -107,19 +136,35 @@
                 Log.e(TAG, "Metric collector is enabled but the heap dump file id is not valid.");
             }
         }
+        mHeapDumpFinalMap.put(
+                MANAGED_HEAPDUMP_EMPTY_FILES_COUNT_METRIC, String.valueOf(managedEmptyFilesCount));
+        mHeapDumpFinalMap.put(
+                NATIVE_HEAPDUMP_EMPTY_FILES_COUNT_METRIC, String.valueOf(nativeEmptyFilesCount));
         return mHeapDumpFinalMap;
     }
 
-    private String execHeapDump(String processName, String filePath, boolean isNativeHeapDump)
+    /** Get the pid of a process name */
+    private String getPid(String processName) {
+        String output = "";
+        try {
+            output = mUiDevice.executeShellCommand(String.format(PIDOF_CMD, processName));
+            Log.i(TAG, String.format("The PID of %s is %s.", processName, output));
+        } catch (IOException e) {
+            Log.e(TAG, String.format("Failed to get the pid of %s", processName), e);
+        }
+        return output;
+    }
+
+    private String execHeapDump(
+            String pid, String processName, String filePath, boolean isNativeHeapDump)
             throws IOException {
         try {
-            String heapdumpCommand = isNativeHeapDump ? NATIVE_HEAPDUMP_CMD : HEAPDUMP_CMD;
-            Log.i(
-                    TAG,
-                    "Running heapdump command :"
-                            + String.format(heapdumpCommand, processName, filePath));
-            return mUiDevice.executeShellCommand(
-                    String.format(heapdumpCommand, processName, filePath));
+            String heapdumpCommand =
+                    isNativeHeapDump
+                            ? String.format(NATIVE_HEAPDUMP_CMD, pid, filePath)
+                            : String.format(HEAPDUMP_CMD, pid, filePath);
+            Log.i(TAG, "Running heapdump command :" + heapdumpCommand);
+            return mUiDevice.executeShellCommand(heapdumpCommand);
         } catch (IOException e) {
             throw new RuntimeException(
                     String.format("Unable to execute heapdump command for %s ", processName), e);
@@ -136,6 +181,26 @@
         return true;
     }
 
+    /** Returns true if heapdump file is empty */
+    private boolean isEmptyFile(String path) throws IOException {
+        long bytes = Files.size(Paths.get(path));
+        Log.i(TAG, String.format("File size of %s is %s bytes", path, bytes));
+        return bytes < 10;
+    }
+
+    /** Rename an empty file */
+    private String renameEmptyFile(Path dir, String fileName) {
+        String oldFile = dir.resolve(fileName).toString();
+        String newFile = dir.resolve("EMPTY-" + fileName).toString();
+        try {
+            mUiDevice.executeShellCommand(String.format(MV_CMD, oldFile, newFile));
+            return newFile;
+        } catch (IOException e) {
+            Log.i(TAG, String.format("Rename %s failed.", oldFile), e);
+        }
+        return oldFile;
+    }
+
     @Override
     public boolean stopCollecting() {
         mId = null;
diff --git a/libraries/collectors-helper/memory/src/com/android/helpers/ShowmapSnapshotHelper.java b/libraries/collectors-helper/memory/src/com/android/helpers/ShowmapSnapshotHelper.java
index 455c1e5..f1edd4d 100644
--- a/libraries/collectors-helper/memory/src/com/android/helpers/ShowmapSnapshotHelper.java
+++ b/libraries/collectors-helper/memory/src/com/android/helpers/ShowmapSnapshotHelper.java
@@ -29,6 +29,7 @@
 import java.io.FileWriter;
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.InputMismatchException;
@@ -47,12 +48,15 @@
  */
 public class ShowmapSnapshotHelper implements ICollectorHelper<String> {
     private static final String TAG = ShowmapSnapshotHelper.class.getSimpleName();
-
     private static final String DROP_CACHES_CMD = "echo %d > /proc/sys/vm/drop_caches";
     private static final String PIDOF_CMD = "pidof %s";
     public static final String ALL_PROCESSES_CMD = "ps -A";
     private static final String SHOWMAP_CMD = "showmap -v %d";
     private static final String CHILD_PROCESSES_CMD = "ps -A --ppid %d";
+    @VisibleForTesting public static final String OOM_SCORE_ADJ_CMD = "cat /proc/%d/oom_score_adj";
+    private static final int PROCESS_OOM_SCORE_IMPERCEPTIBLE = 200;
+    private static final int PROCESS_OOM_SCORE_CACHED = 899;
+    private static final String ACTIVITY_LRU_CMD = "dumpsys activity lru";
     private static final String THREADS_FILE_PATH = "/sdcard/countThreads.sh";
     @VisibleForTesting public static final String THREADS_CMD = "sh /sdcard/countThreads.sh";
     private static final String THREADS_EXEC_SCRIPT =
@@ -60,16 +64,19 @@
                     + " /proc/$i/cmdline) : $(ls /proc/$i/task | wc -l)\"; done;";
     public static final String THREADS_PATTERN = "(?<key>^threads_count_.+) : (?<value>[0-9]+)";
     public static final String OUTPUT_METRIC_PATTERN = "showmap_%s_bytes";
+    public static final String OUTPUT_IMPERCEPTIBLE_METRIC_PATTERN =
+            "showmap_%s_bytes_imperceptible";
     public static final String OUTPUT_FILE_PATH_KEY = "showmap_output_file";
     public static final String PROCESS_COUNT = "process_count";
     public static final String CHILD_PROCESS_COUNT_PREFIX = "child_processes_count";
     public static final String OUTPUT_CHILD_PROCESS_COUNT_KEY = CHILD_PROCESS_COUNT_PREFIX + "_%s";
     public static final String PROCESS_WITH_CHILD_PROCESS_COUNT =
             "process_with_child_process_count";
-    private static final String CHILD_PROCESS_NAME_REGEX = "(\\S+)$";
     private static final String METRIC_VALUE_SEPARATOR = "_";
     public static final String PARENT_PROCESS_STRING = "parent_process";
     public static final String CHILD_PROCESS_STRING = "child_process";
+    // The reason to skip the process: b/272181398#comment24
+    private static final Set<String> SKIP_PROCESS = new HashSet<>(Arrays.asList("logcat", "sh"));
 
     private String[] mProcessNames = null;
     private String mTestOutputDir = null;
@@ -162,37 +169,61 @@
             }
             HashSet<Integer> zygoteChildrenPids = getZygoteChildrenPids();
             FileWriter writer = new FileWriter(new File(mTestOutputFile), true);
+
+            try {
+                // dump the activity lru to better understand the process state
+                String activityLRU = executeShellCommand(ACTIVITY_LRU_CMD);
+                Log.d(TAG, String.format("Dumpsys activity lru output: %s", activityLRU));
+            } catch (IOException e) {
+                Log.e(TAG, String.format("Failed to execute %s", ACTIVITY_LRU_CMD));
+            }
+
             for (String processName : mProcessNames) {
                 List<Integer> pids = new ArrayList<>();
                 // Collect required data
                 try {
                     pids = getPids(processName);
                     for (Integer pid : pids) {
-                      // Force Garbage collect to trim transient objects before taking memory
-                      // measurements as memory tests aim to track persistent memory regression
-                      // instead of transient memory which also allows for de-noising and reducing
-                      // likelihood of false alerts.
-                      if (mRunGcPrecollection && zygoteChildrenPids.contains(pid)) {
-                        // Skip native processes from sending GC signal.
-                        android.os.Trace.beginSection("IssueGCForPid: " + pid);
-                        // Perform a synchronous GC which happens when we request meminfo
-                        // This save us the need of setting up timeouts that may or may not
-                        // match with the end time of GC.
-                        mUiDevice.executeShellCommand("dumpsys meminfo -a " + pid);
-                        android.os.Trace.endSection();
-                      }
+                        // Force Garbage collect to trim transient objects before taking memory
+                        // measurements as memory tests aim to track persistent memory regression
+                        // instead of transient memory which also allows for de-noising and reducing
+                        // likelihood of false alerts.
+                        if (mRunGcPrecollection && zygoteChildrenPids.contains(pid)) {
+                            // Skip native processes from sending GC signal.
+                            android.os.Trace.beginSection("IssueGCForPid: " + pid);
+                            // Perform a synchronous GC which happens when we request meminfo
+                            // This save us the need of setting up timeouts that may or may not
+                            // match with the end time of GC.
+                            mUiDevice.executeShellCommand("dumpsys meminfo -a " + pid);
+                            android.os.Trace.endSection();
+                        }
 
-                      android.os.Trace.beginSection("ExecuteShowmap");
-                      String showmapOutput = execShowMap(processName, pid);
-                      android.os.Trace.endSection();
-                      parseAndUpdateMemoryInfo(processName, showmapOutput);
-                      // Store showmap output into file. If there are more than one process
-                      // with same name write the individual showmap associated with pid.
-                      storeToFile(mTestOutputFile, processName, pid, showmapOutput, writer);
-                      // Parse number of child processes for the given pid and update the
-                      // total number of child process count for the process name that pid
-                      // is associated with.
-                      updateChildProcessesDetails(processName, pid);
+                        android.os.Trace.beginSection("ExecuteShowmap");
+                        String showmapOutput = execShowMap(processName, pid);
+                        android.os.Trace.endSection();
+                        // Mark the imperceptible process for showmap and child process count
+                        if (isProcessOomScoreAbove(
+                                processName, pid, PROCESS_OOM_SCORE_IMPERCEPTIBLE)) {
+                            Log.i(
+                                    TAG,
+                                    String.format(
+                                            "This process is imperceptible: %s", processName));
+                            parseAndUpdateMemoryInfo(
+                                    processName,
+                                    showmapOutput,
+                                    OUTPUT_IMPERCEPTIBLE_METRIC_PATTERN);
+                        } else {
+                            parseAndUpdateMemoryInfo(
+                                    processName, showmapOutput, OUTPUT_METRIC_PATTERN);
+                        }
+
+                        // Store showmap output into file. If there are more than one process
+                        // with same name write the individual showmap associated with pid.
+                        storeToFile(mTestOutputFile, processName, pid, showmapOutput, writer);
+                        // Parse number of child processes for the given pid and update the
+                        // total number of child process count for the process name that pid
+                        // is associated with.
+                        updateChildProcessesDetails(processName, pid);
                     }
                 } catch (RuntimeException e) {
                     Log.e(TAG, e.getMessage(), e.getCause());
@@ -388,7 +419,8 @@
      * @param processName name of the process to extract memory info for
      * @param showmapOutput showmap command output
      */
-    private void parseAndUpdateMemoryInfo(String processName, String showmapOutput)
+    private void parseAndUpdateMemoryInfo(
+            String processName, String showmapOutput, String metricPattern)
             throws RuntimeException {
         try {
 
@@ -405,9 +437,8 @@
 
             for (Map.Entry<String, List<Integer>> entry : mMetricNameIndexMap.entrySet()) {
                 Long metricValue = 0L;
-                String metricKey = constructKey(
-                        String.format(OUTPUT_METRIC_PATTERN, entry.getKey()),
-                        processName);
+                String metricKey =
+                        constructKey(String.format(metricPattern, entry.getKey()), processName);
                 for (int index = 0; index < entry.getValue().size(); index++) {
                     metricValue += Long.parseLong(summarySplit[entry.getValue().get(index) + 1]);
                 }
@@ -473,6 +504,27 @@
     }
 
     /**
+     * Return true if the giving process is imperceptible. If the OOM adjustment score is in [900,
+     * 1000), the process is cached. If the OOM adjustment score is in (-1000, 200], the process is
+     * perceptible. If the OOM adjustment score is in (200, 1000), the process is imperceptible
+     */
+    public boolean isProcessOomScoreAbove(String processName, long pid, int threshold) {
+        try {
+            String score = executeShellCommand(String.format(OOM_SCORE_ADJ_CMD, pid));
+            boolean result = Integer.parseInt(score.trim()) > threshold;
+            Log.i(
+                    TAG,
+                    String.format(
+                            "The OOM adjustment score for process %s is %s", processName, score));
+            return result;
+        } catch (IOException e) {
+            Log.e(TAG, String.format("Unable to get process oom_score_adj for %s", processName), e);
+            // We don't know the process is cached or not, still collect it
+            return false;
+        }
+    }
+
+    /**
      * Retrieves the number of child processes for the given process id and updates the total
      * process count and adds a child process metric for the process name that pid is associated
      * with.
@@ -482,8 +534,8 @@
      */
     private void updateChildProcessesDetails(String processName, long pid) {
         String childProcessName;
+        String childPID;
         String completeChildProcessMetric;
-        Pattern childProcessPattern = Pattern.compile(CHILD_PROCESS_NAME_REGEX);
         try {
             Log.i(TAG,
                     String.format("Retrieving child processes count for process name: %s with"
@@ -491,38 +543,50 @@
             String childProcessesStr = mUiDevice
                     .executeShellCommand(String.format(CHILD_PROCESSES_CMD, pid));
             Log.i(TAG, String.format("Child processes cmd output: %s", childProcessesStr));
-            String[] childProcessStrSplit = childProcessesStr.split("\\n");
-            // To discard the header line in the command output.
-            int childProcessCount = childProcessStrSplit.length - 1;
-            String childCountMetricKey = String.format(OUTPUT_CHILD_PROCESS_COUNT_KEY, processName);
 
+            int childProcessCount = 0;
+            String[] childProcessStrSplit = childProcessesStr.split("\\n");
+            for (String line : childProcessStrSplit) {
+                // To discard the header line in the command output.
+                if (Objects.equals(line, childProcessStrSplit[0])) continue;
+                String[] childProcessSplit = line.trim().split("\\s+");
+                /**
+                 * final metric will be of following format
+                 * parent_process_<process>_child_process_<process>
+                 * parent_process_zygote64_child_process_system_server
+                 */
+                childPID = childProcessSplit[1];
+                childProcessName = childProcessSplit[8];
+                // Skip the logcat and sh processes in child process count
+                if (SKIP_PROCESS.contains(childProcessName)
+                        || isProcessOomScoreAbove(
+                                childProcessName,
+                                Long.parseLong(childPID),
+                                PROCESS_OOM_SCORE_CACHED)) {
+                    Log.i(
+                            TAG,
+                            String.format(
+                                    "Skip the child process %s in the parent process %s.",
+                                    childProcessName, processName));
+                    continue;
+                }
+                childProcessCount++;
+                completeChildProcessMetric =
+                        String.join(
+                                METRIC_VALUE_SEPARATOR,
+                                PARENT_PROCESS_STRING,
+                                processName,
+                                CHILD_PROCESS_STRING,
+                                childProcessName);
+                mMemoryMap.put(completeChildProcessMetric, "1");
+            }
+            String childCountMetricKey = String.format(OUTPUT_CHILD_PROCESS_COUNT_KEY, processName);
             if (childProcessCount > 0) {
                 mMemoryMap.put(childCountMetricKey,
                         Long.toString(
                                 Long.parseLong(mMemoryMap.getOrDefault(childCountMetricKey, "0"))
                                         + childProcessCount));
             }
-            for (String line : childProcessStrSplit) {
-                // To discard the header line in the command output.
-                if (Objects.equals(line, childProcessStrSplit[0])) continue;
-                Matcher childProcessMatcher = childProcessPattern.matcher(line);
-                if (childProcessMatcher.find()) {
-                    /**
-                     * final metric will be of following format
-                     * parent_process_<process>_child_process_<process>
-                     * parent_process_zygote64_child_process_system_server
-                     */
-                    childProcessName = childProcessMatcher.group(1);
-                    completeChildProcessMetric =
-                            String.join(
-                                    METRIC_VALUE_SEPARATOR,
-                                    PARENT_PROCESS_STRING,
-                                    processName,
-                                    CHILD_PROCESS_STRING,
-                                    childProcessName);
-                    mMemoryMap.put(completeChildProcessMetric, "1");
-                }
-            }
         } catch (IOException e) {
             throw new RuntimeException("Unable to run child process command.", e);
         }
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 c322e26..386ce38 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
@@ -58,11 +58,29 @@
     }
 
     @Test
+    public void testHeapDumpCollectionNoProcess() {
+        mHeapDumpHelper.setUp("/data/local/tmp/", "");
+        assertTrue(mHeapDumpHelper.startCollecting("sample-heapdump-1"));
+        Map<String, String> metrics = mHeapDumpHelper.getMetrics();
+        assertTrue(metrics.size() == 2);
+        assertTrue(metrics.get("managed_heapdump_empty_files_count").equalsIgnoreCase("0"));
+        assertTrue(metrics.get("native_heapdump_empty_files_count").equalsIgnoreCase("0"));
+    }
+
+    @Test
     public void testSuccessfulHeapDumpCollection() {
         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(metrics.size() == 3);
+        assertTrue(metrics.get("managed_heapdump_empty_files_count").equalsIgnoreCase("0"));
+        assertTrue(metrics.get("native_heapdump_empty_files_count").equalsIgnoreCase("0"));
+        assertTrue(
+                metrics.get("managed_heapdump_file_1")
+                        .equalsIgnoreCase(
+                                "/data/local/tmp/"
+                                        + "managed_heapdump_file_"
+                                        + "com.android.systemui_sample-heapdump-1.hprof"));
     }
 
     @Test
@@ -70,11 +88,15 @@
         mHeapDumpHelper.setUp("/data/local/tmp/", "/system/bin/surfaceflinger");
         assertTrue(mHeapDumpHelper.startCollecting("sample-heapdump-1"));
         Map<String, String> metrics = mHeapDumpHelper.getMetrics();
-        assertTrue(metrics.size() == 1);
+        assertTrue(metrics.size() == 3);
+        assertTrue(metrics.get("managed_heapdump_empty_files_count").equalsIgnoreCase("1"));
+        assertTrue(metrics.get("native_heapdump_empty_files_count").equalsIgnoreCase("0"));
         assertTrue(
                 metrics.get("managed_heapdump_file_1")
                         .equalsIgnoreCase(
-                                "/data/local/tmp/managed_heapdump_file_#system#bin#surfaceflinger_sample-heapdump-1.hprof"));
+                                "/data/local/tmp/"
+                                        + "EMPTY-managed_heapdump_file_"
+                                        + "#system#bin#surfaceflinger_sample-heapdump-1.hprof"));
     }
 
     @Test
@@ -83,7 +105,21 @@
         mHeapDumpHelper.setUp("/data/local/tmp/", processNames);
         assertTrue(mHeapDumpHelper.startCollecting("sample-heapdump-2"));
         Map<String, String> metrics = mHeapDumpHelper.getMetrics();
-        assertTrue(metrics.size() == 2);
+        assertTrue(metrics.size() == 4);
+        assertTrue(metrics.get("managed_heapdump_empty_files_count").equalsIgnoreCase("0"));
+        assertTrue(metrics.get("native_heapdump_empty_files_count").equalsIgnoreCase("0"));
+        assertTrue(
+                metrics.get("managed_heapdump_file_1")
+                        .equalsIgnoreCase(
+                                "/data/local/tmp/"
+                                        + "managed_heapdump_file_"
+                                        + "com.android.systemui_sample-heapdump-2.hprof"));
+        assertTrue(
+                metrics.get("managed_heapdump_file_2")
+                        .equalsIgnoreCase(
+                                "/data/local/tmp/"
+                                        + "managed_heapdump_file_"
+                                        + "system_server_sample-heapdump-2.hprof"));
     }
 
     @Test
@@ -93,11 +129,32 @@
         mHeapDumpHelper.enableNativeHeapDump();
         assertTrue(mHeapDumpHelper.startCollecting("sample-heapdump-2"));
         Map<String, String> metrics = mHeapDumpHelper.getMetrics();
-        assertTrue(metrics.size() == 4);
+        assertTrue(metrics.size() == 6);
+        assertTrue(metrics.get("managed_heapdump_empty_files_count").equalsIgnoreCase("0"));
+        assertTrue(metrics.get("native_heapdump_empty_files_count").equalsIgnoreCase("0"));
+        assertTrue(
+                metrics.get("managed_heapdump_file_1")
+                        .equalsIgnoreCase(
+                                "/data/local/tmp/"
+                                        + "managed_heapdump_file_"
+                                        + "com.android.systemui_sample-heapdump-2.hprof"));
+        assertTrue(
+                metrics.get("managed_heapdump_file_2")
+                        .equalsIgnoreCase(
+                                "/data/local/tmp/"
+                                        + "managed_heapdump_file_"
+                                        + "system_server_sample-heapdump-2.hprof"));
+        assertTrue(
+                metrics.get("native_heapdump_file_1")
+                        .equalsIgnoreCase(
+                                "/data/local/tmp/"
+                                        + "native_heapdump_file_"
+                                        + "com.android.systemui_sample-heapdump-2.txt"));
         assertTrue(
                 metrics.get("native_heapdump_file_2")
                         .equalsIgnoreCase(
-                                "/data/local/tmp/native_heapdump_file_system_server_sample-heapdump-2.txt"));
+                                "/data/local/tmp/"
+                                    + "native_heapdump_file_system_server_sample-heapdump-2.txt"));
     }
 
     @Test
@@ -105,6 +162,48 @@
         mHeapDumpHelper.setUp("/data/local/tmp/", "com.android.systemui");
         assertFalse(mHeapDumpHelper.startCollecting(""));
         Map<String, String> metrics = mHeapDumpHelper.getMetrics();
-        assertTrue(metrics.size() == 0);
+        assertTrue(metrics.size() == 2);
+        assertTrue(metrics.get("managed_heapdump_empty_files_count").equalsIgnoreCase("0"));
+        assertTrue(metrics.get("native_heapdump_empty_files_count").equalsIgnoreCase("0"));
+    }
+
+    @Test
+    public void testHeapDumpCollectionForInvalidProcesses() {
+        String[] processNames = new String[] {"systemui", "twoshay"};
+        mHeapDumpHelper.setUp("/data/local/tmp/", processNames);
+        assertTrue(mHeapDumpHelper.startCollecting("sample-heapdump-2"));
+        Map<String, String> metrics = mHeapDumpHelper.getMetrics();
+        assertTrue(metrics.size() == 3);
+        assertTrue(metrics.get("managed_heapdump_empty_files_count").equalsIgnoreCase("1"));
+        assertTrue(metrics.get("native_heapdump_empty_files_count").equalsIgnoreCase("0"));
+        assertTrue(
+                metrics.get("managed_heapdump_file_1")
+                        .equalsIgnoreCase(
+                                "/data/local/tmp/"
+                                        + "EMPTY-managed_heapdump_file_"
+                                        + "twoshay_sample-heapdump-2.hprof"));
+    }
+
+    @Test
+    public void testNativeHeapDumpCollectionForInvalidProcesses() {
+        String[] processNames = new String[] {"systemui", "twoshay"};
+        mHeapDumpHelper.setUp("/data/local/tmp/", processNames);
+        mHeapDumpHelper.enableNativeHeapDump();
+        assertTrue(mHeapDumpHelper.startCollecting("sample-heapdump-2"));
+        Map<String, String> metrics = mHeapDumpHelper.getMetrics();
+        assertTrue(metrics.size() == 4);
+        assertTrue(metrics.get("managed_heapdump_empty_files_count").equalsIgnoreCase("1"));
+        assertTrue(metrics.get("native_heapdump_empty_files_count").equalsIgnoreCase("1"));
+        assertTrue(
+                metrics.get("managed_heapdump_file_1")
+                        .equalsIgnoreCase(
+                                "/data/local/tmp/"
+                                        + "EMPTY-managed_heapdump_file_"
+                                        + "twoshay_sample-heapdump-2.hprof"));
+        assertTrue(
+                metrics.get("native_heapdump_file_1")
+                        .equalsIgnoreCase(
+                                "/data/local/tmp/"
+                                    + "EMPTY-native_heapdump_file_twoshay_sample-heapdump-2.txt"));
     }
 }
diff --git a/libraries/collectors-helper/memory/test/src/com/android/helpers/tests/ShowmapSnapshotHelperTest.java b/libraries/collectors-helper/memory/test/src/com/android/helpers/tests/ShowmapSnapshotHelperTest.java
index 76625a7..5d13bcd 100644
--- a/libraries/collectors-helper/memory/test/src/com/android/helpers/tests/ShowmapSnapshotHelperTest.java
+++ b/libraries/collectors-helper/memory/test/src/com/android/helpers/tests/ShowmapSnapshotHelperTest.java
@@ -21,8 +21,13 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.contains;
 import static org.mockito.ArgumentMatchers.matches;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.anyLong;
+import static org.mockito.Mockito.anyString;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 
@@ -71,6 +76,15 @@
     private static final String[] TWO_PROCESS_LIST = {
             "com.android.systemui", "system_server"
     };
+    private static final String[] MIXED_PROCESS_LIST = {
+        "com.android.systemui",
+        "system_server",
+        "com.google.android.googlequicksearchbox:search",
+        "com.google.android.connectivitymonitor"
+    };
+    private static final String[] CACHED_PROCESS_LIST = {
+        "com.google.android.googlequicksearchbox:search", "com.google.android.connectivitymonitor"
+    };
     private static final String[] NO_PROCESS_LIST = {
             null
     };
@@ -155,6 +169,81 @@
         testProcessList(METRIC_INDEX_STR, TWO_PROCESS_LIST);
     }
 
+    /** Test that cached processes are skipped for showmap metrics. */
+    @Test
+    public void testGetMetrics_MixedProcess() {
+        doReturn(true)
+                .when(mShowmapSnapshotHelper)
+                .isProcessOomScoreAbove(
+                        eq("com.google.android.googlequicksearchbox:search"), anyLong(), anyInt());
+        doReturn(true)
+                .when(mShowmapSnapshotHelper)
+                .isProcessOomScoreAbove(
+                        eq("com.google.android.connectivitymonitor"), anyLong(), anyInt());
+        doReturn(false)
+                .when(mShowmapSnapshotHelper)
+                .isProcessOomScoreAbove(eq("com.android.systemui"), anyLong(), anyInt());
+        doReturn(false)
+                .when(mShowmapSnapshotHelper)
+                .isProcessOomScoreAbove(eq("system_server"), anyLong(), anyInt());
+        mShowmapSnapshotHelper.setUp(VALID_OUTPUT_DIR, MIXED_PROCESS_LIST);
+        mShowmapSnapshotHelper.setMetricNameIndex(METRIC_INDEX_STR);
+        assertTrue(mShowmapSnapshotHelper.startCollecting());
+        Map<String, String> metrics = mShowmapSnapshotHelper.getMetrics();
+        assertFalse(metrics.isEmpty());
+        for (String processName : CACHED_PROCESS_LIST) {
+            assertTrue(
+                    metrics.containsKey(
+                            constructKey(
+                                    String.format(
+                                            ShowmapSnapshotHelper
+                                                    .OUTPUT_IMPERCEPTIBLE_METRIC_PATTERN,
+                                            "rss"),
+                                    processName)));
+            assertTrue(
+                    metrics.containsKey(
+                            constructKey(
+                                    String.format(
+                                            ShowmapSnapshotHelper
+                                                    .OUTPUT_IMPERCEPTIBLE_METRIC_PATTERN,
+                                            "pss"),
+                                    processName)));
+        }
+        assertTrue(metrics.containsKey(ShowmapSnapshotHelper.OUTPUT_FILE_PATH_KEY));
+    }
+
+    /** Test isProcessOomScoreAbove() from cached process only. */
+    @Test
+    public void testGetMetrics_CachedProcess() throws IOException {
+        doReturn("1000")
+                .when(mShowmapSnapshotHelper)
+                .executeShellCommand(contains("oom_score_adj"));
+        mShowmapSnapshotHelper.setUp(VALID_OUTPUT_DIR, CACHED_PROCESS_LIST);
+        mShowmapSnapshotHelper.setMetricNameIndex(METRIC_INDEX_STR);
+        assertTrue(mShowmapSnapshotHelper.startCollecting());
+        Map<String, String> metrics = mShowmapSnapshotHelper.getMetrics();
+        assertFalse(metrics.isEmpty());
+        for (String processName : CACHED_PROCESS_LIST) {
+            assertTrue(
+                    metrics.containsKey(
+                            constructKey(
+                                    String.format(
+                                            ShowmapSnapshotHelper
+                                                    .OUTPUT_IMPERCEPTIBLE_METRIC_PATTERN,
+                                            "rss"),
+                                    processName)));
+            assertTrue(
+                    metrics.containsKey(
+                            constructKey(
+                                    String.format(
+                                            ShowmapSnapshotHelper
+                                                    .OUTPUT_IMPERCEPTIBLE_METRIC_PATTERN,
+                                            "pss"),
+                                    processName)));
+        }
+        assertTrue(metrics.containsKey(ShowmapSnapshotHelper.OUTPUT_FILE_PATH_KEY));
+    }
+
     /**
      * Test all process flag return more than 2 processes metrics at least.
      */
@@ -167,7 +256,6 @@
         Map<String, String> metrics = mShowmapSnapshotHelper.getMetrics();
         assertTrue(metrics.size() > 2);
         assertTrue(metrics.containsKey(ShowmapSnapshotHelper.OUTPUT_FILE_PATH_KEY));
-
     }
 
     @Test
@@ -220,7 +308,6 @@
         mShowmapSnapshotHelper.setAllProcesses();
         assertTrue(mShowmapSnapshotHelper.startCollecting());
         Map<String, String> metrics = mShowmapSnapshotHelper.getMetrics();
-
         assertTrue(metrics.size() != 0);
 
         // process count, process with child process count and path to snapshot file in the output
@@ -235,6 +322,19 @@
         // At least one process (i.e init) will have child process
         assertTrue(parentWithChildProcessSet.size() > 0);
         assertTrue(metrics.containsKey(ShowmapSnapshotHelper.CHILD_PROCESS_COUNT_PREFIX + "_init"));
+        // These assertions are for checking SKIP_PROCESS
+        assertFalse(
+                metrics.containsKey(
+                        ShowmapSnapshotHelper.PARENT_PROCESS_STRING
+                                + "_init_"
+                                + ShowmapSnapshotHelper.CHILD_PROCESS_STRING
+                                + "_logcat"));
+        assertFalse(
+                metrics.containsKey(
+                        ShowmapSnapshotHelper.PARENT_PROCESS_STRING
+                                + "_init_"
+                                + ShowmapSnapshotHelper.CHILD_PROCESS_STRING
+                                + "_sh"));
     }
 
     @Test
@@ -274,6 +374,9 @@
     }
 
     private void testProcessList(String metricIndexStr, String... processNames) {
+        doReturn(false)
+                .when(mShowmapSnapshotHelper)
+                .isProcessOomScoreAbove(anyString(), anyLong(), anyInt());
         mShowmapSnapshotHelper.setUp(VALID_OUTPUT_DIR, processNames);
         mShowmapSnapshotHelper.setMetricNameIndex(metricIndexStr);
         assertTrue(mShowmapSnapshotHelper.startCollecting());
diff --git a/libraries/health/rules/src/android/platform/test/rule/QuickstepPressureRule.java b/libraries/health/rules/src/android/platform/test/rule/QuickstepPressureRule.java
index 804d0a7..2ff3f48 100644
--- a/libraries/health/rules/src/android/platform/test/rule/QuickstepPressureRule.java
+++ b/libraries/health/rules/src/android/platform/test/rule/QuickstepPressureRule.java
@@ -29,18 +29,22 @@
     // TODO: b/243991517 Replace static sleep with process not crashing verification.
     private static final long MIN_CRASH_WAIT_TIMEOUT = 500;
     private static final long UI_RESPONSE_TIMEOUT_MSECS = 10000;
+    @VisibleForTesting static final String PACKAGES_OPTION = "quickstep-packages";
 
-    private final String[] mPackages;
+    private String[] mPackages;
 
     public QuickstepPressureRule(String... packages) {
-        if (packages.length == 0) {
-            throw new IllegalArgumentException("Must supply an application to open.");
-        }
         mPackages = packages;
     }
 
     @Override
     protected void starting(Description description) {
+        String packageOption = getArguments().getString(PACKAGES_OPTION);
+        mPackages = packageOption == null ? mPackages : packageOption.split(",");
+        if (mPackages.length == 0) {
+            throw new IllegalArgumentException("Must supply an application to open.");
+        }
+
         // Start each app in sequence.
         for (String pkg : mPackages) {
             startActivity(pkg);
diff --git a/libraries/health/rules/tests/src/android/platform/test/rule/QuickstepPressureRuleTest.java b/libraries/health/rules/tests/src/android/platform/test/rule/QuickstepPressureRuleTest.java
index 82836dd..771f6f8 100644
--- a/libraries/health/rules/tests/src/android/platform/test/rule/QuickstepPressureRuleTest.java
+++ b/libraries/health/rules/tests/src/android/platform/test/rule/QuickstepPressureRuleTest.java
@@ -16,8 +16,11 @@
 package android.platform.test.rule;
 
 import static com.google.common.truth.Truth.assertThat;
+
 import static org.junit.Assert.fail;
 
+import android.os.Bundle;
+
 import org.junit.Test;
 import org.junit.runner.Description;
 import org.junit.runner.RunWith;
@@ -32,9 +35,11 @@
 public class QuickstepPressureRuleTest {
     /** Tests that this rule will fail to register if no apps are supplied. */
     @Test
-    public void testNoAppToOpenFails() {
+    public void testNoAppToOpenFails() throws Throwable {
         try {
-            QuickstepPressureRule rule = new QuickstepPressureRule();
+            TestableQuickstepPressureRule rule = new TestableQuickstepPressureRule();
+            rule.apply(rule.getTestStatement(), Description.createTestDescription("clzz", "mthd"))
+                    .evaluate();
             fail("An illegal argument error should have been thrown, but wasn't.");
         } catch (IllegalArgumentException e) {
             return;
@@ -67,8 +72,22 @@
                 .inOrder();
     }
 
+    /** Tests that this rule will open one app from option before the test, if supplied. */
+    @Test
+    public void testOneAppToOpenFromOption() throws Throwable {
+        Bundle PackageOption = new Bundle();
+        PackageOption.putString(QuickstepPressureRule.PACKAGES_OPTION, "option.package.name");
+        TestableQuickstepPressureRule rule = new TestableQuickstepPressureRule(PackageOption);
+        rule.apply(rule.getTestStatement(), Description.createTestDescription("clzz", "mthd"))
+                .evaluate();
+        assertThat(rule.getOperations())
+                .containsExactly("start option.package.name", "test")
+                .inOrder();
+    }
+
     private static class TestableQuickstepPressureRule extends QuickstepPressureRule {
         private List<String> mOperations = new ArrayList<>();
+        private Bundle mBundle = new Bundle();
 
         public TestableQuickstepPressureRule(String app) {
             super(app);
@@ -78,11 +97,20 @@
             super(apps);
         }
 
+        public TestableQuickstepPressureRule(Bundle bundle) {
+            mBundle = bundle;
+        }
+
         @Override
         void startActivity(String pkg) {
             mOperations.add(String.format("start %s", pkg));
         }
 
+        @Override
+        protected Bundle getArguments() {
+            return mBundle;
+        }
+
         public List<String> getOperations() {
             return mOperations;
         }
diff --git a/libraries/power-helper/Android.bp b/libraries/power-helper/Android.bp
index 230791b..80bbfbe 100644
--- a/libraries/power-helper/Android.bp
+++ b/libraries/power-helper/Android.bp
@@ -27,7 +27,7 @@
 
     libs: [
         "android.test.runner.stubs",
-        "ub-uiautomator",
+        "androidx.test.uiautomator_uiautomator",
         "android.test.base.stubs",
     ],
     static_libs: ["junit"],
diff --git a/libraries/power-helper/sample/Android.bp b/libraries/power-helper/sample/Android.bp
index f55a852..56dfcc9 100644
--- a/libraries/power-helper/sample/Android.bp
+++ b/libraries/power-helper/sample/Android.bp
@@ -9,7 +9,7 @@
     static_libs: [
         "PowerTestHelper-src",
         "android.test.runner.stubs",
-        "ub-uiautomator",
+        "androidx.test.uiautomator_uiautomator",
         "android.test.base.stubs",
         "junit",
     ],
diff --git a/libraries/power-helper/src/com/android/helper/PowerTestHelper.java b/libraries/power-helper/src/com/android/helper/PowerTestHelper.java
index f290205..266f8f0 100644
--- a/libraries/power-helper/src/com/android/helper/PowerTestHelper.java
+++ b/libraries/power-helper/src/com/android/helper/PowerTestHelper.java
@@ -16,20 +16,19 @@
 
 package com.android.helper;
 
+import android.os.Bundle;
+import android.os.Environment;
+import android.os.SystemClock;
+import android.test.InstrumentationTestRunner;
+
+import androidx.test.uiautomator.UiAutomatorTestCase;
+
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.FileWriter;
 import java.io.IOException;
-import java.util.List;
 import java.util.Properties;
-import android.os.Bundle;
-import android.os.Environment;
-
-import android.os.SystemClock;
-import android.support.test.uiautomator.UiAutomatorTestCase;
-import android.test.InstrumentationTestRunner;
-import android.util.Log;
 
 public class PowerTestHelper extends UiAutomatorTestCase {
     private final static String PARAM_CONFIG = "conf";
diff --git a/libraries/screenshot/src/main/java/platform/test/screenshot/GoldenImagePathManager.kt b/libraries/screenshot/src/main/java/platform/test/screenshot/GoldenImagePathManager.kt
index 22a45bd..27ecd2e 100644
--- a/libraries/screenshot/src/main/java/platform/test/screenshot/GoldenImagePathManager.kt
+++ b/libraries/screenshot/src/main/java/platform/test/screenshot/GoldenImagePathManager.kt
@@ -59,10 +59,17 @@
 open class GoldenImagePathManager @JvmOverloads constructor(
     open val appContext: Context,
     open val assetsPathRelativeToBuildRoot: String = "assets",
-    open val deviceLocalPath: String = getDeviceOutputDirectory(appContext),
+    open var deviceLocalPath: String = getDeviceOutputDirectory(appContext),
     open val pathConfig: PathConfig = getSimplePathConfig()
 ) {
 
+    init {
+        val robolectricOverride = System.getProperty("robolectric.artifacts.dir")
+        if (Build.FINGERPRINT.contains("robolectric") && !robolectricOverride.isNullOrEmpty()) {
+            deviceLocalPath = robolectricOverride
+        }
+    }
+
     public val imageExtension = "png"
 
     /*
diff --git a/robolab/roboStandaloneProj/tests/Android.bp b/robolab/roboStandaloneProj/tests/Android.bp
index 3e5bf10..cc57001 100644
--- a/robolab/roboStandaloneProj/tests/Android.bp
+++ b/robolab/roboStandaloneProj/tests/Android.bp
@@ -36,6 +36,4 @@
     instrumentation_for: "MyRoboApplication",
 
     upstream: true,
-
-    test_suites: ["general-tests"],
 }
diff --git a/tests/perf/PowerPerfTest/Android.bp b/tests/perf/PowerPerfTest/Android.bp
index bdf537b..3a159b9 100644
--- a/tests/perf/PowerPerfTest/Android.bp
+++ b/tests/perf/PowerPerfTest/Android.bp
@@ -21,7 +21,7 @@
     sdk_version: "current",
     static_libs: [
         "PowerTestHelper-src",
-        "ub-uiautomator",
+        "androidx.test.uiautomator_uiautomator",
         "junit",
     ],
     libs: ["android.test.base.stubs"],