Merge changes I0fabcbb4,I214f172d,I3529c2cb

* changes:
  Remove kernel commandline arguments fom bootimg rule
  Update kernel to builds 9047506
  Update kernel to builds 9047506
diff --git a/apex/Android.bp b/apex/Android.bp
index a9fad55..fade6c5 100644
--- a/apex/Android.bp
+++ b/apex/Android.bp
@@ -102,11 +102,7 @@
         "sign_virt_apex.py",
     ],
     version: {
-        py2: {
-            enabled: false,
-        },
         py3: {
-            enabled: true,
             embedded_launcher: true,
         },
     },
@@ -170,11 +166,7 @@
         "replace_bytes.py",
     ],
     version: {
-        py2: {
-            enabled: false,
-        },
         py3: {
-            enabled: true,
             embedded_launcher: true,
         },
     },
diff --git a/authfs/fd_server/src/main.rs b/authfs/fd_server/src/main.rs
index 5b7a4f4..93a788b 100644
--- a/authfs/fd_server/src/main.rs
+++ b/authfs/fd_server/src/main.rs
@@ -26,12 +26,13 @@
 mod fsverity;
 
 use anyhow::{bail, Result};
+use clap::Parser;
 use log::debug;
 use nix::sys::stat::{umask, Mode};
 use rpcbinder::run_rpc_server;
 use std::collections::BTreeMap;
 use std::fs::File;
-use std::os::unix::io::FromRawFd;
+use std::os::unix::io::{FromRawFd, OwnedFd};
 
 use aidl::{FdConfig, FdService};
 use authfs_fsverity_metadata::parse_fsverity_metadata;
@@ -72,86 +73,52 @@
     ))
 }
 
-fn parse_arg_rw_fds(arg: &str) -> Result<(i32, FdConfig)> {
-    let fd = arg.parse::<i32>()?;
-    let file = fd_to_owned::<File>(fd)?;
-    if file.metadata()?.len() > 0 {
-        bail!("File is expected to be empty");
-    }
-    Ok((fd, FdConfig::ReadWrite(file)))
-}
-
-fn parse_arg_ro_dirs(arg: &str) -> Result<(i32, FdConfig)> {
-    let fd = arg.parse::<i32>()?;
-    Ok((fd, FdConfig::InputDir(fd_to_owned(fd)?)))
-}
-
-fn parse_arg_rw_dirs(arg: &str) -> Result<(i32, FdConfig)> {
-    let fd = arg.parse::<i32>()?;
-    Ok((fd, FdConfig::OutputDir(fd_to_owned(fd)?)))
-}
-
+#[derive(Parser)]
 struct Args {
-    fd_pool: BTreeMap<i32, FdConfig>,
-    ready_fd: Option<File>,
+    /// Read-only FD of file, with optional FD of corresponding .fsv_meta, joined with a ':'.
+    /// Example: "1:2", "3".
+    #[clap(long)]
+    ro_fds: Vec<String>,
+
+    /// Read-writable FD of file
+    #[clap(long)]
+    rw_fds: Vec<i32>,
+
+    /// Read-only FD of directory
+    #[clap(long)]
+    ro_dirs: Vec<i32>,
+
+    /// Read-writable FD of directory
+    #[clap(long)]
+    rw_dirs: Vec<i32>,
+
+    /// A pipe FD for signaling the other end once ready
+    #[clap(long)]
+    ready_fd: Option<i32>,
 }
 
-fn parse_args() -> Result<Args> {
-    #[rustfmt::skip]
-    let matches = clap::App::new("fd_server")
-        .arg(clap::Arg::with_name("ro-fds")
-             .long("ro-fds")
-             .multiple(true)
-             .number_of_values(1))
-        .arg(clap::Arg::with_name("rw-fds")
-             .long("rw-fds")
-             .multiple(true)
-             .number_of_values(1))
-        .arg(clap::Arg::with_name("ro-dirs")
-             .long("ro-dirs")
-             .multiple(true)
-             .number_of_values(1))
-        .arg(clap::Arg::with_name("rw-dirs")
-             .long("rw-dirs")
-             .multiple(true)
-             .number_of_values(1))
-        .arg(clap::Arg::with_name("ready-fd")
-            .long("ready-fd")
-            .takes_value(true))
-        .get_matches();
-
+/// Convert argument strings and integers to a form that is easier to use and handles ownership.
+fn convert_args(args: Args) -> Result<(BTreeMap<i32, FdConfig>, Option<OwnedFd>)> {
     let mut fd_pool = BTreeMap::new();
-    if let Some(args) = matches.values_of("ro-fds") {
-        for arg in args {
-            let (fd, config) = parse_arg_ro_fds(arg)?;
-            fd_pool.insert(fd, config);
-        }
+    for arg in args.ro_fds {
+        let (fd, config) = parse_arg_ro_fds(&arg)?;
+        fd_pool.insert(fd, config);
     }
-    if let Some(args) = matches.values_of("rw-fds") {
-        for arg in args {
-            let (fd, config) = parse_arg_rw_fds(arg)?;
-            fd_pool.insert(fd, config);
+    for fd in args.rw_fds {
+        let file = fd_to_owned::<File>(fd)?;
+        if file.metadata()?.len() > 0 {
+            bail!("File is expected to be empty");
         }
+        fd_pool.insert(fd, FdConfig::ReadWrite(file));
     }
-    if let Some(args) = matches.values_of("ro-dirs") {
-        for arg in args {
-            let (fd, config) = parse_arg_ro_dirs(arg)?;
-            fd_pool.insert(fd, config);
-        }
+    for fd in args.ro_dirs {
+        fd_pool.insert(fd, FdConfig::InputDir(fd_to_owned(fd)?));
     }
-    if let Some(args) = matches.values_of("rw-dirs") {
-        for arg in args {
-            let (fd, config) = parse_arg_rw_dirs(arg)?;
-            fd_pool.insert(fd, config);
-        }
+    for fd in args.rw_dirs {
+        fd_pool.insert(fd, FdConfig::OutputDir(fd_to_owned(fd)?));
     }
-    let ready_fd = if let Some(arg) = matches.value_of("ready-fd") {
-        let fd = arg.parse::<i32>()?;
-        Some(fd_to_owned(fd)?)
-    } else {
-        None
-    };
-    Ok(Args { fd_pool, ready_fd })
+    let ready_fd = args.ready_fd.map(fd_to_owned).transpose()?;
+    Ok((fd_pool, ready_fd))
 }
 
 fn main() -> Result<()> {
@@ -159,7 +126,8 @@
         android_logger::Config::default().with_tag("fd_server").with_min_level(log::Level::Debug),
     );
 
-    let args = parse_args()?;
+    let args = Args::parse();
+    let (fd_pool, mut ready_fd) = convert_args(args)?;
 
     // Allow open/create/mkdir from authfs to create with expecting mode. It's possible to still
     // use a custom mask on creation, then report the actual file mode back to authfs. But there
@@ -167,9 +135,8 @@
     let old_umask = umask(Mode::empty());
     debug!("Setting umask to 0 (old: {:03o})", old_umask.bits());
 
-    let service = FdService::new_binder(args.fd_pool).as_binder();
+    let service = FdService::new_binder(fd_pool).as_binder();
     debug!("fd_server is starting as a rpc service.");
-    let mut ready_fd = args.ready_fd;
     let retval = run_rpc_server(service, RPC_SERVICE_PORT, || {
         debug!("fd_server is ready");
         // Close the ready-fd if we were given one to signal our readiness.
diff --git a/avmd/Android.bp b/avmd/Android.bp
index dc6a896..6d91b59 100644
--- a/avmd/Android.bp
+++ b/avmd/Android.bp
@@ -11,6 +11,7 @@
     rustlibs: [
         "libserde",
         "libapexutil_rust", // TODO(b/239413416): Remove this after adding hex
+        "libapkverify",
     ],
 }
 
@@ -40,6 +41,9 @@
     name: "avmdtool_tests",
     srcs: ["tests/*_test.rs"],
     test_suites: ["general-tests"],
+    rustlibs: [
+        "libtempfile",
+    ],
     compile_multilib: "first",
     data_bins: ["avmdtool"],
     data: ["tests/data/*"],
diff --git a/avmd/src/avmd.rs b/avmd/src/avmd.rs
index 50cdfdf..05fc201 100644
--- a/avmd/src/avmd.rs
+++ b/avmd/src/avmd.rs
@@ -19,6 +19,7 @@
     vec::Vec,
 };
 use apexutil::to_hex_string;
+use apkverify::SignatureAlgorithmID;
 use core::fmt;
 use serde::{Deserialize, Serialize};
 
@@ -120,7 +121,7 @@
     /// It should be one of the algorithms in the [list][].
     ///
     /// [list]: https://source.android.com/security/apksigning/v2#signature-algorithm-ids
-    pub signature_algorithm_id: u32,
+    pub signature_algorithm_id: SignatureAlgorithmID,
     /// Digest of the APK's v3 signing block. TODO: fix
     pub apk_digest: Vec<u8>,
 }
@@ -130,7 +131,7 @@
         writeln!(f, "  APK descriptor:")?;
         writeln!(f, "    namespace:             {}", self.resource.namespace)?;
         writeln!(f, "    name:                  {}", self.resource.name)?;
-        writeln!(f, "    Signing algorithm ID:  {:#x}", self.signature_algorithm_id)?;
+        writeln!(f, "    Signing algorithm ID:  {:#04x}", self.signature_algorithm_id.to_u32())?;
         writeln!(f, "    APK digest:            {}", to_hex_string(&self.apk_digest))?;
         Ok(())
     }
diff --git a/avmd/tests/avmdtool_test.rs b/avmd/tests/avmdtool_test.rs
index d93cb6f..4647f06 100644
--- a/avmd/tests/avmdtool_test.rs
+++ b/avmd/tests/avmdtool_test.rs
@@ -16,19 +16,50 @@
 
 use std::fs;
 use std::process::Command;
+use tempfile::TempDir;
 
 #[test]
 fn test_dump() {
-    // test.avmd is generated with
-    // ```
-    // avmdtool create /tmp/test.amvd \
-    // --apex-payload microdroid vbmeta ./libs/apexutil/tests/data/test.apex \
-    // --apk microdroid_manager apk \
-    // ./libs/apkverify/tests/data/v3-only-with-rsa-pkcs1-sha256-4096.apk \
-    // --apk microdroid_manager extra-apk ./libs/apkverify/tests/data/v3-only-with-stamp.apk
-    //```
-    let output =
-        Command::new("./avmdtool").args(["dump", "tests/data/test.avmd"]).output().unwrap();
+    let filename = "tests/data/test.avmd";
+    assert!(
+        fs::metadata(filename).is_ok(),
+        "File '{}' does not exist. You can re-create it with:
+    avmdtool create {} \\
+    --apex-payload microdroid vbmeta tests/data/test.apex \\
+    --apk microdroid_manager apk \\
+    tests/data/v3-only-with-rsa-pkcs1-sha256-4096.apk \\
+    --apk microdroid_manager extra-apk tests/data/v3-only-with-stamp.apk",
+        filename,
+        filename
+    );
+    let output = Command::new("./avmdtool").args(["dump", filename]).output().unwrap();
     assert!(output.status.success());
     assert_eq!(output.stdout, fs::read("tests/data/test.avmd.dump").unwrap());
 }
+
+#[test]
+fn test_create() {
+    let test_dir = TempDir::new().unwrap();
+    let test_file_path = test_dir.path().join("tmp_test.amvd");
+    let output = Command::new("./avmdtool")
+        .args([
+            "create",
+            test_file_path.to_str().unwrap(),
+            "--apex-payload",
+            "microdroid",
+            "vbmeta",
+            "tests/data/test.apex",
+            "--apk",
+            "microdroid_manager",
+            "apk",
+            "tests/data/v3-only-with-rsa-pkcs1-sha256-4096.apk",
+            "--apk",
+            "microdroid_manager",
+            "extra-apk",
+            "tests/data/v3-only-with-stamp.apk",
+        ])
+        .output()
+        .unwrap();
+    assert!(output.status.success());
+    assert_eq!(fs::read(test_file_path).unwrap(), fs::read("tests/data/test.avmd").unwrap());
+}
diff --git a/avmd/tests/data/test.apex b/avmd/tests/data/test.apex
new file mode 100644
index 0000000..fd79365
--- /dev/null
+++ b/avmd/tests/data/test.apex
Binary files differ
diff --git a/avmd/tests/data/test.avmd b/avmd/tests/data/test.avmd
index 52e634f..e567125 100644
--- a/avmd/tests/data/test.avmd
+++ b/avmd/tests/data/test.avmd
Binary files differ
diff --git a/avmd/tests/data/v3-only-with-rsa-pkcs1-sha256-4096.apk b/avmd/tests/data/v3-only-with-rsa-pkcs1-sha256-4096.apk
new file mode 100644
index 0000000..0c9391c
--- /dev/null
+++ b/avmd/tests/data/v3-only-with-rsa-pkcs1-sha256-4096.apk
Binary files differ
diff --git a/avmd/tests/data/v3-only-with-stamp.apk b/avmd/tests/data/v3-only-with-stamp.apk
new file mode 100644
index 0000000..5f65214
--- /dev/null
+++ b/avmd/tests/data/v3-only-with-stamp.apk
Binary files differ
diff --git a/compos/benchmark/Android.bp b/compos/benchmark/Android.bp
index 37af87e..3d46a39 100644
--- a/compos/benchmark/Android.bp
+++ b/compos/benchmark/Android.bp
@@ -12,6 +12,7 @@
         "androidx.test.runner",
         "androidx.test.ext.junit",
         "MicrodroidDeviceTestHelper",
+        "MicrodroidTestHelper",
         "truth-prebuilt",
     ],
     platform_apis: true,
diff --git a/compos/benchmark/src/java/com/android/compos/benchmark/ComposBenchmark.java b/compos/benchmark/src/java/com/android/compos/benchmark/ComposBenchmark.java
index 21b2ecd..996d32a 100644
--- a/compos/benchmark/src/java/com/android/compos/benchmark/ComposBenchmark.java
+++ b/compos/benchmark/src/java/com/android/compos/benchmark/ComposBenchmark.java
@@ -26,8 +26,11 @@
 import android.os.ParcelFileDescriptor;
 import android.util.Log;
 
+import com.android.microdroid.test.common.MetricsProcessor;
+import com.android.microdroid.test.common.ProcessUtil;
 import com.android.microdroid.test.device.MicrodroidDeviceTestBase;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -40,11 +43,15 @@
 import java.text.DateFormat;
 import java.text.ParseException;
 import java.text.SimpleDateFormat;
+import java.util.ArrayList;
 import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
-
 @RunWith(JUnit4.class)
 public class ComposBenchmark extends MicrodroidDeviceTestBase {
     private static final String TAG = "ComposBenchmark";
@@ -53,100 +60,106 @@
     private static final double NANOS_IN_SEC = 1_000_000_000.0;
     private static final String METRIC_PREFIX = "avf_perf/compos/";
 
+    private final MetricsProcessor mMetricsProcessor = new MetricsProcessor(METRIC_PREFIX);
+
     private Instrumentation mInstrumentation;
 
     @Before
     public void setup() {
         mInstrumentation = getInstrumentation();
+        mInstrumentation.getUiAutomation().adoptShellPermissionIdentity();
     }
 
-    private void reportMetric(String name, String unit, double[] values) {
-        double sum = 0;
-        double squareSum = 0;
-        double min = Double.MAX_VALUE;
-        double max = Double.MIN_VALUE;
-
-        for (double val : values) {
-            sum += val;
-            squareSum += val * val;
-            min = val < min ? val : min;
-            max = val > max ? val : max;
-        }
-
-        double average = sum / values.length;
-        double variance = squareSum / values.length - average * average;
-        double stdev = Math.sqrt(variance);
-
-        Bundle bundle = new Bundle();
-        bundle.putDouble(METRIC_PREFIX + name + "_average_" + unit, average);
-        bundle.putDouble(METRIC_PREFIX + name + "_min_" + unit, min);
-        bundle.putDouble(METRIC_PREFIX + name + "_max_" + unit, max);
-        bundle.putDouble(METRIC_PREFIX + name + "_stdev_" + unit, stdev);
-        mInstrumentation.sendStatus(0, bundle);
-    }
-
-    public byte[] executeCommandBlocking(String command) {
-        try (
-            InputStream is = new ParcelFileDescriptor.AutoCloseInputStream(
-                getInstrumentation().getUiAutomation().executeShellCommand(command));
-            ByteArrayOutputStream out = new ByteArrayOutputStream()
-        ) {
-            byte[] buf = new byte[BUFFER_SIZE];
-            int length;
-            while ((length = is.read(buf)) >= 0) {
-                out.write(buf, 0, length);
-            }
-            return out.toByteArray();
-        } catch (IOException e) {
-            Log.e(TAG, "Error executing: " + command, e);
-            return null;
-        }
-    }
-
-    public String executeCommand(String command)
-            throws  InterruptedException, IOException {
-
-        getInstrumentation().getUiAutomation()
-                .adoptShellPermissionIdentity();
-        byte[] output = executeCommandBlocking(command);
-        getInstrumentation().getUiAutomation()
-                .dropShellPermissionIdentity();
-
-        if (output == null) {
-            throw new RuntimeException("Failed to run the command.");
-        } else {
-            String stdout = new String(output, "UTF-8");
-            Log.i(TAG, "Get stdout : " + stdout);
-            return stdout;
-        }
+    @After
+    public void tearDown() throws Exception {
+        mInstrumentation.getUiAutomation().dropShellPermissionIdentity();
     }
 
     @Test
-    public void testGuestCompileTime() throws InterruptedException, IOException {
-        assume().withMessage("Skip on CF; too slow").that(isCuttlefish()).isFalse();
+    public void testHostCompileTime() throws Exception {
+        final String command = "/apex/com.android.art/bin/odrefresh --force-compile";
 
-        final String command = "/apex/com.android.compos/bin/composd_cmd test-compile";
-
-        double[] compileTime = new double[ROUND_COUNT];
+        final List<Double> compileTimes = new ArrayList<>(ROUND_COUNT);
+        // The mapping is <memory metrics name> -> <all rounds value list>.
+        // EX : pss -> [10, 20, 30, ........]
+        final Map<String, List<Long>> processMemory = new HashMap<>();
 
         for (int round = 0; round < ROUND_COUNT; ++round) {
+
+            GetMetricsRunnable getMetricsRunnable =
+                    new GetMetricsRunnable("dex2oat64", processMemory);
+            Thread threadGetMetrics = new Thread(getMetricsRunnable);
+
+            threadGetMetrics.start();
+
+            Timestamp beforeCompileLatestTime = getLatestDex2oatSuccessTime();
+            Long compileStartTime = System.nanoTime();
+            executeCommand(command);
+            Long compileEndTime = System.nanoTime();
+            Timestamp afterCompileLatestTime = getLatestDex2oatSuccessTime();
+
+            assertTrue(afterCompileLatestTime != null);
+            assertTrue(
+                    beforeCompileLatestTime == null
+                            || beforeCompileLatestTime.before(afterCompileLatestTime));
+
+            double elapsedSec = (compileEndTime - compileStartTime) / NANOS_IN_SEC;
+            Log.i(TAG, "Compile time in host took " + elapsedSec + "s");
+            getMetricsRunnable.stop();
+
+            Log.i(TAG, "Waits for thread finish");
+            threadGetMetrics.join();
+            Log.i(TAG, "Thread is finish");
+
+            compileTimes.add(elapsedSec);
+        }
+
+        reportMetric("host_compile_time", "s", compileTimes);
+
+        reportAggregatedMetric(processMemory, "host_compile_dex2oat64_", "kB");
+    }
+
+    @Test
+    public void testGuestCompileTime() throws Exception {
+        assume().withMessage("Skip on CF; too slow").that(isCuttlefish()).isFalse();
+        final String command = "/apex/com.android.compos/bin/composd_cmd test-compile";
+
+        final List<Double> compileTimes = new ArrayList<>(ROUND_COUNT);
+        // The mapping is <memory metrics name> -> <all rounds value list>.
+        // EX : pss -> [10, 20, 30, ........]
+        final Map<String, List<Long>> processMemory = new HashMap<>();
+
+        for (int round = 0; round < ROUND_COUNT; ++round) {
+
+            GetMetricsRunnable getMetricsRunnable = new GetMetricsRunnable("crosvm", processMemory);
+            Thread threadGetMetrics = new Thread(getMetricsRunnable);
+
+            threadGetMetrics.start();
+
             Long compileStartTime = System.nanoTime();
             String output = executeCommand(command);
             Long compileEndTime = System.nanoTime();
-
             Pattern pattern = Pattern.compile("All Ok");
             Matcher matcher = pattern.matcher(output);
             assertTrue(matcher.find());
+            double elapsedSec = (compileEndTime - compileStartTime) / NANOS_IN_SEC;
+            Log.i(TAG, "Compile time in guest took " + elapsedSec + "s");
+            getMetricsRunnable.stop();
 
-            compileTime[round] = (compileEndTime - compileStartTime) / NANOS_IN_SEC;
+            Log.i(TAG, "Waits for thread finish");
+            threadGetMetrics.join();
+            Log.i(TAG, "Thread is finish");
+
+            compileTimes.add(elapsedSec);
         }
 
-        reportMetric("guest_compile_time", "s", compileTime);
+        reportMetric("guest_compile_time", "s", compileTimes);
+
+        reportAggregatedMetric(processMemory, "guest_compile_crosvm_", "kB");
     }
 
     private Timestamp getLatestDex2oatSuccessTime()
-            throws  InterruptedException, IOException, ParseException {
-
+            throws InterruptedException, IOException, ParseException {
         final String command = "logcat -d -e dex2oat";
         String output = executeCommand(command);
         String latestTime = null;
@@ -170,29 +183,106 @@
         return timeStampDate;
     }
 
-    @Test
-    public void testHostCompileTime()
-            throws InterruptedException, IOException, ParseException {
-
-        final String command = "/apex/com.android.art/bin/odrefresh --force-compile";
-
-        double[] compileTime = new double[ROUND_COUNT];
-
-        for (int round = 0; round < ROUND_COUNT; ++round) {
-            Timestamp beforeCompileLatestTime = getLatestDex2oatSuccessTime();
-            Long compileStartTime = System.nanoTime();
-            String output = executeCommand(command);
-            Long compileEndTime = System.nanoTime();
-            Timestamp afterCompileLatestTime = getLatestDex2oatSuccessTime();
-
-            assertTrue(afterCompileLatestTime != null);
-            assertTrue(beforeCompileLatestTime == null
-                    || beforeCompileLatestTime.before(afterCompileLatestTime));
-
-            compileTime[round] = (compileEndTime - compileStartTime) / NANOS_IN_SEC;
+    private void reportMetric(String name, String unit, List<? extends Number> values) {
+        Log.d(TAG, "Report metric " + name + "(" + unit + ") : " + values.toString());
+        Map<String, Double> stats = mMetricsProcessor.computeStats(values, name, unit);
+        Bundle bundle = new Bundle();
+        for (Map.Entry<String, Double> entry : stats.entrySet()) {
+            bundle.putDouble(entry.getKey(), entry.getValue());
         }
-
-        reportMetric("host_compile_time", "s", compileTime);
+        mInstrumentation.sendStatus(0, bundle);
     }
 
+    private void reportAggregatedMetric(
+            Map<String, List<Long>> processMemory, String prefix, String unit) {
+        processMemory.forEach((k, v) -> reportMetric(prefix + k, unit, v));
+    }
+
+    private byte[] executeCommandBlocking(String command) {
+        try (InputStream is =
+                        new ParcelFileDescriptor.AutoCloseInputStream(
+                                mInstrumentation.getUiAutomation().executeShellCommand(command));
+                ByteArrayOutputStream out = new ByteArrayOutputStream()) {
+            byte[] buf = new byte[BUFFER_SIZE];
+            int length;
+            while ((length = is.read(buf)) >= 0) {
+                out.write(buf, 0, length);
+            }
+            return out.toByteArray();
+        } catch (IOException e) {
+            Log.e(TAG, "Error executing: " + command, e);
+            return null;
+        }
+    }
+
+    private String executeCommand(String command) {
+        try {
+            byte[] output = executeCommandBlocking(command);
+
+            if (output == null) {
+                throw new RuntimeException("Failed to run the command.");
+            } else {
+                String stdout = new String(output, "UTF-8");
+                Log.i(TAG, "Get stdout : " + stdout);
+                return stdout;
+            }
+        } catch (Exception e) {
+            throw new RuntimeException("Error executing: " + command + " , Exception: " + e);
+        }
+    }
+
+    private class GetMetricsRunnable implements Runnable {
+        private final String mProcessName;
+        private Map<String, List<Long>> mProcessMemory;
+        private AtomicBoolean mStop = new AtomicBoolean(false);
+
+        GetMetricsRunnable(String processName, Map<String, List<Long>> processMemory) {
+            this.mProcessName = processName;
+            this.mProcessMemory = processMemory;
+        }
+
+        void stop() {
+            mStop.set(true);
+        }
+
+        public void run() {
+            while (!mStop.get()) {
+                try {
+                    updateProcessMemory(mProcessName, mProcessMemory);
+                    Thread.sleep(1000);
+                } catch (InterruptedException e) {
+                    Thread.currentThread().interrupt();
+                    return;
+                } catch (Exception e) {
+                    Log.e(TAG, "Get exception : " + e);
+                    throw new RuntimeException(e);
+                }
+            }
+        }
+    }
+
+    private void updateProcessMemory(String processName, Map<String, List<Long>> processMemory)
+            throws Exception {
+        for (Map.Entry<Integer, String> process :
+                ProcessUtil.getProcessMap(this::executeCommand).entrySet()) {
+            int pId = process.getKey();
+            String pName = process.getValue();
+            if (pName.equalsIgnoreCase(processName)) {
+                for (Map.Entry<String, Long> stat :
+                        ProcessUtil.getProcessSmapsRollup(pId, this::executeCommand).entrySet()) {
+                    Log.i(
+                            TAG,
+                            "Get running process "
+                                    + pName
+                                    + " metrics : "
+                                    + stat.getKey().toLowerCase()
+                                    + '-'
+                                    + stat.getValue());
+                    processMemory
+                            .computeIfAbsent(stat.getKey().toLowerCase(), k -> new ArrayList<>())
+                            .add(stat.getValue());
+                }
+            }
+        }
+    }
 }
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachine.java b/javalib/src/android/system/virtualmachine/VirtualMachine.java
index 828ac9f..ae84c08 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachine.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachine.java
@@ -45,11 +45,15 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
+import java.lang.ref.WeakReference;
 import java.nio.file.FileAlreadyExistsException;
 import java.nio.file.Files;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.Optional;
+import java.util.WeakHashMap;
 import java.util.concurrent.Executor;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
@@ -65,6 +69,11 @@
  * @hide
  */
 public class VirtualMachine {
+    private static final Map<Context, Map<String, WeakReference<VirtualMachine>>> sInstances =
+            new WeakHashMap<>();
+
+    private static final Object sInstancesLock = new Object();
+
     /** Name of the directory under the files directory where all VMs created for the app exist. */
     private static final String VM_DIR = "vm";
 
@@ -159,6 +168,8 @@
 
     private final ExecutorService mExecutorService = Executors.newCachedThreadPool();
 
+    @NonNull private final Context mContext;
+
     static {
         System.loadLibrary("virtualmachine_jni");
     }
@@ -166,6 +177,7 @@
     private VirtualMachine(
             @NonNull Context context, @NonNull String name, @NonNull VirtualMachineConfig config)
             throws VirtualMachineException {
+        mContext = context;
         mPackageName = context.getPackageName();
         mName = name;
         mConfig = config;
@@ -231,6 +243,18 @@
             throw new VirtualMachineException("failed to create instance partition", e);
         }
 
+        synchronized (sInstancesLock) {
+            Map<String, WeakReference<VirtualMachine>> instancesMap;
+            if (sInstances.containsKey(context)) {
+                instancesMap = sInstances.get(context);
+            } else {
+                instancesMap = new HashMap<>();
+                sInstances.put(context, instancesMap);
+            }
+
+            instancesMap.put(name, new WeakReference<>(vm));
+        }
+
         return vm;
     }
 
@@ -249,7 +273,23 @@
             throw new VirtualMachineException(e);
         }
 
-        VirtualMachine vm = new VirtualMachine(context, name, config);
+        VirtualMachine vm;
+        synchronized (sInstancesLock) {
+            Map<String, WeakReference<VirtualMachine>> instancesMap;
+            if (sInstances.containsKey(context)) {
+                instancesMap = sInstances.get(context);
+            } else {
+                instancesMap = new HashMap<>();
+                sInstances.put(context, instancesMap);
+            }
+
+            if (instancesMap.containsKey(name)) {
+                vm = instancesMap.get(name).get();
+            } else {
+                vm = new VirtualMachine(context, name, config);
+                instancesMap.put(name, new WeakReference<>(vm));
+            }
+        }
 
         // If config file exists, but the instance image file doesn't, it means that the VM is
         // corrupted. That's different from the case that the VM doesn't exist. Throw an exception
@@ -544,6 +584,11 @@
         mInstanceFilePath.delete();
         mIdsigFilePath.delete();
         vmRootDir.delete();
+
+        synchronized (sInstancesLock) {
+            Map<String, WeakReference<VirtualMachine>> instancesMap = sInstances.get(mContext);
+            if (instancesMap != null) instancesMap.remove(mName);
+        }
     }
 
     /**
diff --git a/libs/apkverify/Android.bp b/libs/apkverify/Android.bp
index 9bb8f8e..78192d2 100644
--- a/libs/apkverify/Android.bp
+++ b/libs/apkverify/Android.bp
@@ -15,6 +15,7 @@
         "liblog_rust",
         "libnum_traits",
         "libopenssl",
+        "libserde",
         "libzip",
     ],
     proc_macros: ["libnum_derive"],
diff --git a/libs/apkverify/src/algorithms.rs b/libs/apkverify/src/algorithms.rs
index f15f0a4..a1cf368 100644
--- a/libs/apkverify/src/algorithms.rs
+++ b/libs/apkverify/src/algorithms.rs
@@ -16,51 +16,153 @@
 
 //! Algorithms used for APK Signature Scheme.
 
-use anyhow::{bail, Result};
-use num_derive::FromPrimitive;
+use anyhow::{ensure, Result};
+use bytes::{Buf, Bytes};
+use num_derive::{FromPrimitive, ToPrimitive};
+use num_traits::{FromPrimitive, ToPrimitive};
 use openssl::hash::MessageDigest;
-use std::cmp::Ordering;
+use openssl::pkey::{self, PKey};
+use openssl::rsa::Padding;
+use openssl::sign::Verifier;
+use serde::{Deserialize, Serialize};
+
+use crate::bytes_ext::ReadFromBytes;
 
 /// [Signature Algorithm IDs]: https://source.android.com/docs/security/apksigning/v2#signature-algorithm-ids
+/// [SignatureAlgorithm.java]: (tools/apksig/src/main/java/com/android/apksig/internal/apk/SignatureAlgorithm.java)
 ///
 /// Some of the algorithms are not implemented. See b/197052981.
-#[derive(Clone, Debug, Eq, FromPrimitive)]
+#[derive(Serialize, Deserialize, Clone, Copy, Debug, Eq, PartialEq, FromPrimitive, ToPrimitive)]
 #[repr(u32)]
 pub enum SignatureAlgorithmID {
+    /// RSASSA-PSS with SHA2-256 digest, SHA2-256 MGF1, 32 bytes of salt, trailer: 0xbc, content
+    /// digested using SHA2-256 in 1 MB chunks.
     RsaPssWithSha256 = 0x0101,
+
+    /// RSASSA-PSS with SHA2-512 digest, SHA2-512 MGF1, 64 bytes of salt, trailer: 0xbc, content
+    /// digested using SHA2-512 in 1 MB chunks.
     RsaPssWithSha512 = 0x0102,
+
+    /// RSASSA-PKCS1-v1_5 with SHA2-256 digest, content digested using SHA2-256 in 1 MB chunks.
     RsaPkcs1V15WithSha256 = 0x0103,
+
+    /// RSASSA-PKCS1-v1_5 with SHA2-512 digest, content digested using SHA2-512 in 1 MB chunks.
     RsaPkcs1V15WithSha512 = 0x0104,
+
+    /// ECDSA with SHA2-256 digest, content digested using SHA2-256 in 1 MB chunks.
     EcdsaWithSha256 = 0x0201,
+
+    /// ECDSA with SHA2-512 digest, content digested using SHA2-512 in 1 MB chunks.
     EcdsaWithSha512 = 0x0202,
+
+    /// DSA with SHA2-256 digest, content digested using SHA2-256 in 1 MB chunks.
+    /// Signing is done deterministically according to RFC 6979.
     DsaWithSha256 = 0x0301,
+
+    /// RSASSA-PKCS1-v1_5 with SHA2-256 digest, content digested using SHA2-256 in 4 KB
+    /// chunks, in the same way fsverity operates. This digest and the content length
+    /// (before digestion, 8 bytes in little endian) construct the final digest.
     VerityRsaPkcs1V15WithSha256 = 0x0421,
+
+    /// ECDSA with SHA2-256 digest, content digested using SHA2-256 in 4 KB chunks, in the
+    /// same way fsverity operates. This digest and the content length (before digestion,
+    /// 8 bytes in little endian) construct the final digest.
     VerityEcdsaWithSha256 = 0x0423,
+
+    /// DSA with SHA2-256 digest, content digested using SHA2-256 in 4 KB chunks, in the
+    /// same way fsverity operates. This digest and the content length (before digestion,
+    /// 8 bytes in little endian) construct the final digest.
     VerityDsaWithSha256 = 0x0425,
 }
 
-impl Ord for SignatureAlgorithmID {
-    /// Ranks the signature algorithm according to the corresponding content
-    /// digest algorithm's rank.
-    fn cmp(&self, other: &Self) -> Ordering {
-        self.to_content_digest_algorithm().cmp(&other.to_content_digest_algorithm())
+impl Default for SignatureAlgorithmID {
+    fn default() -> Self {
+        SignatureAlgorithmID::DsaWithSha256
     }
 }
 
-impl PartialOrd for SignatureAlgorithmID {
-    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
-        Some(self.cmp(other))
-    }
-}
-
-impl PartialEq for SignatureAlgorithmID {
-    fn eq(&self, other: &Self) -> bool {
-        self.cmp(other) == Ordering::Equal
+impl ReadFromBytes for Option<SignatureAlgorithmID> {
+    fn read_from_bytes(buf: &mut Bytes) -> Result<Self> {
+        Ok(SignatureAlgorithmID::from_u32(buf.get_u32_le()))
     }
 }
 
 impl SignatureAlgorithmID {
-    pub(crate) fn to_content_digest_algorithm(&self) -> ContentDigestAlgorithm {
+    /// Converts the signature algorithm ID to the corresponding u32.
+    pub fn to_u32(&self) -> u32 {
+        ToPrimitive::to_u32(self).expect("Unsupported algorithm for to_u32.")
+    }
+
+    pub(crate) fn new_verifier<'a>(
+        &self,
+        public_key: &'a PKey<pkey::Public>,
+    ) -> Result<Verifier<'a>> {
+        ensure!(
+            !matches!(
+                self,
+                SignatureAlgorithmID::DsaWithSha256 | SignatureAlgorithmID::VerityDsaWithSha256
+            ),
+            "TODO(b/197052981): Algorithm '{:?}' is not implemented.",
+            self
+        );
+        ensure!(public_key.id() == self.pkey_id(), "Public key has the wrong ID");
+        let mut verifier = Verifier::new(self.new_message_digest(), public_key)?;
+        if public_key.id() == pkey::Id::RSA {
+            verifier.set_rsa_padding(self.rsa_padding())?;
+        }
+        Ok(verifier)
+    }
+
+    /// Returns the message digest corresponding to the signature algorithm
+    /// according to the spec [Signature Algorithm IDs].
+    pub(crate) fn new_message_digest(&self) -> MessageDigest {
+        match self {
+            SignatureAlgorithmID::RsaPssWithSha256
+            | SignatureAlgorithmID::RsaPkcs1V15WithSha256
+            | SignatureAlgorithmID::EcdsaWithSha256
+            | SignatureAlgorithmID::DsaWithSha256
+            | SignatureAlgorithmID::VerityRsaPkcs1V15WithSha256
+            | SignatureAlgorithmID::VerityEcdsaWithSha256
+            | SignatureAlgorithmID::VerityDsaWithSha256 => MessageDigest::sha256(),
+            SignatureAlgorithmID::RsaPssWithSha512
+            | SignatureAlgorithmID::RsaPkcs1V15WithSha512
+            | SignatureAlgorithmID::EcdsaWithSha512 => MessageDigest::sha512(),
+        }
+    }
+
+    fn pkey_id(&self) -> pkey::Id {
+        match self {
+            SignatureAlgorithmID::RsaPssWithSha256
+            | SignatureAlgorithmID::RsaPssWithSha512
+            | SignatureAlgorithmID::RsaPkcs1V15WithSha256
+            | SignatureAlgorithmID::RsaPkcs1V15WithSha512
+            | SignatureAlgorithmID::VerityRsaPkcs1V15WithSha256 => pkey::Id::RSA,
+            SignatureAlgorithmID::EcdsaWithSha256
+            | SignatureAlgorithmID::EcdsaWithSha512
+            | SignatureAlgorithmID::VerityEcdsaWithSha256 => pkey::Id::EC,
+            SignatureAlgorithmID::DsaWithSha256 | SignatureAlgorithmID::VerityDsaWithSha256 => {
+                pkey::Id::DSA
+            }
+        }
+    }
+
+    fn rsa_padding(&self) -> Padding {
+        match self {
+            SignatureAlgorithmID::RsaPssWithSha256 | SignatureAlgorithmID::RsaPssWithSha512 => {
+                Padding::PKCS1_PSS
+            }
+            SignatureAlgorithmID::RsaPkcs1V15WithSha256
+            | SignatureAlgorithmID::VerityRsaPkcs1V15WithSha256
+            | SignatureAlgorithmID::RsaPkcs1V15WithSha512 => Padding::PKCS1,
+            SignatureAlgorithmID::EcdsaWithSha256
+            | SignatureAlgorithmID::EcdsaWithSha512
+            | SignatureAlgorithmID::VerityEcdsaWithSha256
+            | SignatureAlgorithmID::DsaWithSha256
+            | SignatureAlgorithmID::VerityDsaWithSha256 => Padding::NONE,
+        }
+    }
+
+    pub(crate) fn content_digest_algorithm(&self) -> ContentDigestAlgorithm {
         match self {
             SignatureAlgorithmID::RsaPssWithSha256
             | SignatureAlgorithmID::RsaPkcs1V15WithSha256
@@ -93,15 +195,3 @@
     VerityChunkedSha256,
     ChunkedSha512,
 }
-
-impl ContentDigestAlgorithm {
-    pub(crate) fn new_message_digest(&self) -> Result<MessageDigest> {
-        match self {
-            ContentDigestAlgorithm::ChunkedSha256 => Ok(MessageDigest::sha256()),
-            ContentDigestAlgorithm::ChunkedSha512 => Ok(MessageDigest::sha512()),
-            ContentDigestAlgorithm::VerityChunkedSha256 => {
-                bail!("TODO(b/197052981): CONTENT_DIGEST_VERITY_CHUNKED_SHA256 is not implemented")
-            }
-        }
-    }
-}
diff --git a/libs/apkverify/src/bytes_ext.rs b/libs/apkverify/src/bytes_ext.rs
index 22a3085..8fb36ee 100644
--- a/libs/apkverify/src/bytes_ext.rs
+++ b/libs/apkverify/src/bytes_ext.rs
@@ -16,7 +16,7 @@
 
 //! Provides extension methods Bytes::read<T>(), which calls back ReadFromBytes::read_from_byte()
 
-use anyhow::{bail, Result};
+use anyhow::{ensure, Result};
 use bytes::{Buf, Bytes};
 use std::ops::Deref;
 
@@ -79,20 +79,18 @@
 }
 
 fn read_length_prefixed_slice(buf: &mut Bytes) -> Result<Bytes> {
-    if buf.remaining() < 4 {
-        bail!(
-            "Remaining buffer too short to contain length of length-prefixed field. Remaining: {}",
-            buf.remaining()
-        );
-    }
+    ensure!(
+        buf.remaining() >= 4,
+        "Remaining buffer too short to contain length of length-prefixed field. Remaining: {}",
+        buf.remaining()
+    );
     let len = buf.get_u32_le() as usize;
-    if len > buf.remaining() {
-        bail!(
-            "length-prefixed field longer than remaining buffer. Field length: {}, remaining: {}",
-            len,
-            buf.remaining()
-        );
-    }
+    ensure!(
+        buf.remaining() >= len,
+        "length-prefixed field longer than remaining buffer. Field length: {}, remaining: {}",
+        len,
+        buf.remaining()
+    );
     Ok(buf.split_to(len))
 }
 
diff --git a/libs/apkverify/src/lib.rs b/libs/apkverify/src/lib.rs
index f6c1a21..084a910 100644
--- a/libs/apkverify/src/lib.rs
+++ b/libs/apkverify/src/lib.rs
@@ -24,5 +24,6 @@
 mod v3;
 mod ziputil;
 
-// TODO(jooyung) fallback to v2 when v3 not found
+// TODO(b/197052981) fallback to v2 when v3 not found
+pub use algorithms::SignatureAlgorithmID;
 pub use v3::{get_public_key_der, pick_v4_apk_digest, verify};
diff --git a/libs/apkverify/src/sigutil.rs b/libs/apkverify/src/sigutil.rs
index ea6d63a..bfa51c1 100644
--- a/libs/apkverify/src/sigutil.rs
+++ b/libs/apkverify/src/sigutil.rs
@@ -19,7 +19,6 @@
 use anyhow::{anyhow, ensure, Error, Result};
 use byteorder::{LittleEndian, ReadBytesExt};
 use bytes::{Buf, BufMut, Bytes, BytesMut};
-use num_traits::FromPrimitive;
 use openssl::hash::{DigestBytes, Hasher, MessageDigest};
 use std::cmp::min;
 use std::io::{self, Cursor, ErrorKind, Read, Seek, SeekFrom, Take};
@@ -30,19 +29,9 @@
 const APK_SIG_BLOCK_MIN_SIZE: u32 = 32;
 const APK_SIG_BLOCK_MAGIC: u128 = 0x3234206b636f6c4220676953204b5041;
 
-// TODO(b/246254355): Migrates usages of raw signature algorithm id to the enum.
-pub const SIGNATURE_RSA_PSS_WITH_SHA256: u32 = 0x0101;
-pub const SIGNATURE_RSA_PSS_WITH_SHA512: u32 = 0x0102;
-pub const SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256: u32 = 0x0103;
-pub const SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512: u32 = 0x0104;
-pub const SIGNATURE_ECDSA_WITH_SHA256: u32 = 0x0201;
-pub const SIGNATURE_ECDSA_WITH_SHA512: u32 = 0x0202;
-pub const SIGNATURE_DSA_WITH_SHA256: u32 = 0x0301;
-pub const SIGNATURE_VERITY_RSA_PKCS1_V1_5_WITH_SHA256: u32 = 0x0421;
-pub const SIGNATURE_VERITY_ECDSA_WITH_SHA256: u32 = 0x0423;
-pub const SIGNATURE_VERITY_DSA_WITH_SHA256: u32 = 0x0425;
-
 const CHUNK_SIZE_BYTES: u64 = 1024 * 1024;
+const CHUNK_HEADER_TOP: &[u8] = &[0x5a];
+const CHUNK_HEADER_MID: &[u8] = &[0xa5];
 
 /// The [APK structure] has four major sections:
 ///
@@ -91,12 +80,11 @@
     ///    chunks (little-endian uint32), and the concatenation of digests of the chunks in the
     ///    order the chunks appear in the APK.
     /// (see https://source.android.com/security/apksigning/v2#integrity-protected-contents)
-    pub fn compute_digest(&mut self, signature_algorithm_id: u32) -> Result<Vec<u8>> {
-        // TODO(b/246254355): Passes the enum SignatureAlgorithmID directly to this method.
-        let signature_algorithm_id = SignatureAlgorithmID::from_u32(signature_algorithm_id)
-            .ok_or_else(|| anyhow!("Unsupported algorithm ID: {}", signature_algorithm_id))?;
-        let digester = Digester::new(signature_algorithm_id)?;
-
+    pub(crate) fn compute_digest(
+        &mut self,
+        signature_algorithm_id: SignatureAlgorithmID,
+    ) -> Result<Vec<u8>> {
+        let digester = Digester { message_digest: signature_algorithm_id.new_message_digest() };
         let mut digests_of_chunks = BytesMut::new();
         let mut chunk_count = 0u32;
         let mut chunk = vec![0u8; CHUNK_SIZE_BYTES as usize];
@@ -164,16 +152,7 @@
     message_digest: MessageDigest,
 }
 
-const CHUNK_HEADER_TOP: &[u8] = &[0x5a];
-const CHUNK_HEADER_MID: &[u8] = &[0xa5];
-
 impl Digester {
-    fn new(signature_algorithm_id: SignatureAlgorithmID) -> Result<Digester> {
-        let message_digest =
-            signature_algorithm_id.to_content_digest_algorithm().new_message_digest()?;
-        Ok(Digester { message_digest })
-    }
-
     // v2/v3 digests are computed after prepending "header" byte and "size" info.
     fn digest(&self, data: &[u8], header: &[u8], size: u32) -> Result<DigestBytes> {
         let mut hasher = Hasher::new(self.message_digest)?;
@@ -295,7 +274,7 @@
     fn test_apk_digest() {
         let apk_file = File::open("tests/data/v3-only-with-dsa-sha256-1024.apk").unwrap();
         let mut apk_sections = ApkSections::new(apk_file).unwrap();
-        let digest = apk_sections.compute_digest(SIGNATURE_DSA_WITH_SHA256).unwrap();
+        let digest = apk_sections.compute_digest(SignatureAlgorithmID::DsaWithSha256).unwrap();
         assert_eq!(
             "0DF2426EA33AEDAF495D88E5BE0C6A1663FF0A81C5ED12D5B2929AE4B4300F2F",
             to_hex_string(&digest[..])
diff --git a/libs/apkverify/src/v3.rs b/libs/apkverify/src/v3.rs
index c86696f..05694ff 100644
--- a/libs/apkverify/src/v3.rs
+++ b/libs/apkverify/src/v3.rs
@@ -18,13 +18,9 @@
 //!
 //! [v3 verification]: https://source.android.com/security/apksigning/v3#verification
 
-use anyhow::{anyhow, bail, ensure, Context, Result};
+use anyhow::{ensure, Context, Result};
 use bytes::Bytes;
-use num_traits::FromPrimitive;
-use openssl::hash::MessageDigest;
 use openssl::pkey::{self, PKey};
-use openssl::rsa::Padding;
-use openssl::sign::Verifier;
 use openssl::x509::X509;
 use std::fs::File;
 use std::io::{Read, Seek};
@@ -37,7 +33,7 @@
 
 pub const APK_SIGNATURE_SCHEME_V3_BLOCK_ID: u32 = 0xf05368c0;
 
-// TODO(jooyung): get "ro.build.version.sdk"
+// TODO(b/190343842): get "ro.build.version.sdk"
 const SDK_INT: u32 = 31;
 
 type Signers = LengthPrefixed<Vec<LengthPrefixed<Signer>>>;
@@ -47,7 +43,7 @@
     min_sdk: u32,
     max_sdk: u32,
     signatures: LengthPrefixed<Vec<LengthPrefixed<Signature>>>,
-    public_key: LengthPrefixed<Bytes>,
+    public_key: PKey<pkey::Public>,
 }
 
 impl Signer {
@@ -73,13 +69,13 @@
 
 #[derive(Debug)]
 struct Signature {
-    /// TODO(b/246254355): Change the type of signature_algorithm_id to SignatureAlgorithmID
-    signature_algorithm_id: u32,
+    /// Option is used here to allow us to ignore unsupported algorithm.
+    signature_algorithm_id: Option<SignatureAlgorithmID>,
     signature: LengthPrefixed<Bytes>,
 }
 
 struct Digest {
-    signature_algorithm_id: u32,
+    signature_algorithm_id: Option<SignatureAlgorithmID>,
     digest: LengthPrefixed<Bytes>,
 }
 
@@ -88,9 +84,9 @@
 
 /// Verifies APK Signature Scheme v3 signatures of the provided APK and returns the public key
 /// associated with the signer in DER format.
-pub fn verify<P: AsRef<Path>>(path: P) -> Result<Box<[u8]>> {
-    let f = File::open(path.as_ref())?;
-    let mut sections = ApkSections::new(f)?;
+pub fn verify<P: AsRef<Path>>(apk_path: P) -> Result<Box<[u8]>> {
+    let apk = File::open(apk_path.as_ref())?;
+    let mut sections = ApkSections::new(apk)?;
     find_signer_and_then(&mut sections, |(signer, sections)| signer.verify(sections))
 }
 
@@ -108,30 +104,29 @@
     let supported = signers.iter().filter(|s| s.sdk_range().contains(&SDK_INT)).collect::<Vec<_>>();
 
     // there should be exactly one
-    if supported.len() != 1 {
-        bail!(
-            "APK Signature Scheme V3 only supports one signer: {} signers found.",
-            supported.len()
-        )
-    }
+    ensure!(
+        supported.len() == 1,
+        "APK Signature Scheme V3 only supports one signer: {} signers found.",
+        supported.len()
+    );
 
     // Call the supplied function
     f((supported[0], sections))
 }
 
 /// Gets the public key (in DER format) that was used to sign the given APK/APEX file
-pub fn get_public_key_der<P: AsRef<Path>>(path: P) -> Result<Box<[u8]>> {
-    let f = File::open(path.as_ref())?;
-    let mut sections = ApkSections::new(f)?;
+pub fn get_public_key_der<P: AsRef<Path>>(apk_path: P) -> Result<Box<[u8]>> {
+    let apk = File::open(apk_path.as_ref())?;
+    let mut sections = ApkSections::new(apk)?;
     find_signer_and_then(&mut sections, |(signer, _)| {
-        Ok(signer.public_key.to_vec().into_boxed_slice())
+        Ok(signer.public_key.public_key_to_der()?.into_boxed_slice())
     })
 }
 
 /// Gets the v4 [apk_digest].
 ///
 /// [apk_digest]: https://source.android.com/docs/security/apksigning/v4#apk-digest
-pub fn pick_v4_apk_digest<R: Read + Seek>(apk: R) -> Result<(u32, Box<[u8]>)> {
+pub fn pick_v4_apk_digest<R: Read + Seek>(apk: R) -> Result<(SignatureAlgorithmID, Box<[u8]>)> {
     let mut sections = ApkSections::new(apk)?;
     let mut block = sections.find_signature(APK_SIGNATURE_SCHEME_V3_BLOCK_ID)?;
     let signers = block.read::<Signers>()?;
@@ -146,20 +141,36 @@
         Ok(self
             .signatures
             .iter()
-            .filter(|sig| SignatureAlgorithmID::from_u32(sig.signature_algorithm_id).is_some())
-            .max_by_key(|sig| SignatureAlgorithmID::from_u32(sig.signature_algorithm_id).unwrap())
-            .ok_or_else(|| anyhow!("No supported signatures found"))?)
+            .filter(|sig| sig.signature_algorithm_id.is_some())
+            .max_by_key(|sig| sig.signature_algorithm_id.unwrap().content_digest_algorithm())
+            .context("No supported signatures found")?)
     }
 
-    fn pick_v4_apk_digest(&self) -> Result<(u32, Box<[u8]>)> {
+    fn pick_v4_apk_digest(&self) -> Result<(SignatureAlgorithmID, Box<[u8]>)> {
         let strongest = self.strongest_signature()?;
         let signed_data: SignedData = self.signed_data.slice(..).read()?;
         let digest = signed_data
             .digests
             .iter()
             .find(|&dig| dig.signature_algorithm_id == strongest.signature_algorithm_id)
-            .ok_or_else(|| anyhow!("Digest not found"))?;
-        Ok((digest.signature_algorithm_id, digest.digest.as_ref().to_vec().into_boxed_slice()))
+            .context("Digest not found")?;
+        Ok((
+            digest.signature_algorithm_id.context("Unsupported algorithm")?,
+            digest.digest.as_ref().to_vec().into_boxed_slice(),
+        ))
+    }
+
+    /// Verifies the strongest signature from signatures against signed data using public key.
+    /// Returns the verified signed data.
+    fn verify_signature(&self, strongest: &Signature) -> Result<SignedData> {
+        let mut verifier = strongest
+            .signature_algorithm_id
+            .context("Unsupported algorithm")?
+            .new_verifier(&self.public_key)?;
+        verifier.update(&self.signed_data)?;
+        ensure!(verifier.verify(&strongest.signature)?, "Signature is invalid.");
+        // It is now safe to parse signed data.
+        self.signed_data.slice(..).read()
     }
 
     /// The steps in this method implements APK Signature Scheme v3 verification step 3.
@@ -168,102 +179,61 @@
         let strongest = self.strongest_signature()?;
 
         // 2. Verify the corresponding signature from signatures against signed data using public key.
-        //    (It is now safe to parse signed data.)
-        let public_key = PKey::public_key_from_der(self.public_key.as_ref())?;
-        verify_signed_data(&self.signed_data, strongest, &public_key)?;
-
-        // It is now safe to parse signed data.
-        let signed_data: SignedData = self.signed_data.slice(..).read()?;
+        let verified_signed_data = self.verify_signature(strongest)?;
 
         // 3. Verify the min and max SDK versions in the signed data match those specified for the
         //    signer.
-        if self.sdk_range() != signed_data.sdk_range() {
-            bail!("SDK versions mismatch between signed and unsigned in v3 signer block.");
-        }
+        ensure!(
+            self.sdk_range() == verified_signed_data.sdk_range(),
+            "SDK versions mismatch between signed and unsigned in v3 signer block."
+        );
 
         // 4. Verify that the ordered list of signature algorithm IDs in digests and signatures is
         //    identical. (This is to prevent signature stripping/addition.)
-        if !self
-            .signatures
-            .iter()
-            .map(|sig| sig.signature_algorithm_id)
-            .eq(signed_data.digests.iter().map(|dig| dig.signature_algorithm_id))
-        {
-            bail!("Signature algorithms don't match between digests and signatures records");
-        }
+        ensure!(
+            self.signatures
+                .iter()
+                .map(|sig| sig.signature_algorithm_id)
+                .eq(verified_signed_data.digests.iter().map(|dig| dig.signature_algorithm_id)),
+            "Signature algorithms don't match between digests and signatures records"
+        );
 
         // 5. Compute the digest of APK contents using the same digest algorithm as the digest
         //    algorithm used by the signature algorithm.
-        let digest = signed_data
+        let digest = verified_signed_data
             .digests
             .iter()
             .find(|&dig| dig.signature_algorithm_id == strongest.signature_algorithm_id)
             .unwrap(); // ok to unwrap since we check if two lists are the same above
-        let computed = sections.compute_digest(digest.signature_algorithm_id)?;
+        let computed = sections
+            .compute_digest(digest.signature_algorithm_id.context("Unsupported algorithm")?)?;
 
         // 6. Verify that the computed digest is identical to the corresponding digest from digests.
-        if computed != digest.digest.as_ref() {
-            bail!(
-                "Digest mismatch: computed={:?} vs expected={:?}",
-                to_hex_string(&computed),
-                to_hex_string(&digest.digest),
-            );
-        }
+        ensure!(
+            computed == digest.digest.as_ref(),
+            "Digest mismatch: computed={:?} vs expected={:?}",
+            to_hex_string(&computed),
+            to_hex_string(&digest.digest),
+        );
 
         // 7. Verify that public key of the first certificate of certificates is identical
         //    to public key.
-        let cert = signed_data.certificates.first().context("No certificates listed")?;
+        let cert = verified_signed_data.certificates.first().context("No certificates listed")?;
         let cert = X509::from_der(cert.as_ref())?;
-        if !cert.public_key()?.public_eq(&public_key) {
-            bail!("Public key mismatch between certificate and signature record");
-        }
+        ensure!(
+            cert.public_key()?.public_eq(&self.public_key),
+            "Public key mismatch between certificate and signature record"
+        );
 
-        // TODO(jooyung) 8. If the proof-of-rotation attribute exists for the signer verify that the struct is valid and this signer is the last certificate in the list.
-        Ok(self.public_key.to_vec().into_boxed_slice())
+        // TODO(b/245914104)
+        // 8. If the proof-of-rotation attribute exists for the signer verify that the
+        // struct is valid and this signer is the last certificate in the list.
+        Ok(self.public_key.public_key_to_der()?.into_boxed_slice())
     }
 }
 
-fn verify_signed_data(data: &Bytes, signature: &Signature, key: &PKey<pkey::Public>) -> Result<()> {
-    let (pkey_id, padding, digest) = match signature.signature_algorithm_id {
-        SIGNATURE_RSA_PSS_WITH_SHA256 => {
-            (pkey::Id::RSA, Padding::PKCS1_PSS, MessageDigest::sha256())
-        }
-        SIGNATURE_RSA_PSS_WITH_SHA512 => {
-            (pkey::Id::RSA, Padding::PKCS1_PSS, MessageDigest::sha512())
-        }
-        SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256 | SIGNATURE_VERITY_RSA_PKCS1_V1_5_WITH_SHA256 => {
-            (pkey::Id::RSA, Padding::PKCS1, MessageDigest::sha256())
-        }
-        SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512 => {
-            (pkey::Id::RSA, Padding::PKCS1, MessageDigest::sha512())
-        }
-        SIGNATURE_ECDSA_WITH_SHA256 | SIGNATURE_VERITY_ECDSA_WITH_SHA256 => {
-            (pkey::Id::EC, Padding::NONE, MessageDigest::sha256())
-        }
-        // TODO(b/190343842) not implemented signature algorithm
-        SIGNATURE_ECDSA_WITH_SHA512
-        | SIGNATURE_DSA_WITH_SHA256
-        | SIGNATURE_VERITY_DSA_WITH_SHA256 => {
-            bail!(
-                "TODO(b/190343842) not implemented signature algorithm: {:#x}",
-                signature.signature_algorithm_id
-            );
-        }
-        _ => bail!("Unsupported signature algorithm: {:#x}", signature.signature_algorithm_id),
-    };
-    ensure!(key.id() == pkey_id, "Public key has the wrong ID");
-    let mut verifier = Verifier::new(digest, key)?;
-    if pkey_id == pkey::Id::RSA {
-        verifier.set_rsa_padding(padding)?;
-    }
-    verifier.update(data)?;
-    let verified = verifier.verify(&signature.signature)?;
-    ensure!(verified, "Signature is invalid ");
-    Ok(())
-}
-
 // ReadFromBytes implementations
-// TODO(jooyung): add derive macro: #[derive(ReadFromBytes)]
+// TODO(b/190343842): add derive macro: #[derive(ReadFromBytes)]
 
 impl ReadFromBytes for Signer {
     fn read_from_bytes(buf: &mut Bytes) -> Result<Self> {
@@ -301,40 +271,14 @@
     }
 }
 
+impl ReadFromBytes for PKey<pkey::Public> {
+    fn read_from_bytes(buf: &mut Bytes) -> Result<Self> {
+        let raw_public_key = buf.read::<LengthPrefixed<Bytes>>()?;
+        Ok(PKey::public_key_from_der(raw_public_key.as_ref())?)
+    }
+}
+
 #[inline]
 pub(crate) fn to_hex_string(buf: &[u8]) -> String {
     buf.iter().map(|b| format!("{:02X}", b)).collect()
 }
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-    use std::fs::File;
-
-    #[test]
-    fn test_pick_v4_apk_digest_only_with_v3_dsa_sha256() {
-        check_v4_apk_digest(
-            "tests/data/v3-only-with-dsa-sha256-1024.apk",
-            SIGNATURE_DSA_WITH_SHA256,
-            "0DF2426EA33AEDAF495D88E5BE0C6A1663FF0A81C5ED12D5B2929AE4B4300F2F",
-        );
-    }
-
-    #[test]
-    fn test_pick_v4_apk_digest_only_with_v3_pkcs1_sha512() {
-        check_v4_apk_digest(
-            "tests/data/v3-only-with-rsa-pkcs1-sha512-1024.apk",
-            SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512,
-            "9B9AE02DA60B18999BF541790F00D380006FDF0655C3C482AA0BB0AF17CF7A42\
-             ECF56B973518546C9080B2FEF83027E895ED2882BFC88EA19790BBAB29AF53B3",
-        );
-    }
-
-    fn check_v4_apk_digest(apk_filename: &str, expected_algorithm: u32, expected_digest: &str) {
-        let apk_file = File::open(apk_filename).unwrap();
-        let (signature_algorithm_id, apk_digest) = pick_v4_apk_digest(apk_file).unwrap();
-
-        assert_eq!(expected_algorithm, signature_algorithm_id);
-        assert_eq!(expected_digest, to_hex_string(apk_digest.as_ref()));
-    }
-}
diff --git a/libs/apkverify/src/ziputil.rs b/libs/apkverify/src/ziputil.rs
index 8badff2..eb2826a 100644
--- a/libs/apkverify/src/ziputil.rs
+++ b/libs/apkverify/src/ziputil.rs
@@ -16,7 +16,7 @@
 
 //! Utilities for zip handling of APK files.
 
-use anyhow::{bail, Result};
+use anyhow::{ensure, Result};
 use bytes::{Buf, BufMut};
 use std::io::{Read, Seek, SeekFrom};
 use zip::ZipArchive;
@@ -41,25 +41,26 @@
     // open a zip to parse EOCD
     let archive = ZipArchive::new(reader)?;
     let eocd_size = archive.comment().len() + EOCD_SIZE_WITHOUT_COMMENT;
-    if archive.offset() != 0 {
-        bail!("Invalid ZIP: offset should be 0, but {}.", archive.offset());
-    }
+    ensure!(archive.offset() == 0, "Invalid ZIP: offset should be 0, but {}.", archive.offset());
     // retrieve reader back
     reader = archive.into_inner();
     // the current position should point EOCD offset
     let eocd_offset = reader.seek(SeekFrom::Current(0))? as u32;
     let mut eocd = vec![0u8; eocd_size as usize];
     reader.read_exact(&mut eocd)?;
-    if (&eocd[0..]).get_u32_le() != EOCD_SIGNATURE {
-        bail!("Invalid ZIP: ZipArchive::new() should point EOCD after reading.");
-    }
+    ensure!(
+        (&eocd[0..]).get_u32_le() == EOCD_SIGNATURE,
+        "Invalid ZIP: ZipArchive::new() should point EOCD after reading."
+    );
     let (central_directory_size, central_directory_offset) = get_central_directory(&eocd)?;
-    if central_directory_offset == ZIP64_MARK || central_directory_size == ZIP64_MARK {
-        bail!("Unsupported ZIP: ZIP64 is not supported.");
-    }
-    if central_directory_offset + central_directory_size != eocd_offset {
-        bail!("Invalid ZIP: EOCD should follow CD with no extra data or overlap.");
-    }
+    ensure!(
+        central_directory_offset != ZIP64_MARK && central_directory_size != ZIP64_MARK,
+        "Unsupported ZIP: ZIP64 is not supported."
+    );
+    ensure!(
+        central_directory_offset + central_directory_size == eocd_offset,
+        "Invalid ZIP: EOCD should follow CD with no extra data or overlap."
+    );
 
     Ok((
         reader,
@@ -73,9 +74,7 @@
 }
 
 fn get_central_directory(buf: &[u8]) -> Result<(u32, u32)> {
-    if buf.len() < EOCD_SIZE_WITHOUT_COMMENT {
-        bail!("Invalid EOCD size: {}", buf.len());
-    }
+    ensure!(buf.len() >= EOCD_SIZE_WITHOUT_COMMENT, "Invalid EOCD size: {}", buf.len());
     let mut buf = &buf[EOCD_CENTRAL_DIRECTORY_SIZE_FIELD_OFFSET..];
     let size = buf.get_u32_le();
     let offset = buf.get_u32_le();
@@ -84,9 +83,7 @@
 
 /// Update EOCD's central_directory_offset field.
 pub fn set_central_directory_offset(buf: &mut [u8], value: u32) -> Result<()> {
-    if buf.len() < EOCD_SIZE_WITHOUT_COMMENT {
-        bail!("Invalid EOCD size: {}", buf.len());
-    }
+    ensure!(buf.len() >= EOCD_SIZE_WITHOUT_COMMENT, "Invalid EOCD size: {}", buf.len());
     (&mut buf[EOCD_CENTRAL_DIRECTORY_OFFSET_FIELD_OFFSET..]).put_u32_le(value);
     Ok(())
 }
diff --git a/libs/apkverify/tests/apkverify_test.rs b/libs/apkverify/tests/apkverify_test.rs
index a674ad7..5bd901d 100644
--- a/libs/apkverify/tests/apkverify_test.rs
+++ b/libs/apkverify/tests/apkverify_test.rs
@@ -14,8 +14,10 @@
  * limitations under the License.
  */
 
-use apkverify::{testing::assert_contains, verify};
-use std::matches;
+use apkverify::{
+    get_public_key_der, pick_v4_apk_digest, testing::assert_contains, verify, SignatureAlgorithmID,
+};
+use std::{fs, matches, path::Path};
 
 const KEY_NAMES_DSA: &[&str] = &["1024", "2048", "3072"];
 const KEY_NAMES_ECDSA: &[&str] = &["p256", "p384", "p521"];
@@ -25,7 +27,7 @@
 fn test_verify_truncated_cd() {
     use zip::result::ZipError;
     let res = verify("tests/data/v2-only-truncated-cd.apk");
-    // TODO(jooyung): consider making a helper for err assertion
+    // TODO(b/190343842): consider making a helper for err assertion
     assert!(matches!(
         res.unwrap_err().root_cause().downcast_ref::<ZipError>().unwrap(),
         ZipError::InvalidArchive(_),
@@ -33,62 +35,70 @@
 }
 
 #[test]
-fn test_verify_v3() {
-    assert!(verify("tests/data/test.apex").is_ok());
+fn apex_signed_with_v3_rsa_pkcs1_sha512_is_valid() {
+    validate_apk("tests/data/test.apex", SignatureAlgorithmID::RsaPkcs1V15WithSha512);
 }
 
-// TODO(b/190343842)
 #[test]
 fn test_verify_v3_dsa_sha256() {
     for key_name in KEY_NAMES_DSA.iter() {
         let res = verify(format!("tests/data/v3-only-with-dsa-sha256-{}.apk", key_name));
         assert!(res.is_err());
-        assert_contains(
-            &res.unwrap_err().to_string(),
-            "TODO(b/190343842) not implemented signature algorithm",
+        assert_contains(&res.unwrap_err().to_string(), "not implemented");
+    }
+}
+
+/// TODO(b/197052981): DSA algorithm is not yet supported.
+#[test]
+fn apks_signed_with_v3_dsa_sha256_have_valid_apk_digest() {
+    for key_name in KEY_NAMES_DSA.iter() {
+        validate_apk_digest(
+            format!("tests/data/v3-only-with-dsa-sha256-{}.apk", key_name),
+            SignatureAlgorithmID::DsaWithSha256,
         );
     }
 }
 
 #[test]
-fn test_verify_v3_ecdsa_sha256() {
+fn apks_signed_with_v3_ecdsa_sha256_are_valid() {
     for key_name in KEY_NAMES_ECDSA.iter() {
-        assert!(verify(format!("tests/data/v3-only-with-ecdsa-sha256-{}.apk", key_name)).is_ok());
+        validate_apk(
+            format!("tests/data/v3-only-with-ecdsa-sha256-{}.apk", key_name),
+            SignatureAlgorithmID::EcdsaWithSha256,
+        );
     }
 }
 
-// TODO(b/190343842)
 #[test]
-fn test_verify_v3_ecdsa_sha512() {
+fn apks_signed_with_v3_ecdsa_sha512_are_valid() {
     for key_name in KEY_NAMES_ECDSA.iter() {
-        let res = verify(format!("tests/data/v3-only-with-ecdsa-sha512-{}.apk", key_name));
-        assert!(res.is_err());
-        assert_contains(
-            &res.unwrap_err().to_string(),
-            "TODO(b/190343842) not implemented signature algorithm",
+        validate_apk(
+            format!("tests/data/v3-only-with-ecdsa-sha512-{}.apk", key_name),
+            SignatureAlgorithmID::EcdsaWithSha512,
         );
     }
 }
 
 #[test]
-fn test_verify_v3_rsa_sha256() {
+fn apks_signed_with_v3_rsa_pkcs1_sha256_are_valid() {
     for key_name in KEY_NAMES_RSA.iter() {
-        assert!(
-            verify(format!("tests/data/v3-only-with-rsa-pkcs1-sha256-{}.apk", key_name)).is_ok()
+        validate_apk(
+            format!("tests/data/v3-only-with-rsa-pkcs1-sha256-{}.apk", key_name),
+            SignatureAlgorithmID::RsaPkcs1V15WithSha256,
         );
     }
 }
 
 #[test]
-fn test_verify_v3_rsa_sha512() {
+fn apks_signed_with_v3_rsa_pkcs1_sha512_are_valid() {
     for key_name in KEY_NAMES_RSA.iter() {
-        assert!(
-            verify(format!("tests/data/v3-only-with-rsa-pkcs1-sha512-{}.apk", key_name)).is_ok()
+        validate_apk(
+            format!("tests/data/v3-only-with-rsa-pkcs1-sha512-{}.apk", key_name),
+            SignatureAlgorithmID::RsaPkcs1V15WithSha512,
         );
     }
 }
 
-// TODO(b/190343842)
 #[test]
 fn test_verify_v3_sig_does_not_verify() {
     let path_list = [
@@ -101,13 +111,11 @@
         assert!(res.is_err());
         let error_msg = &res.unwrap_err().to_string();
         assert!(
-            error_msg.contains("Signature is invalid")
-                || error_msg.contains("TODO(b/190343842) not implemented signature algorithm")
+            error_msg.contains("Signature is invalid") || error_msg.contains("not implemented")
         );
     }
 }
 
-// TODO(b/190343842)
 #[test]
 fn test_verify_v3_digest_mismatch() {
     let path_list = [
@@ -118,10 +126,7 @@
         let res = verify(path);
         assert!(res.is_err());
         let error_msg = &res.unwrap_err().to_string();
-        assert!(
-            error_msg.contains("Digest mismatch")
-                || error_msg.contains("TODO(b/190343842) not implemented signature algorithm")
-        );
+        assert!(error_msg.contains("Digest mismatch") || error_msg.contains("not implemented"));
     }
 }
 
@@ -182,21 +187,88 @@
 }
 
 #[test]
-fn test_verify_v3_unknown_additional_attr() {
-    assert!(verify("tests/data/v3-only-unknown-additional-attr.apk").is_ok());
+fn apk_signed_with_v3_unknown_additional_attr_is_valid() {
+    validate_apk(
+        "tests/data/v3-only-unknown-additional-attr.apk",
+        SignatureAlgorithmID::RsaPkcs1V15WithSha256,
+    );
 }
 
 #[test]
-fn test_verify_v3_unknown_pair_in_apk_sig_block() {
-    assert!(verify("tests/data/v3-only-unknown-pair-in-apk-sig-block.apk").is_ok());
+fn apk_signed_with_v3_unknown_pair_in_apk_sig_block_is_valid() {
+    validate_apk(
+        "tests/data/v3-only-unknown-pair-in-apk-sig-block.apk",
+        SignatureAlgorithmID::RsaPkcs1V15WithSha256,
+    );
 }
 
 #[test]
-fn test_verify_v3_ignorable_unsupported_sig_algs() {
-    assert!(verify("tests/data/v3-only-with-ignorable-unsupported-sig-algs.apk").is_ok());
+fn apk_signed_with_v3_ignorable_unsupported_sig_algs_is_valid() {
+    validate_apk(
+        "tests/data/v3-only-with-ignorable-unsupported-sig-algs.apk",
+        SignatureAlgorithmID::RsaPkcs1V15WithSha256,
+    );
 }
 
 #[test]
-fn test_verify_v3_stamp() {
-    assert!(verify("tests/data/v3-only-with-stamp.apk").is_ok());
+fn apk_signed_with_v3_stamp_is_valid() {
+    validate_apk("tests/data/v3-only-with-stamp.apk", SignatureAlgorithmID::EcdsaWithSha256);
+}
+
+fn validate_apk<P: AsRef<Path>>(apk_path: P, expected_algorithm_id: SignatureAlgorithmID) {
+    validate_apk_public_key(&apk_path);
+    validate_apk_digest(&apk_path, expected_algorithm_id);
+}
+
+/// Validates that the following public keys are equal:
+/// * public key from verification
+/// * public key extracted from apk without verification
+/// * expected public key from the corresponding .der file
+fn validate_apk_public_key<P: AsRef<Path>>(apk_path: P) {
+    let public_key_from_verification = verify(&apk_path);
+    let public_key_from_verification =
+        public_key_from_verification.expect("Error in verification result");
+
+    let expected_public_key_path = format!("{}.der", apk_path.as_ref().to_str().unwrap());
+    assert_bytes_eq_to_data_in_file(&public_key_from_verification, expected_public_key_path);
+
+    let public_key_from_apk = get_public_key_der(&apk_path);
+    let public_key_from_apk =
+        public_key_from_apk.expect("Error when extracting public key from apk");
+    assert_eq!(
+        public_key_from_verification, public_key_from_apk,
+        "Public key extracted directly from apk does not match the public key from verification."
+    );
+}
+
+/// Validates that the following apk_digest are equal:
+/// * apk_digest directly extracted from apk without computation
+/// * expected apk digest from the corresponding .apk_digest file
+fn validate_apk_digest<P: AsRef<Path>>(apk_path: P, expected_algorithm_id: SignatureAlgorithmID) {
+    let apk = fs::File::open(&apk_path).expect("Unabled to open apk file");
+
+    let (signature_algorithm_id, digest_from_apk) =
+        pick_v4_apk_digest(apk).expect("Error when extracting apk digest.");
+
+    assert_eq!(expected_algorithm_id, signature_algorithm_id);
+    let expected_digest_path = format!("{}.apk_digest", apk_path.as_ref().to_str().unwrap());
+    assert_bytes_eq_to_data_in_file(&digest_from_apk, expected_digest_path);
+}
+
+fn assert_bytes_eq_to_data_in_file<P: AsRef<Path> + std::fmt::Display>(
+    bytes_data: &[u8],
+    expected_data_path: P,
+) {
+    assert!(
+        fs::metadata(&expected_data_path).is_ok(),
+        "File does not exist. You can re-create it with:\n$ echo -en {} > {}\n",
+        bytes_data.iter().map(|b| format!("\\\\x{:02x}", b)).collect::<String>(),
+        expected_data_path
+    );
+    let expected_data = fs::read(&expected_data_path).unwrap();
+    assert_eq!(
+        expected_data, bytes_data,
+        "Actual data does not match the data from: {}",
+        expected_data_path
+    );
 }
diff --git a/libs/apkverify/tests/data/README.md b/libs/apkverify/tests/data/README.md
index 7556921..7ba5d8e 100644
--- a/libs/apkverify/tests/data/README.md
+++ b/libs/apkverify/tests/data/README.md
@@ -1,6 +1,10 @@
+# About test data
+
+## .apk
+
 test.apex is copied from ADBD apex built in AOSP.
 
-```sh
+```bash
 $ apksigner verify -v test.apex
 Verifies
 Verified using v1 scheme (JAR signing): false
@@ -11,4 +15,20 @@
 Number of signers: 1
 ```
 
-APK files are copied from tools/apksig/src/test/resources/com/android/apksig/.
+APK files are copied from [tools/apksig/src/test/resources/com/android/apksig/](https://cs.android.com/android/platform/superproject/+/master:tools/apksig/src/test/resources/com/android/apksig/;l=1;drc=c2a8da1913d7fb359b023bf200e31d75ff22a5c3).
+
+## .der
+
+`.der` files contain the expected public keys. When validating the public keys in tests, if the corresponding `.der` file is missing, there will be some text as follows in the failure message:
+
+```bash
+$ echo -en \\x30\\x59\\x30\\x13\\x06\\x07\\x2a\\x86\\x48\\xce\\x3d\\x02\\x01\\x06\\x08\\x2a\\x86\\x48\\xce\\x3d\\x03\\x01\\x07\\x03\\x42\\x00\\x04\\xa6\\x5f\\x11\\x3d\\x22\\xcb\\x49\\x13\\x90\\x83\\x07\\xac\\x31\\xee\\x2b\\xa0\\xe9\\x13\\x8b\\x78\\x5f\\xac\\x65\\x36\\xd1\\x4e\\xa2\\xce\\x90\\xd2\\xb4\\xbf\\xe1\\x94\\xb5\\x0c\\xdc\\x8e\\x16\\x9f\\x54\\xa7\\x3a\\x99\\x1e\\xf0\\xfa\\x76\\x32\\x98\\x25\\xbe\\x07\\x8c\\xc7\\x82\\x74\\x07\\x03\\xda\\x44\\xb4\\xd7\\xeb > tests/data/v3-only-with-stamp.apk.der
+```
+
+You just need to execute this command in the folder `libs/apkverify` to generate the corresponding `.der` file. After it, you can run the test again, and the test will pass.
+
+Otherwise, you can also use the command `apksigner verify --min-sdk-version 24 --print-certs-pem tests/data/v3-only-with-stamp.apk` to get the public key in `.pem` format and then convert it to `.der` with openssl.
+
+## .apk_digest
+
+`.apk_digest` files contain the expected [apk_digest](https://source.android.com/docs/security/features/apksigning/v4#apk-digest). They are generated the same way as `.der` when validating the apk_digest.
\ No newline at end of file
diff --git a/libs/apkverify/tests/data/test.apex.apk_digest b/libs/apkverify/tests/data/test.apex.apk_digest
new file mode 100644
index 0000000..09a7340
--- /dev/null
+++ b/libs/apkverify/tests/data/test.apex.apk_digest
Binary files differ
diff --git a/libs/apkverify/tests/data/test.apex.der b/libs/apkverify/tests/data/test.apex.der
new file mode 100644
index 0000000..abeb1eb
--- /dev/null
+++ b/libs/apkverify/tests/data/test.apex.der
Binary files differ
diff --git a/libs/apkverify/tests/data/v3-only-unknown-additional-attr.apk.apk_digest b/libs/apkverify/tests/data/v3-only-unknown-additional-attr.apk.apk_digest
new file mode 100644
index 0000000..c5aec18
--- /dev/null
+++ b/libs/apkverify/tests/data/v3-only-unknown-additional-attr.apk.apk_digest
Binary files differ
diff --git a/libs/apkverify/tests/data/v3-only-unknown-additional-attr.apk.der b/libs/apkverify/tests/data/v3-only-unknown-additional-attr.apk.der
new file mode 100644
index 0000000..27535ca
--- /dev/null
+++ b/libs/apkverify/tests/data/v3-only-unknown-additional-attr.apk.der
Binary files differ
diff --git a/libs/apkverify/tests/data/v3-only-unknown-pair-in-apk-sig-block.apk.apk_digest b/libs/apkverify/tests/data/v3-only-unknown-pair-in-apk-sig-block.apk.apk_digest
new file mode 100644
index 0000000..c5aec18
--- /dev/null
+++ b/libs/apkverify/tests/data/v3-only-unknown-pair-in-apk-sig-block.apk.apk_digest
Binary files differ
diff --git a/libs/apkverify/tests/data/v3-only-unknown-pair-in-apk-sig-block.apk.der b/libs/apkverify/tests/data/v3-only-unknown-pair-in-apk-sig-block.apk.der
new file mode 100644
index 0000000..6aafd09
--- /dev/null
+++ b/libs/apkverify/tests/data/v3-only-unknown-pair-in-apk-sig-block.apk.der
Binary files differ
diff --git a/libs/apkverify/tests/data/v3-only-with-dsa-sha256-1024.apk.apk_digest b/libs/apkverify/tests/data/v3-only-with-dsa-sha256-1024.apk.apk_digest
new file mode 100644
index 0000000..c5aec18
--- /dev/null
+++ b/libs/apkverify/tests/data/v3-only-with-dsa-sha256-1024.apk.apk_digest
Binary files differ
diff --git a/libs/apkverify/tests/data/v3-only-with-dsa-sha256-2048.apk.apk_digest b/libs/apkverify/tests/data/v3-only-with-dsa-sha256-2048.apk.apk_digest
new file mode 100644
index 0000000..c5aec18
--- /dev/null
+++ b/libs/apkverify/tests/data/v3-only-with-dsa-sha256-2048.apk.apk_digest
Binary files differ
diff --git a/libs/apkverify/tests/data/v3-only-with-dsa-sha256-3072.apk.apk_digest b/libs/apkverify/tests/data/v3-only-with-dsa-sha256-3072.apk.apk_digest
new file mode 100644
index 0000000..c5aec18
--- /dev/null
+++ b/libs/apkverify/tests/data/v3-only-with-dsa-sha256-3072.apk.apk_digest
Binary files differ
diff --git a/libs/apkverify/tests/data/v3-only-with-ecdsa-sha256-p256.apk.apk_digest b/libs/apkverify/tests/data/v3-only-with-ecdsa-sha256-p256.apk.apk_digest
new file mode 100644
index 0000000..c5aec18
--- /dev/null
+++ b/libs/apkverify/tests/data/v3-only-with-ecdsa-sha256-p256.apk.apk_digest
Binary files differ
diff --git a/libs/apkverify/tests/data/v3-only-with-ecdsa-sha256-p256.apk.der b/libs/apkverify/tests/data/v3-only-with-ecdsa-sha256-p256.apk.der
new file mode 100644
index 0000000..01927af
--- /dev/null
+++ b/libs/apkverify/tests/data/v3-only-with-ecdsa-sha256-p256.apk.der
Binary files differ
diff --git a/libs/apkverify/tests/data/v3-only-with-ecdsa-sha256-p384.apk.apk_digest b/libs/apkverify/tests/data/v3-only-with-ecdsa-sha256-p384.apk.apk_digest
new file mode 100644
index 0000000..c5aec18
--- /dev/null
+++ b/libs/apkverify/tests/data/v3-only-with-ecdsa-sha256-p384.apk.apk_digest
Binary files differ
diff --git a/libs/apkverify/tests/data/v3-only-with-ecdsa-sha256-p384.apk.der b/libs/apkverify/tests/data/v3-only-with-ecdsa-sha256-p384.apk.der
new file mode 100644
index 0000000..95baf40
--- /dev/null
+++ b/libs/apkverify/tests/data/v3-only-with-ecdsa-sha256-p384.apk.der
Binary files differ
diff --git a/libs/apkverify/tests/data/v3-only-with-ecdsa-sha256-p521.apk.apk_digest b/libs/apkverify/tests/data/v3-only-with-ecdsa-sha256-p521.apk.apk_digest
new file mode 100644
index 0000000..c5aec18
--- /dev/null
+++ b/libs/apkverify/tests/data/v3-only-with-ecdsa-sha256-p521.apk.apk_digest
Binary files differ
diff --git a/libs/apkverify/tests/data/v3-only-with-ecdsa-sha256-p521.apk.der b/libs/apkverify/tests/data/v3-only-with-ecdsa-sha256-p521.apk.der
new file mode 100644
index 0000000..b68f925
--- /dev/null
+++ b/libs/apkverify/tests/data/v3-only-with-ecdsa-sha256-p521.apk.der
Binary files differ
diff --git a/libs/apkverify/tests/data/v3-only-with-ecdsa-sha512-p256.apk.apk_digest b/libs/apkverify/tests/data/v3-only-with-ecdsa-sha512-p256.apk.apk_digest
new file mode 100644
index 0000000..4fe1b65
--- /dev/null
+++ b/libs/apkverify/tests/data/v3-only-with-ecdsa-sha512-p256.apk.apk_digest
Binary files differ
diff --git a/libs/apkverify/tests/data/v3-only-with-ecdsa-sha512-p256.apk.der b/libs/apkverify/tests/data/v3-only-with-ecdsa-sha512-p256.apk.der
new file mode 100644
index 0000000..01927af
--- /dev/null
+++ b/libs/apkverify/tests/data/v3-only-with-ecdsa-sha512-p256.apk.der
Binary files differ
diff --git a/libs/apkverify/tests/data/v3-only-with-ecdsa-sha512-p384.apk.apk_digest b/libs/apkverify/tests/data/v3-only-with-ecdsa-sha512-p384.apk.apk_digest
new file mode 100644
index 0000000..4fe1b65
--- /dev/null
+++ b/libs/apkverify/tests/data/v3-only-with-ecdsa-sha512-p384.apk.apk_digest
Binary files differ
diff --git a/libs/apkverify/tests/data/v3-only-with-ecdsa-sha512-p384.apk.der b/libs/apkverify/tests/data/v3-only-with-ecdsa-sha512-p384.apk.der
new file mode 100644
index 0000000..95baf40
--- /dev/null
+++ b/libs/apkverify/tests/data/v3-only-with-ecdsa-sha512-p384.apk.der
Binary files differ
diff --git a/libs/apkverify/tests/data/v3-only-with-ecdsa-sha512-p521.apk.apk_digest b/libs/apkverify/tests/data/v3-only-with-ecdsa-sha512-p521.apk.apk_digest
new file mode 100644
index 0000000..4fe1b65
--- /dev/null
+++ b/libs/apkverify/tests/data/v3-only-with-ecdsa-sha512-p521.apk.apk_digest
Binary files differ
diff --git a/libs/apkverify/tests/data/v3-only-with-ecdsa-sha512-p521.apk.der b/libs/apkverify/tests/data/v3-only-with-ecdsa-sha512-p521.apk.der
new file mode 100644
index 0000000..b68f925
--- /dev/null
+++ b/libs/apkverify/tests/data/v3-only-with-ecdsa-sha512-p521.apk.der
Binary files differ
diff --git a/libs/apkverify/tests/data/v3-only-with-ignorable-unsupported-sig-algs.apk.apk_digest b/libs/apkverify/tests/data/v3-only-with-ignorable-unsupported-sig-algs.apk.apk_digest
new file mode 100644
index 0000000..c5aec18
--- /dev/null
+++ b/libs/apkverify/tests/data/v3-only-with-ignorable-unsupported-sig-algs.apk.apk_digest
Binary files differ
diff --git a/libs/apkverify/tests/data/v3-only-with-ignorable-unsupported-sig-algs.apk.der b/libs/apkverify/tests/data/v3-only-with-ignorable-unsupported-sig-algs.apk.der
new file mode 100644
index 0000000..96dc543
--- /dev/null
+++ b/libs/apkverify/tests/data/v3-only-with-ignorable-unsupported-sig-algs.apk.der
Binary files differ
diff --git a/libs/apkverify/tests/data/v3-only-with-rsa-pkcs1-sha256-1024.apk.apk_digest b/libs/apkverify/tests/data/v3-only-with-rsa-pkcs1-sha256-1024.apk.apk_digest
new file mode 100644
index 0000000..c5aec18
--- /dev/null
+++ b/libs/apkverify/tests/data/v3-only-with-rsa-pkcs1-sha256-1024.apk.apk_digest
Binary files differ
diff --git a/libs/apkverify/tests/data/v3-only-with-rsa-pkcs1-sha256-1024.apk.der b/libs/apkverify/tests/data/v3-only-with-rsa-pkcs1-sha256-1024.apk.der
new file mode 100644
index 0000000..6aafd09
--- /dev/null
+++ b/libs/apkverify/tests/data/v3-only-with-rsa-pkcs1-sha256-1024.apk.der
Binary files differ
diff --git a/libs/apkverify/tests/data/v3-only-with-rsa-pkcs1-sha256-16384.apk.apk_digest b/libs/apkverify/tests/data/v3-only-with-rsa-pkcs1-sha256-16384.apk.apk_digest
new file mode 100644
index 0000000..c5aec18
--- /dev/null
+++ b/libs/apkverify/tests/data/v3-only-with-rsa-pkcs1-sha256-16384.apk.apk_digest
Binary files differ
diff --git a/libs/apkverify/tests/data/v3-only-with-rsa-pkcs1-sha256-16384.apk.der b/libs/apkverify/tests/data/v3-only-with-rsa-pkcs1-sha256-16384.apk.der
new file mode 100644
index 0000000..31abdc7
--- /dev/null
+++ b/libs/apkverify/tests/data/v3-only-with-rsa-pkcs1-sha256-16384.apk.der
Binary files differ
diff --git a/libs/apkverify/tests/data/v3-only-with-rsa-pkcs1-sha256-2048.apk.apk_digest b/libs/apkverify/tests/data/v3-only-with-rsa-pkcs1-sha256-2048.apk.apk_digest
new file mode 100644
index 0000000..c5aec18
--- /dev/null
+++ b/libs/apkverify/tests/data/v3-only-with-rsa-pkcs1-sha256-2048.apk.apk_digest
Binary files differ
diff --git a/libs/apkverify/tests/data/v3-only-with-rsa-pkcs1-sha256-2048.apk.der b/libs/apkverify/tests/data/v3-only-with-rsa-pkcs1-sha256-2048.apk.der
new file mode 100644
index 0000000..96dc543
--- /dev/null
+++ b/libs/apkverify/tests/data/v3-only-with-rsa-pkcs1-sha256-2048.apk.der
Binary files differ
diff --git a/libs/apkverify/tests/data/v3-only-with-rsa-pkcs1-sha256-3072.apk.apk_digest b/libs/apkverify/tests/data/v3-only-with-rsa-pkcs1-sha256-3072.apk.apk_digest
new file mode 100644
index 0000000..c5aec18
--- /dev/null
+++ b/libs/apkverify/tests/data/v3-only-with-rsa-pkcs1-sha256-3072.apk.apk_digest
Binary files differ
diff --git a/libs/apkverify/tests/data/v3-only-with-rsa-pkcs1-sha256-3072.apk.der b/libs/apkverify/tests/data/v3-only-with-rsa-pkcs1-sha256-3072.apk.der
new file mode 100644
index 0000000..bd70f5f
--- /dev/null
+++ b/libs/apkverify/tests/data/v3-only-with-rsa-pkcs1-sha256-3072.apk.der
Binary files differ
diff --git a/libs/apkverify/tests/data/v3-only-with-rsa-pkcs1-sha256-4096.apk.apk_digest b/libs/apkverify/tests/data/v3-only-with-rsa-pkcs1-sha256-4096.apk.apk_digest
new file mode 100644
index 0000000..c5aec18
--- /dev/null
+++ b/libs/apkverify/tests/data/v3-only-with-rsa-pkcs1-sha256-4096.apk.apk_digest
Binary files differ
diff --git a/libs/apkverify/tests/data/v3-only-with-rsa-pkcs1-sha256-4096.apk.der b/libs/apkverify/tests/data/v3-only-with-rsa-pkcs1-sha256-4096.apk.der
new file mode 100644
index 0000000..951648e
--- /dev/null
+++ b/libs/apkverify/tests/data/v3-only-with-rsa-pkcs1-sha256-4096.apk.der
Binary files differ
diff --git a/libs/apkverify/tests/data/v3-only-with-rsa-pkcs1-sha256-8192.apk.apk_digest b/libs/apkverify/tests/data/v3-only-with-rsa-pkcs1-sha256-8192.apk.apk_digest
new file mode 100644
index 0000000..c5aec18
--- /dev/null
+++ b/libs/apkverify/tests/data/v3-only-with-rsa-pkcs1-sha256-8192.apk.apk_digest
Binary files differ
diff --git a/libs/apkverify/tests/data/v3-only-with-rsa-pkcs1-sha256-8192.apk.der b/libs/apkverify/tests/data/v3-only-with-rsa-pkcs1-sha256-8192.apk.der
new file mode 100644
index 0000000..15e5edf
--- /dev/null
+++ b/libs/apkverify/tests/data/v3-only-with-rsa-pkcs1-sha256-8192.apk.der
Binary files differ
diff --git a/libs/apkverify/tests/data/v3-only-with-rsa-pkcs1-sha512-1024.apk.apk_digest b/libs/apkverify/tests/data/v3-only-with-rsa-pkcs1-sha512-1024.apk.apk_digest
new file mode 100644
index 0000000..4fe1b65
--- /dev/null
+++ b/libs/apkverify/tests/data/v3-only-with-rsa-pkcs1-sha512-1024.apk.apk_digest
Binary files differ
diff --git a/libs/apkverify/tests/data/v3-only-with-rsa-pkcs1-sha512-1024.apk.der b/libs/apkverify/tests/data/v3-only-with-rsa-pkcs1-sha512-1024.apk.der
new file mode 100644
index 0000000..6aafd09
--- /dev/null
+++ b/libs/apkverify/tests/data/v3-only-with-rsa-pkcs1-sha512-1024.apk.der
Binary files differ
diff --git a/libs/apkverify/tests/data/v3-only-with-rsa-pkcs1-sha512-16384.apk.apk_digest b/libs/apkverify/tests/data/v3-only-with-rsa-pkcs1-sha512-16384.apk.apk_digest
new file mode 100644
index 0000000..4fe1b65
--- /dev/null
+++ b/libs/apkverify/tests/data/v3-only-with-rsa-pkcs1-sha512-16384.apk.apk_digest
Binary files differ
diff --git a/libs/apkverify/tests/data/v3-only-with-rsa-pkcs1-sha512-16384.apk.der b/libs/apkverify/tests/data/v3-only-with-rsa-pkcs1-sha512-16384.apk.der
new file mode 100644
index 0000000..31abdc7
--- /dev/null
+++ b/libs/apkverify/tests/data/v3-only-with-rsa-pkcs1-sha512-16384.apk.der
Binary files differ
diff --git a/libs/apkverify/tests/data/v3-only-with-rsa-pkcs1-sha512-2048.apk.apk_digest b/libs/apkverify/tests/data/v3-only-with-rsa-pkcs1-sha512-2048.apk.apk_digest
new file mode 100644
index 0000000..4fe1b65
--- /dev/null
+++ b/libs/apkverify/tests/data/v3-only-with-rsa-pkcs1-sha512-2048.apk.apk_digest
Binary files differ
diff --git a/libs/apkverify/tests/data/v3-only-with-rsa-pkcs1-sha512-2048.apk.der b/libs/apkverify/tests/data/v3-only-with-rsa-pkcs1-sha512-2048.apk.der
new file mode 100644
index 0000000..96dc543
--- /dev/null
+++ b/libs/apkverify/tests/data/v3-only-with-rsa-pkcs1-sha512-2048.apk.der
Binary files differ
diff --git a/libs/apkverify/tests/data/v3-only-with-rsa-pkcs1-sha512-3072.apk.apk_digest b/libs/apkverify/tests/data/v3-only-with-rsa-pkcs1-sha512-3072.apk.apk_digest
new file mode 100644
index 0000000..4fe1b65
--- /dev/null
+++ b/libs/apkverify/tests/data/v3-only-with-rsa-pkcs1-sha512-3072.apk.apk_digest
Binary files differ
diff --git a/libs/apkverify/tests/data/v3-only-with-rsa-pkcs1-sha512-3072.apk.der b/libs/apkverify/tests/data/v3-only-with-rsa-pkcs1-sha512-3072.apk.der
new file mode 100644
index 0000000..bd70f5f
--- /dev/null
+++ b/libs/apkverify/tests/data/v3-only-with-rsa-pkcs1-sha512-3072.apk.der
Binary files differ
diff --git a/libs/apkverify/tests/data/v3-only-with-rsa-pkcs1-sha512-4096.apk.apk_digest b/libs/apkverify/tests/data/v3-only-with-rsa-pkcs1-sha512-4096.apk.apk_digest
new file mode 100644
index 0000000..4fe1b65
--- /dev/null
+++ b/libs/apkverify/tests/data/v3-only-with-rsa-pkcs1-sha512-4096.apk.apk_digest
Binary files differ
diff --git a/libs/apkverify/tests/data/v3-only-with-rsa-pkcs1-sha512-4096.apk.der b/libs/apkverify/tests/data/v3-only-with-rsa-pkcs1-sha512-4096.apk.der
new file mode 100644
index 0000000..951648e
--- /dev/null
+++ b/libs/apkverify/tests/data/v3-only-with-rsa-pkcs1-sha512-4096.apk.der
Binary files differ
diff --git a/libs/apkverify/tests/data/v3-only-with-rsa-pkcs1-sha512-8192.apk.apk_digest b/libs/apkverify/tests/data/v3-only-with-rsa-pkcs1-sha512-8192.apk.apk_digest
new file mode 100644
index 0000000..4fe1b65
--- /dev/null
+++ b/libs/apkverify/tests/data/v3-only-with-rsa-pkcs1-sha512-8192.apk.apk_digest
Binary files differ
diff --git a/libs/apkverify/tests/data/v3-only-with-rsa-pkcs1-sha512-8192.apk.der b/libs/apkverify/tests/data/v3-only-with-rsa-pkcs1-sha512-8192.apk.der
new file mode 100644
index 0000000..15e5edf
--- /dev/null
+++ b/libs/apkverify/tests/data/v3-only-with-rsa-pkcs1-sha512-8192.apk.der
Binary files differ
diff --git a/libs/apkverify/tests/data/v3-only-with-stamp.apk.apk_digest b/libs/apkverify/tests/data/v3-only-with-stamp.apk.apk_digest
new file mode 100644
index 0000000..a29c2a9
--- /dev/null
+++ b/libs/apkverify/tests/data/v3-only-with-stamp.apk.apk_digest
@@ -0,0 +1 @@
+bk¶GÀ—§ÿ¥/ØèEù@=^'÷¥¨u.G³4_¸/\
\ No newline at end of file
diff --git a/libs/apkverify/tests/data/v3-only-with-stamp.apk.der b/libs/apkverify/tests/data/v3-only-with-stamp.apk.der
new file mode 100644
index 0000000..01927af
--- /dev/null
+++ b/libs/apkverify/tests/data/v3-only-with-stamp.apk.der
Binary files differ
diff --git a/libs/idsig/src/apksigv4.rs b/libs/idsig/src/apksigv4.rs
index f8ca184..c1b6495 100644
--- a/libs/idsig/src/apksigv4.rs
+++ b/libs/idsig/src/apksigv4.rs
@@ -15,7 +15,7 @@
  */
 
 use anyhow::{anyhow, bail, Context, Result};
-use apkverify::pick_v4_apk_digest;
+use apkverify::{pick_v4_apk_digest, SignatureAlgorithmID};
 use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
 use num_derive::{FromPrimitive, ToPrimitive};
 use num_traits::{FromPrimitive, ToPrimitive};
@@ -69,7 +69,7 @@
     /// Public key of the signer in ASN.1 DER form. This must match the `x509_certificate` field.
     pub public_key: Box<[u8]>,
     /// Signature algorithm used to sign this file.
-    pub signature_algorithm_id: SignatureAlgorithmId,
+    pub signature_algorithm_id: SignatureAlgorithmID,
     /// The signature of this file.
     pub signature: Box<[u8]>,
 }
@@ -114,40 +114,6 @@
     }
 }
 
-/// Signature algorithm that can be used for idsig file
-#[derive(Debug, PartialEq, Eq, FromPrimitive, ToPrimitive)]
-#[allow(non_camel_case_types)]
-#[repr(u32)]
-pub enum SignatureAlgorithmId {
-    /// RSASSA-PSS with SHA2-256 digest, SHA2-256 MGF1, 32 bytes of salt, trailer: 0xbc
-    RSASSA_PSS_SHA2_256 = 0x0101,
-    /// RSASSA-PSS with SHA2-512 digest, SHA2-512 MGF1, 64 bytes of salt, trailer: 0xbc
-    RSASSA_PSS_SHA2_512 = 0x0102,
-    /// RSASSA-PKCS1-v1_5 with SHA2-256 digest.
-    RSASSA_PKCS1_SHA2_256 = 0x0103,
-    /// RSASSA-PKCS1-v1_5 with SHA2-512 digest.
-    RSASSA_PKCS1_SHA2_512 = 0x0104,
-    /// ECDSA with SHA2-256 digest.
-    ECDSA_SHA2_256 = 0x0201,
-    /// ECDSA with SHA2-512 digest.
-    ECDSA_SHA2_512 = 0x0202,
-    /// DSA with SHA2-256 digest
-    DSA_SHA2_256 = 0x0301,
-}
-
-impl SignatureAlgorithmId {
-    fn from(val: u32) -> Result<SignatureAlgorithmId> {
-        Self::from_u32(val)
-            .with_context(|| format!("{:#06x} is an unsupported signature algorithm", val))
-    }
-}
-
-impl Default for SignatureAlgorithmId {
-    fn default() -> Self {
-        SignatureAlgorithmId::DSA_SHA2_256
-    }
-}
-
 impl<R: Read + Seek> V4Signature<R> {
     /// Consumes a stream for an idsig file into a `V4Signature` struct.
     pub fn from(mut r: R) -> Result<V4Signature<R>> {
@@ -193,8 +159,7 @@
 
         apk.seek(SeekFrom::Start(start))?;
         let (signature_algorithm_id, apk_digest) = pick_v4_apk_digest(apk)?;
-        ret.signing_info.signature_algorithm_id =
-            SignatureAlgorithmId::from(signature_algorithm_id)?;
+        ret.signing_info.signature_algorithm_id = signature_algorithm_id;
         ret.signing_info.apk_digest = apk_digest;
         // TODO(jiyong): add a signature to the signing_info struct
 
@@ -276,7 +241,8 @@
             x509_certificate: read_sized_array(&mut r)?,
             additional_data: read_sized_array(&mut r)?,
             public_key: read_sized_array(&mut r)?,
-            signature_algorithm_id: SignatureAlgorithmId::from(r.read_u32::<LittleEndian>()?)?,
+            signature_algorithm_id: SignatureAlgorithmID::from_u32(r.read_u32::<LittleEndian>()?)
+                .context("Unsupported signature algorithm")?,
             signature: read_sized_array(&mut r)?,
         })
     }
@@ -291,7 +257,7 @@
         write_sized_array(&mut w, &self.x509_certificate)?;
         write_sized_array(&mut w, &self.additional_data)?;
         write_sized_array(&mut w, &self.public_key)?;
-        w.write_u32::<LittleEndian>(self.signature_algorithm_id.to_u32().unwrap())?;
+        w.write_u32::<LittleEndian>(self.signature_algorithm_id.to_u32())?;
         write_sized_array(&mut w, &self.signature)?;
 
         // Determine the size of signing_info, and write it in front of the struct where the value
@@ -358,7 +324,7 @@
                    a8585c38d7f654835eb219ae9e176b44e86dcb23153e3d9d6",
             hexstring_from(si.signature.as_ref())
         );
-        assert_eq!(SignatureAlgorithmId::DSA_SHA2_256, si.signature_algorithm_id);
+        assert_eq!(SignatureAlgorithmID::DsaWithSha256, si.signature_algorithm_id);
 
         assert_eq!(36864, parsed.merkle_tree_size);
         assert_eq!(2251, parsed.merkle_tree_offset);
diff --git a/microdroid/Android.bp b/microdroid/Android.bp
index c6ba1db..7b28ad2 100644
--- a/microdroid/Android.bp
+++ b/microdroid/Android.bp
@@ -57,8 +57,6 @@
         "libbinder",
         "libbinder_ndk",
         "libstdc++",
-        "logcat.microdroid",
-        "logd.microdroid",
         "secilc",
 
         // "com.android.adbd" requires these,
diff --git a/microdroid/bootconfig.app_debuggable b/microdroid/bootconfig.app_debuggable
index 0d85186..6e66371 100644
--- a/microdroid/bootconfig.app_debuggable
+++ b/microdroid/bootconfig.app_debuggable
@@ -13,7 +13,3 @@
 
 # ADB is supported but rooting is prohibited.
 androidboot.adb.enabled=1
-
-# logd is enabled
-# TODO(b/200914564) Filter only the log from the app
-androidboot.logd.enabled=1
diff --git a/microdroid/bootconfig.full_debuggable b/microdroid/bootconfig.full_debuggable
index 0bdd810..583060b 100644
--- a/microdroid/bootconfig.full_debuggable
+++ b/microdroid/bootconfig.full_debuggable
@@ -12,6 +12,3 @@
 # ro.adb.secure is still 0 (see build.prop) which means that adbd is started
 # unrooted by default. To root, developer should explicitly execute `adb root`.
 androidboot.adb.enabled=1
-
-# logd is enabled
-androidboot.logd.enabled=1
diff --git a/microdroid/bootconfig.normal b/microdroid/bootconfig.normal
index ea83287..ec85f0d 100644
--- a/microdroid/bootconfig.normal
+++ b/microdroid/bootconfig.normal
@@ -12,6 +12,3 @@
 
 # ADB is not enabled.
 androidboot.adb.enabled=0
-
-# logd is not enabled
-androidboot.logd.enabled=0
diff --git a/microdroid/init.rc b/microdroid/init.rc
index 42aa983..cd7332b 100644
--- a/microdroid/init.rc
+++ b/microdroid/init.rc
@@ -17,6 +17,14 @@
 
     start ueventd
 
+# If VM is debuggable, send logs to outside ot the VM via the serial console.
+# If non-debuggable, logs are internally consumed at /dev/null
+on early-init && property:ro.boot.microdroid.app_debuggable=1
+    setprop ro.log.file_logger.path /dev/hvc2
+
+on early-init && property:ro.boot.microdroid.app_debuggable=0
+    setprop ro.log.file_logger.path /dev/null
+
 on init
     # Mount binderfs
     mkdir /dev/binderfs
@@ -62,12 +70,6 @@
     start vendor.dice-microdroid
     start diced
 
-on init && property:ro.boot.logd.enabled=1
-    # Start logd before any other services run to ensure we capture all of their logs.
-    # TODO(b/217796229) set filterspec if debug_level is app_only
-    start logd
-    start seriallogging
-
 on init
     mkdir /mnt/apk 0755 system system
     mkdir /mnt/extra-apk 0755 root root
@@ -99,10 +101,6 @@
 on init && property:ro.boot.adb.enabled=1
     start adbd
 
-on load_persist_props_action && property:ro.boot.logd.enabled=1
-    start logd
-    start logd-reinit
-
 # Mount filesystems and start core system services.
 on late-init
     trigger early-fs
@@ -206,13 +204,3 @@
     seclabel u:r:shell:s0
     setenv HOSTNAME console
 
-service seriallogging /system/bin/logcat -b all -v threadtime -f /dev/hvc2 *:V
-    disabled
-    user logd
-    group root logd
-
-on fs
-    write /dev/event-log-tags "# content owned by logd
-"
-    chown logd logd /dev/event-log-tags
-    chmod 0644 /dev/event-log-tags
diff --git a/microdroid/ueventd.rc b/microdroid/ueventd.rc
index 340a1f7..fc165c8 100644
--- a/microdroid/ueventd.rc
+++ b/microdroid/ueventd.rc
@@ -26,6 +26,6 @@
 /dev/tty0                 0660   root       system
 
 # Virtual console for logcat
-/dev/hvc2                 0660   logd       logd
+/dev/hvc2                 0666   system     system
 
 /dev/open-dice0           0660   diced      diced
diff --git a/tests/benchmark_hostside/java/android/avf/test/AVFHostTestCase.java b/tests/benchmark_hostside/java/android/avf/test/AVFHostTestCase.java
index eeadae1..e5eee27 100644
--- a/tests/benchmark_hostside/java/android/avf/test/AVFHostTestCase.java
+++ b/tests/benchmark_hostside/java/android/avf/test/AVFHostTestCase.java
@@ -39,6 +39,7 @@
 import org.junit.runner.RunWith;
 
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.regex.Matcher;
@@ -56,6 +57,11 @@
     private static final String SYSTEM_SERVER_COMPILER_FILTER_PROP_NAME =
             "dalvik.vm.systemservercompilerfilter";
 
+    private static final String BOOTLOADER_TIME_PROP_NAME = "ro.boot.boottime";
+    private static final String BOOTLOADER_PREFIX = "bootloader-";
+    private static final String BOOTLOADER_TIME = "bootloader_time";
+    private static final String BOOTLOADER_PHASE_SW = "SW";
+
     /** Boot time test related variables */
     private static final int REINSTALL_APEX_RETRY_INTERVAL_MS = 5 * 1000;
     private static final int REINSTALL_APEX_TIMEOUT_SEC = 15;
@@ -89,72 +95,124 @@
     }
 
     @Test
-    public void testBootEnableAndDisablePKVM() throws Exception {
-        skipIfPKVMStatusSwitchNotSupported();
-
-        List<Double> bootWithPKVMEnableTime = new ArrayList<>(ROUND_COUNT);
-        List<Double> bootWithoutPKVMEnableTime = new ArrayList<>(ROUND_COUNT);
-
-        for (int round = 0; round < ROUND_COUNT; ++round) {
-
-            setPKVMStatusWithRebootToBootloader(true);
-            long start = System.nanoTime();
-            rebootFromBootloaderAndWaitBootCompleted();
-            long elapsedWithPKVMEnable = System.nanoTime() - start;
-            double elapsedSec = elapsedWithPKVMEnable / NANOS_IN_SEC;
-            bootWithPKVMEnableTime.add(elapsedSec);
-            CLog.i("Boot time with PKVM enable took " + elapsedSec + "s");
-
-            setPKVMStatusWithRebootToBootloader(false);
-            start = System.nanoTime();
-            rebootFromBootloaderAndWaitBootCompleted();
-            long elapsedWithoutPKVMEnable = System.nanoTime() - start;
-            elapsedSec = elapsedWithoutPKVMEnable / NANOS_IN_SEC;
-            bootWithoutPKVMEnableTime.add(elapsedSec);
-            CLog.i("Boot time with PKVM disable took " + elapsedSec + "s");
-        }
-
-        reportMetric(bootWithPKVMEnableTime, "boot_time_with_pkvm_enable", "s");
-        reportMetric(bootWithoutPKVMEnableTime, "boot_time_with_pkvm_disable", "s");
+    public void testBootEnablePKVM() throws Exception {
+        enableDisablePKVMTestHelper(true);
     }
 
     @Test
-    public void testBootWithAndWithoutCompOS() throws Exception {
-        assumeFalse(isCuttlefish());
+    public void testBootDisablePKVM() throws Exception {
+        enableDisablePKVMTestHelper(false);
+    }
 
-        List<Double> bootWithCompOsTime = new ArrayList<>(ROUND_COUNT);
-        List<Double> bootWithoutCompOsTime = new ArrayList<>(ROUND_COUNT);
+    @Test
+    public void testBootWithCompOS() throws Exception {
+        composTestHelper(true);
+    }
 
+    @Test
+    public void testBootWithoutCompOS() throws Exception {
+        composTestHelper(false);
+    }
+
+    private void updateBootloaderTimeInfo(Map<String, List<Double>> bootloaderTime)
+            throws Exception {
+
+        String bootLoaderVal = getDevice().getProperty(BOOTLOADER_TIME_PROP_NAME);
+        // Sample Output : 1BLL:89,1BLE:590,2BLL:0,2BLE:1344,SW:6734,KL:1193
+        if (bootLoaderVal != null) {
+            String[] bootLoaderPhases = bootLoaderVal.split(",");
+            double bootLoaderTotalTime = 0d;
+            for (String bootLoaderPhase : bootLoaderPhases) {
+                String[] bootKeyVal = bootLoaderPhase.split(":");
+                String key = String.format("%s%s", BOOTLOADER_PREFIX, bootKeyVal[0]);
+
+                bootloaderTime.computeIfAbsent(key,
+                        k -> new ArrayList<>()).add(Double.parseDouble(bootKeyVal[1]));
+                // SW is the time spent on the warning screen. So ignore it in
+                // final boot time calculation.
+                if (BOOTLOADER_PHASE_SW.equalsIgnoreCase(bootKeyVal[0])) {
+                    continue;
+                }
+                bootLoaderTotalTime += Double.parseDouble(bootKeyVal[1]);
+            }
+            bootloaderTime.computeIfAbsent(BOOTLOADER_TIME,
+                    k -> new ArrayList<>()).add(bootLoaderTotalTime);
+        }
+    }
+
+    private Double getDmesgBootTime() throws Exception {
+
+        CommandRunner android = new CommandRunner(getDevice());
+        String result = android.run("dmesg");
+        Pattern pattern = Pattern.compile("\\[(.*)\\].*sys.boot_completed=1.*");
+        for (String line : result.split("[\r\n]+")) {
+            Matcher matcher = pattern.matcher(line);
+            if (matcher.find()) {
+                return Double.valueOf(matcher.group(1));
+            }
+        }
+        throw new IllegalArgumentException("Failed to get boot time info.");
+    }
+
+    private void enableDisablePKVMTestHelper(boolean isEnable) throws Exception {
+        skipIfPKVMStatusSwitchNotSupported();
+
+        List<Double> bootDmesgTime = new ArrayList<>(ROUND_COUNT);
+        Map<String, List<Double>> bootloaderTime = new HashMap<>();
+
+        setPKVMStatusWithRebootToBootloader(isEnable);
+        rebootFromBootloaderAndWaitBootCompleted();
         for (int round = 0; round < ROUND_COUNT; ++round) {
-
-            // Boot time with compilation OS test.
-            reInstallApex(REINSTALL_APEX_TIMEOUT_SEC);
-            compileStagedApex(COMPILE_STAGED_APEX_TIMEOUT_SEC);
             getDevice().nonBlockingReboot();
-            long start = System.nanoTime();
             waitForBootCompleted();
-            long elapsedWithCompOS = System.nanoTime() - start;
-            double elapsedSec = elapsedWithCompOS / NANOS_IN_SEC;
-            bootWithCompOsTime.add(elapsedSec);
-            CLog.i("Boot time with compilation OS took " + elapsedSec + "s");
 
-            // Boot time without compilation OS test.
-            reInstallApex(REINSTALL_APEX_TIMEOUT_SEC);
-            getDevice().nonBlockingReboot();
-            start = System.nanoTime();
-            waitForBootCompleted();
-            long elapsedWithoutCompOS = System.nanoTime() - start;
-            elapsedSec = elapsedWithoutCompOS / NANOS_IN_SEC;
-            bootWithoutCompOsTime.add(elapsedSec);
-            CLog.i("Boot time without compilation OS took " + elapsedSec + "s");
+            updateBootloaderTimeInfo(bootloaderTime);
+
+            double elapsedSec = getDmesgBootTime();
+            bootDmesgTime.add(elapsedSec);
         }
 
-        reportMetric(bootWithCompOsTime, "boot_time_with_compos", "s");
-        reportMetric(bootWithoutCompOsTime, "boot_time_without_compos", "s");
+        String suffix = "";
+        if (isEnable) {
+            suffix = "enable";
+        } else {
+            suffix = "disable";
+        }
+
+        reportMetric(bootDmesgTime, "dmesg_boot_time_with_pkvm_" + suffix, "s");
+        reportAggregatedMetrics(bootloaderTime,
+                "bootloader_time_with_pkvm_" + suffix, "ms");
+    }
+
+    private void composTestHelper(boolean isWithCompos) throws Exception {
+        assumeFalse("Skip on CF; too slow", isCuttlefish());
+
+        List<Double> bootDmesgTime = new ArrayList<>(ROUND_COUNT);
+
+        for (int round = 0; round < ROUND_COUNT; ++round) {
+            reInstallApex(REINSTALL_APEX_TIMEOUT_SEC);
+            if (isWithCompos) {
+                compileStagedApex(COMPILE_STAGED_APEX_TIMEOUT_SEC);
+            }
+            getDevice().nonBlockingReboot();
+            waitForBootCompleted();
+
+            double elapsedSec = getDmesgBootTime();
+            bootDmesgTime.add(elapsedSec);
+        }
+
+        String suffix = "";
+        if (isWithCompos) {
+            suffix = "with_compos";
+        } else {
+            suffix = "without_compos";
+        }
+
+        reportMetric(bootDmesgTime, "dmesg_boot_time_" + suffix, "s");
     }
 
     private void skipIfPKVMStatusSwitchNotSupported() throws Exception {
-        assumeFalse(isCuttlefish());
+        assumeFalse("Skip on CF; can't reboot to bootloader", isCuttlefish());
 
         if (!getDevice().isStateBootloaderOrFastbootd()) {
             getDevice().rebootIntoBootloader();
@@ -171,12 +229,22 @@
     }
 
     private void reportMetric(List<Double> data, String name, String unit) {
+        CLog.d("Report metric " + name + "(" + unit + ") : " + data.toString());
         Map<String, Double> stats = mMetricsProcessor.computeStats(data, name, unit);
         for (Map.Entry<String, Double> entry : stats.entrySet()) {
+            CLog.d("Add test metrics " + entry.getKey() + " : " + entry.getValue().toString());
             mMetrics.addTestMetric(entry.getKey(), entry.getValue().toString());
         }
     }
 
+    private void reportAggregatedMetrics(Map<String, List<Double>> bootloaderTime,
+            String prefix, String unit) {
+
+        for (Map.Entry<String, List<Double>> entry : bootloaderTime.entrySet()) {
+            reportMetric(entry.getValue(), prefix + "_" + entry.getKey(), unit);
+        }
+    }
+
     private void setPKVMStatusWithRebootToBootloader(boolean isEnable) throws Exception {
 
         if (!getDevice().isStateBootloaderOrFastbootd()) {
diff --git a/tests/helper/src/java/com/android/microdroid/test/common/MetricsProcessor.java b/tests/helper/src/java/com/android/microdroid/test/common/MetricsProcessor.java
index c12b07d..41534f1 100644
--- a/tests/helper/src/java/com/android/microdroid/test/common/MetricsProcessor.java
+++ b/tests/helper/src/java/com/android/microdroid/test/common/MetricsProcessor.java
@@ -33,18 +33,21 @@
      * a {@link Map} with the corresponding keys equal to [mPrefix + name +
      * _[min|max|average|stdev]_ + unit].
      */
-    public Map<String, Double> computeStats(List<Double> metrics, String name, String unit) {
+    public Map<String, Double> computeStats(List<? extends Number> metrics, String name,
+            String unit) {
         double sum = 0;
         double min = Double.MAX_VALUE;
         double max = Double.MIN_VALUE;
-        for (double d : metrics) {
+        for (Number metric : metrics) {
+            double d = metric.doubleValue();
             sum += d;
             if (min > d) min = d;
             if (max < d) max = d;
         }
         double avg = sum / metrics.size();
         double sqSum = 0;
-        for (double d : metrics) {
+        for (Number metric : metrics) {
+            double d = metric.doubleValue();
             sqSum += (d - avg) * (d - avg);
         }
         double stdDev = Math.sqrt(sqSum / (metrics.size() - 1));
diff --git a/tests/helper/src/java/com/android/microdroid/test/common/ProcessUtil.java b/tests/helper/src/java/com/android/microdroid/test/common/ProcessUtil.java
new file mode 100644
index 0000000..c5aad6e
--- /dev/null
+++ b/tests/helper/src/java/com/android/microdroid/test/common/ProcessUtil.java
@@ -0,0 +1,78 @@
+/*
+ * 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.microdroid.test.common;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Function;
+
+/** This class provides process utility for both device tests and host tests. */
+public final class ProcessUtil {
+
+    /** Gets metrics key and values mapping of specified process id */
+    public static Map<String, Long> getProcessSmapsRollup(
+            int pid, Function<String, String> shellExecutor) throws IOException {
+        String path = "/proc/" + pid + "/smaps_rollup";
+        return parseMemoryInfo(skipFirstLine(shellExecutor.apply("cat " + path + " || true")));
+    }
+
+    /** Gets process id and process name mapping of the device */
+    public static Map<Integer, String> getProcessMap(Function<String, String> shellExecutor)
+            throws IOException {
+        Map<Integer, String> processMap = new HashMap<>();
+        for (String ps : skipFirstLine(shellExecutor.apply("ps -Ao PID,NAME")).split("\n")) {
+            // Each line is '<pid> <name>'.
+            // EX : 11424 dex2oat64
+            ps = ps.trim();
+            if (ps.length() == 0) {
+                continue;
+            }
+            int space = ps.indexOf(" ");
+            String pName = ps.substring(space + 1);
+            int pId = Integer.parseInt(ps.substring(0, space));
+            processMap.put(pId, pName);
+        }
+
+        return processMap;
+    }
+
+    // To ensures that only one object is created at a time.
+    private ProcessUtil() {}
+
+    private static Map<String, Long> parseMemoryInfo(String file) {
+        Map<String, Long> stats = new HashMap<>();
+        for (String line : file.split("[\r\n]+")) {
+            line = line.trim();
+            if (line.length() == 0) {
+                continue;
+            }
+            // Each line is '<metrics>:        <number> kB'.
+            // EX : Pss_Anon:        70712 kB
+            if (line.endsWith(" kB")) line = line.substring(0, line.length() - 3);
+
+            String[] elems = line.split(":");
+            stats.put(elems[0].trim(), Long.parseLong(elems[1].trim()));
+        }
+        return stats;
+    }
+
+    private static String skipFirstLine(String str) {
+        int index = str.indexOf("\n");
+        return (index < 0) ? "" : str.substring(index + 1);
+    }
+}
diff --git a/tests/hostside/helper/java/com/android/microdroid/test/host/MicrodroidHostTestCaseBase.java b/tests/hostside/helper/java/com/android/microdroid/test/host/MicrodroidHostTestCaseBase.java
index 7a9e2ea..875d89f 100644
--- a/tests/hostside/helper/java/com/android/microdroid/test/host/MicrodroidHostTestCaseBase.java
+++ b/tests/hostside/helper/java/com/android/microdroid/test/host/MicrodroidHostTestCaseBase.java
@@ -367,10 +367,11 @@
         runOnHostRetryingOnFailure(MICRODROID_COMMAND_TIMEOUT_MILLIS,
                 MICRODROID_ADB_CONNECT_MAX_ATTEMPTS, "adb", "-s", MICRODROID_SERIAL, "root");
         // adbd reboots after root. Some commands (including wait-for-device) following this fails
-        // with error: closed. Hence, we run adb shell true in microdroid with retries
-        // before returning.
-        runOnMicrodroidRetryingOnFailure(MICRODROID_COMMAND_TIMEOUT_MILLIS,
-                MICRODROID_ADB_CONNECT_MAX_ATTEMPTS, "true");
+        // with error: closed. Hence, we disconnect and re-connect to the device before returning.
+        runOnHostRetryingOnFailure(MICRODROID_COMMAND_TIMEOUT_MILLIS,
+                MICRODROID_ADB_CONNECT_MAX_ATTEMPTS, "adb", "disconnect", MICRODROID_SERIAL);
+        runOnHostRetryingOnFailure(MICRODROID_COMMAND_TIMEOUT_MILLIS,
+                MICRODROID_ADB_CONNECT_MAX_ATTEMPTS, "adb", "connect", MICRODROID_SERIAL);
     }
 
     // Establish an adb connection to microdroid by letting Android forward the connection to
diff --git a/tests/hostside/java/com/android/microdroid/test/MicrodroidTestCase.java b/tests/hostside/java/com/android/microdroid/test/MicrodroidTestCase.java
index d016a67..69218a8 100644
--- a/tests/hostside/java/com/android/microdroid/test/MicrodroidTestCase.java
+++ b/tests/hostside/java/com/android/microdroid/test/MicrodroidTestCase.java
@@ -708,6 +708,11 @@
         }
     }
 
+    // TODO(b/6184548): to be replaced by ProcessUtil
+    /**
+    * @deprecated use ProcessUtil instead.
+    */
+    @Deprecated
     private Map<String, Long> parseMemInfo(String file) {
         Map<String, Long> stats = new HashMap<>();
         file.lines().forEach(line -> {
@@ -720,11 +725,21 @@
         return stats;
     }
 
+    // TODO(b/6184548): to be replaced by ProcessUtil
+    /**
+    * @deprecated use ProcessUtil instead.
+    */
+    @Deprecated
     private String skipFirstLine(String str) {
         int index = str.indexOf("\n");
         return (index < 0) ? "" : str.substring(index + 1);
     }
 
+    // TODO(b/6184548): to be replaced by ProcessUtil
+    /**
+    * @deprecated use ProcessUtil instead.
+    */
+    @Deprecated
     private List<ProcessInfo> getRunningProcessesList() {
         List<ProcessInfo> list = new ArrayList<ProcessInfo>();
         skipFirstLine(runOnMicrodroid("ps", "-Ao", "PID,NAME")).lines().forEach(ps -> {
@@ -739,10 +754,20 @@
         return list;
     }
 
+    // TODO(b/6184548): to be replaced by ProcessUtil
+    /**
+    * @deprecated use ProcessUtil instead.
+    */
+    @Deprecated
     private Map<String, Long> getProcMemInfo() {
         return parseMemInfo(runOnMicrodroid("cat", "/proc/meminfo"));
     }
 
+    // TODO(b/6184548): to be replaced by ProcessUtil
+    /**
+    * @deprecated use ProcessUtil instead.
+    */
+    @Deprecated
     private Map<String, Long> getProcSmapsRollup(int pid) {
         String path = "/proc/" + pid + "/smaps_rollup";
         return  parseMemInfo(skipFirstLine(runOnMicrodroid("cat", path, "||", "true")));
diff --git a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
index 99afe98..c2060cb 100644
--- a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
+++ b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
@@ -504,4 +504,21 @@
         assertThat(bootResult.deathReason).isEqualTo(
                 VirtualMachineCallback.DEATH_REASON_MICRODROID_INVALID_PAYLOAD_CONFIG);
     }
+
+    @Test
+    public void sameInstancesShareTheSameVmObject()
+            throws VirtualMachineException, InterruptedException, IOException {
+        VirtualMachineConfig.Builder builder =
+                mInner.newVmConfigBuilder("assets/vm_config.json");
+        VirtualMachineConfig normalConfig = builder.debugLevel(DebugLevel.NONE).build();
+        VirtualMachine vm = mInner.forceCreateNewVirtualMachine("test_vm", normalConfig);
+        VirtualMachine vm2 = mInner.getVirtualMachineManager().get("test_vm");
+        assertThat(vm).isEqualTo(vm2);
+
+        VirtualMachine newVm = mInner.forceCreateNewVirtualMachine("test_vm", normalConfig);
+        VirtualMachine newVm2 = mInner.getVirtualMachineManager().get("test_vm");
+        assertThat(newVm).isEqualTo(newVm2);
+
+        assertThat(vm).isNotEqualTo(newVm);
+    }
 }