Merge "Use console=ttynull to disable console"
diff --git a/compos/aidl/com/android/compos/ICompOsService.aidl b/compos/aidl/com/android/compos/ICompOsService.aidl
index 4df22da..9b45e13 100644
--- a/compos/aidl/com/android/compos/ICompOsService.aidl
+++ b/compos/aidl/com/android/compos/ICompOsService.aidl
@@ -44,12 +44,14 @@
     /**
      * Run odrefresh in the VM context.
      *
-     * The execution is based on the VM's APEX mounts, files on Android's /system (by accessing
-     * through systemDirFd over AuthFS), and *CLASSPATH derived in the VM, to generate the same
-     * odrefresh output artifacts to the output directory (through outputDirFd).
+     * The execution is based on the VM's APEX mounts, files on Android's /system and optionally
+     * /system_ext (by accessing through systemDirFd and systemExtDirFd over AuthFS), and
+     * *CLASSPATH derived in the VM, to generate the same odrefresh output artifacts to the output
+     * directory (through outputDirFd).
      *
      * @param compilationMode The type of compilation to be performed
      * @param systemDirFd An fd referring to /system
+     * @param systemExtDirFd An optional fd referring to /system_ext. Negative number means none.
      * @param outputDirFd An fd referring to the output directory, ART_APEX_DATA
      * @param stagingDirFd An fd referring to the staging directory, e.g. ART_APEX_DATA/staging
      * @param targetDirName The sub-directory of the output directory to which artifacts are to be
@@ -58,8 +60,8 @@
      * @param systemServerCompilerFilter The compiler filter used to compile system server
      * @return odrefresh exit code
      */
-    byte odrefresh(CompilationMode compilation_mode, int systemDirFd, int outputDirFd,
-            int stagingDirFd, String targetDirName, String zygoteArch,
+    byte odrefresh(CompilationMode compilation_mode, int systemDirFd, int systemExtDirFd,
+            int outputDirFd, int stagingDirFd, String targetDirName, String zygoteArch,
             String systemServerCompilerFilter);
 
     /**
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 996d32a..dd113a6 100644
--- a/compos/benchmark/src/java/com/android/compos/benchmark/ComposBenchmark.java
+++ b/compos/benchmark/src/java/com/android/compos/benchmark/ComposBenchmark.java
@@ -23,7 +23,6 @@
 
 import android.app.Instrumentation;
 import android.os.Bundle;
-import android.os.ParcelFileDescriptor;
 import android.util.Log;
 
 import com.android.microdroid.test.common.MetricsProcessor;
@@ -36,9 +35,7 @@
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
 
-import java.io.ByteArrayOutputStream;
 import java.io.IOException;
-import java.io.InputStream;
 import java.sql.Timestamp;
 import java.text.DateFormat;
 import java.text.ParseException;
@@ -198,37 +195,8 @@
         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);
-        }
+        return runInShell(TAG, mInstrumentation.getUiAutomation(), command);
     }
 
     private class GetMetricsRunnable implements Runnable {
diff --git a/compos/composd/src/odrefresh_task.rs b/compos/composd/src/odrefresh_task.rs
index a07a7f9..9276fb1 100644
--- a/compos/composd/src/odrefresh_task.rs
+++ b/compos/composd/src/odrefresh_task.rs
@@ -162,8 +162,7 @@
     let staging_dir_raw_fd = staging_dir_fd.as_raw_fd();
 
     // Get the /system_ext FD differently because it may not exist.
-    // TODO(245761690): pass system_ext_dir_raw_fd to service.odrefresh(...)
-    let (_system_ext_dir_raw_fd, ro_dir_fds) =
+    let (system_ext_dir_raw_fd, ro_dir_fds) =
         if let Ok(system_ext_dir_fd) = open_dir(Path::new("/system_ext")) {
             (system_ext_dir_fd.as_raw_fd(), vec![system_dir_fd, system_ext_dir_fd])
         } else {
@@ -184,6 +183,7 @@
     let exit_code = service.odrefresh(
         compilation_mode,
         system_dir_raw_fd,
+        system_ext_dir_raw_fd,
         output_dir_raw_fd,
         staging_dir_raw_fd,
         target_dir_name,
diff --git a/compos/src/compilation.rs b/compos/src/compilation.rs
index ab228e1..d165599 100644
--- a/compos/src/compilation.rs
+++ b/compos/src/compilation.rs
@@ -41,6 +41,7 @@
 pub struct OdrefreshContext<'a> {
     compilation_mode: CompilationMode,
     system_dir_fd: i32,
+    system_ext_dir_fd: Option<i32>,
     output_dir_fd: i32,
     staging_dir_fd: i32,
     target_dir_name: &'a str,
@@ -49,9 +50,11 @@
 }
 
 impl<'a> OdrefreshContext<'a> {
+    #[allow(clippy::too_many_arguments)]
     pub fn new(
         compilation_mode: CompilationMode,
         system_dir_fd: i32,
+        system_ext_dir_fd: Option<i32>,
         output_dir_fd: i32,
         staging_dir_fd: i32,
         target_dir_name: &'a str,
@@ -88,6 +91,7 @@
         Ok(Self {
             compilation_mode,
             system_dir_fd,
+            system_ext_dir_fd,
             output_dir_fd,
             staging_dir_fd,
             target_dir_name,
@@ -108,14 +112,25 @@
 {
     // Mount authfs (via authfs_service). The authfs instance unmounts once the `authfs` variable
     // is out of scope.
+
+    let mut input_dir_fd_annotations = vec![InputDirFdAnnotation {
+        fd: context.system_dir_fd,
+        // Use the 0th APK of the extra_apks in compos/apk/assets/vm_config*.json
+        manifestPath: "/mnt/extra-apk/0/assets/build_manifest.pb".to_string(),
+        prefix: "system/".to_string(),
+    }];
+    if let Some(fd) = context.system_ext_dir_fd {
+        input_dir_fd_annotations.push(InputDirFdAnnotation {
+            fd,
+            // Use the 1st APK of the extra_apks in compos/apk/assets/vm_config_system_ext_*.json
+            manifestPath: "/mnt/extra-apk/1/assets/build_manifest.pb".to_string(),
+            prefix: "system_ext/".to_string(),
+        });
+    }
+
     let authfs_config = AuthFsConfig {
         port: FD_SERVER_PORT,
-        inputDirFdAnnotations: vec![InputDirFdAnnotation {
-            fd: context.system_dir_fd,
-            // 0 is the index of extra_apks in vm_config_extra_apk.json
-            manifestPath: "/mnt/extra-apk/0/assets/build_manifest.pb".to_string(),
-            prefix: "system/".to_string(),
-        }],
+        inputDirFdAnnotations: input_dir_fd_annotations,
         outputDirFdAnnotations: vec![
             OutputDirFdAnnotation { fd: context.output_dir_fd },
             OutputDirFdAnnotation { fd: context.staging_dir_fd },
@@ -134,6 +149,14 @@
     odrefresh_vars.set("ANDROID_ROOT", path_to_str(&android_root)?);
     debug!("ANDROID_ROOT={:?}", &android_root);
 
+    if let Some(fd) = context.system_ext_dir_fd {
+        let mut system_ext_root = mountpoint.clone();
+        system_ext_root.push(fd.to_string());
+        system_ext_root.push("system_ext");
+        odrefresh_vars.set("SYSTEM_EXT_ROOT", path_to_str(&system_ext_root)?);
+        debug!("SYSTEM_EXT_ROOT={:?}", &system_ext_root);
+    }
+
     let art_apex_data = mountpoint.join(context.output_dir_fd.to_string());
     odrefresh_vars.set("ART_APEX_DATA", path_to_str(&art_apex_data)?);
     debug!("ART_APEX_DATA={:?}", &art_apex_data);
diff --git a/compos/src/compsvc.rs b/compos/src/compsvc.rs
index baf444e..7ce60cd 100644
--- a/compos/src/compsvc.rs
+++ b/compos/src/compsvc.rs
@@ -102,6 +102,7 @@
         &self,
         compilation_mode: CompilationMode,
         system_dir_fd: i32,
+        system_ext_dir_fd: i32,
         output_dir_fd: i32,
         staging_dir_fd: i32,
         target_dir_name: &str,
@@ -119,6 +120,7 @@
         let context = to_binder_result(OdrefreshContext::new(
             compilation_mode,
             system_dir_fd,
+            if system_ext_dir_fd >= 0 { Some(system_ext_dir_fd) } else { None },
             output_dir_fd,
             staging_dir_fd,
             target_dir_name,
diff --git a/docs/debug/ramdump.md b/docs/debug/ramdump.md
new file mode 100644
index 0000000..a0d9bf2
--- /dev/null
+++ b/docs/debug/ramdump.md
@@ -0,0 +1,159 @@
+# Doing RAM dump of a Microdroid VM and analyzing it
+
+A Microdroid VM creates a RAM dump of itself when the kernel panics. This
+document explains how the dump can be obtained and analyzed.
+
+## Force triggering a RAM dump
+
+RAM dump is created automatically when there's a kernel panic. However, for
+debugging purpose, you can forcibly trigger it via magic SysRq key.
+
+```shell
+$ adb shell /apex/com.android.virt/bin/vm run-app ...     // run a Microdroid VM
+$ m vm_shell; vm_shell                                    // connect to the VM
+# echo c > /proc/sysrq-trigger                            // force trigger a crash
+```
+
+Then you will see following message showing that crash is detected and the
+crashdump kernel is executed.
+
+```
+[   14.949892][  T148] sysrq: Trigger a crash
+[   14.952133][  T148] Kernel panic - not syncing: sysrq triggered crash
+[   14.955309][  T148] CPU: 0 PID: 148 Comm: sh Kdump: loaded Not tainted 5.15.60-android14-5-04357-gbac79d727aea-ab9013362 #1
+[   14.957803][  T148] Hardware name: linux,dummy-virt (DT)
+[   14.959053][  T148] Call trace:
+[   14.959809][  T148]  dump_backtrace.cfi_jt+0x0/0x8
+[   14.961019][  T148]  dump_stack_lvl+0x68/0x98
+[   14.962137][  T148]  panic+0x160/0x3f4
+
+----------snip----------
+
+[   14.998693][  T148] Starting crashdump kernel...
+[   14.999411][  T148] Bye!
+Booting Linux on physical CPU 0x0000000000 [0x412fd050]
+Linux version 5.15.44+ (build-user@build-host) (Android (8508608, based on r450784e) clang version 14.0.7 (https://android.googlesource.com/toolchain/llvm-project 4c603efb0cca074e9238af8b4106c30add4418f6), LLD 14.0.7) #1 SMP PREEMPT Thu Jul 7 02:57:03 UTC 2022
+achine model: linux,dummy-virt
+earlycon: uart8250 at MMIO 0x00000000000003f8 (options '')
+printk: bootconsole [uart8250] enabled
+
+----------snip----------
+
+Run /bin/crashdump as init process
+Crashdump started
+Size is 98836480 bytes
+.....................................................................random: crng init done
+...............................done
+reboot: Restarting system with command 'kernel panic'
+```
+
+## Obtaining the RAM dump
+
+By default, RAM dumps are sent to tombstone. To see which tombstone file is for
+the RAM dump, look into the log.
+
+```shell
+$ adb logcat | grep SYSTEM_TOMBSTONE
+09-22 17:24:28.798  1335  1504 I BootReceiver: Copying /data/tombstones/tombstone_47 to DropBox (SYSTEM_TOMBSTONE)
+```
+
+In the above example, the RAM dump is saved as `/data/tombstones/tombstone_47`.
+You can download this using `adb pull`.
+
+```shell
+$ adb root && adb pull /data/tombstones/tombstone_47 ramdump && adb unroot
+```
+
+Alternatively, you can specify the path to where RAM dump is stored when
+launching the VM using the `--ramdump` option of the `vm` tool.
+
+```shell
+$ adb shelll /apex/com.android.virt/bin/vm run-app --ramdump /data/local/tmp/virt/ramdump ...
+```
+
+In the above example, the RAM dump is saved to `/data/local/tmp/virt/ramdump`.
+
+## Analyzing the RAM dump
+
+### Building the crash(8) tool
+
+You first need to build the crash(8) tool for the target architecture, which in most case is aarch64.
+
+Download the source code and build it as follows. This needs to be done only once.
+
+```shell
+$ wget https://github.com/crash-utility/crash/archive/refs/tags/8.0.1.tar.gz -O - | tar xzvf
+$ make -C crash-8.0.1 target=ARM64
+```
+
+### Obtaining vmlinux
+
+You also need the image of the kernel binary with debuggin enabled. The kernel
+binary should be the same as the actual kernel that you used in the Microdroid
+VM that crashed. To identify which kernel it was, look for the kernel version
+number in the logcat log.
+
+```
+[   14.955309][  T148] CPU: 0 PID: 148 Comm: sh Kdump: loaded Not tainted 5.15.60-android14-5-04357-gbac79d727aea-ab9013362 #1
+```
+
+Here, the version number is
+`5.15.60-android14-5-04357-gbac79d727aea-ab9013362`. What is important here is
+the last component: `ab9013362`. The numbers after `ab` is the Android Build ID
+of the kernel.
+
+With the build ID, you can find the image from `ci.android.com` and download
+it. The direct link to the image is `https://ci.android.com/builds/submitted/9013362/kernel_microdroid_aarch64/latest/vmlinux`.
+
+DON'T forget to replace `9013362` with the actual build ID of the kernel you used.
+
+### Running crash(8) with the RAM dump and the kernel image
+
+```shell
+$ crash-8.0.1/crash ramdump vmlinux
+```
+
+You can now analyze the RAM dump using the various commands that crash(8) provides. For example, `bt <pid>` command shows the stack trace of a process.
+
+```
+crash> bt
+PID: 148    TASK: ffffff8001a2d880  CPU: 0   COMMAND: "sh"
+ #0 [ffffffc00926b9f0] machine_kexec at ffffffd48a852004
+ #1 [ffffffc00926bb90] __crash_kexec at ffffffd48a948008
+ #2 [ffffffc00926bc40] panic at ffffffd48a86e2a8
+ #3 [ffffffc00926bc90] sysrq_handle_crash.35db4764f472dc1c4a43f39b71f858ea at ffffffd48ad985c8
+ #4 [ffffffc00926bca0] __handle_sysrq at ffffffd48ad980e4
+ #5 [ffffffc00926bcf0] write_sysrq_trigger.35db4764f472dc1c4a43f39b71f858ea at ffffffd48ad994f0
+ #6 [ffffffc00926bd10] proc_reg_write.bc7c2a3e70d8726163739fbd131db16e at ffffffd48ab4d280
+ #7 [ffffffc00926bda0] vfs_write at ffffffd48aaaa1a4
+ #8 [ffffffc00926bdf0] ksys_write at ffffffd48aaaa5b0
+ #9 [ffffffc00926be30] __arm64_sys_write at ffffffd48aaaa644
+#10 [ffffffc00926be40] invoke_syscall at ffffffd48a84b55c
+#11 [ffffffc00926be60] do_el0_svc at ffffffd48a84b424
+#12 [ffffffc00926be80] el0_svc at ffffffd48b0a29e4
+#13 [ffffffc00926bea0] el0t_64_sync_handler at ffffffd48b0a2950
+#14 [ffffffc00926bfe0] el0t_64_sync at ffffffd48a811644
+     PC: 00000079d880b798   LR: 00000064b4afec8c   SP: 0000007ff6ddb2e0
+    X29: 0000007ff6ddb360  X28: 0000007ff6ddb320  X27: 00000064b4b238e8
+    X26: 00000079d9c49000  X25: 0000000000000000  X24: b40000784870fda9
+    X23: 00000064b4b236f8  X22: 0000007ff6ddb340  X21: 0000007ff6ddb338
+    X20: b40000784870f618  X19: 0000000000000002  X18: 00000079daea4000
+    X17: 00000079d880b790  X16: 00000079d882dee0  X15: 0000000000000080
+    X14: 0000000000000000  X13: 0000008f00000160  X12: 000000004870f6ac
+    X11: 0000000000000008  X10: 000000000009c000   X9: b40000784870f618
+     X8: 0000000000000040   X7: 000000e70000000b   X6: 0000020500000210
+     X5: 00000079d883a984   X4: ffffffffffffffff   X3: ffffffffffffffff
+     X2: 0000000000000002   X1: b40000784870f618   X0: 0000000000000001
+    ORIG_X0: 0000000000000001  SYSCALLNO: 40  PSTATE: 00001000
+```
+
+Above shows that the shell process that executed `echo c > /proc/sysrq-trigger`
+actually triggered a crash in the kernel.
+
+For more commands of crash(8), refer to the man page, or embedded `help` command.
+
+
+
+
+
+
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachine.java b/javalib/src/android/system/virtualmachine/VirtualMachine.java
index ae84c08..cc99006 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachine.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachine.java
@@ -273,7 +273,7 @@
             throw new VirtualMachineException(e);
         }
 
-        VirtualMachine vm;
+        VirtualMachine vm = null;
         synchronized (sInstancesLock) {
             Map<String, WeakReference<VirtualMachine>> instancesMap;
             if (sInstances.containsKey(context)) {
@@ -285,7 +285,8 @@
 
             if (instancesMap.containsKey(name)) {
                 vm = instancesMap.get(name).get();
-            } else {
+            }
+            if (vm == null) {
                 vm = new VirtualMachine(context, name, config);
                 instancesMap.put(name, new WeakReference<>(vm));
             }
diff --git a/libs/apkverify/src/algorithms.rs b/libs/apkverify/src/algorithms.rs
index 6d4362b..ed2c1fc 100644
--- a/libs/apkverify/src/algorithms.rs
+++ b/libs/apkverify/src/algorithms.rs
@@ -102,7 +102,7 @@
                 self,
                 SignatureAlgorithmID::DsaWithSha256 | SignatureAlgorithmID::VerityDsaWithSha256
             ),
-            "TODO(b/197052981): Algorithm '{:?}' is not implemented.",
+            "Algorithm '{:?}' is not supported in openssl to build this verifier (b/197052981).",
             self
         );
         ensure!(public_key.id() == self.pkey_id(), "Public key has the wrong ID");
@@ -130,6 +130,14 @@
         }
     }
 
+    /// DSA is not directly supported in openssl today. See b/197052981.
+    pub(crate) fn is_supported(&self) -> bool {
+        !matches!(
+            self,
+            SignatureAlgorithmID::DsaWithSha256 | SignatureAlgorithmID::VerityDsaWithSha256,
+        )
+    }
+
     fn pkey_id(&self) -> pkey::Id {
         match self {
             SignatureAlgorithmID::RsaPssWithSha256
diff --git a/libs/apkverify/src/v3.rs b/libs/apkverify/src/v3.rs
index 2a16cb1..5272834 100644
--- a/libs/apkverify/src/v3.rs
+++ b/libs/apkverify/src/v3.rs
@@ -139,7 +139,7 @@
         Ok(self
             .signatures
             .iter()
-            .filter(|sig| sig.signature_algorithm_id.is_some())
+            .filter(|sig| sig.signature_algorithm_id.map_or(false, |algo| algo.is_supported()))
             .max_by_key(|sig| sig.signature_algorithm_id.unwrap().content_digest_algorithm())
             .context("No supported signatures found")?)
     }
diff --git a/libs/apkverify/tests/apkverify_test.rs b/libs/apkverify/tests/apkverify_test.rs
index 5bd901d..e17ba5c 100644
--- a/libs/apkverify/tests/apkverify_test.rs
+++ b/libs/apkverify/tests/apkverify_test.rs
@@ -40,22 +40,11 @@
 }
 
 #[test]
-fn test_verify_v3_dsa_sha256() {
+fn apks_signed_with_v3_dsa_sha256_are_not_supported() {
     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(), "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,
-        );
+        assert!(res.is_err(), "DSA algorithm is not supported for verification. See b/197052981.");
+        assert_contains(&res.unwrap_err().to_string(), "No supported signatures found");
     }
 }
 
@@ -102,32 +91,21 @@
 #[test]
 fn test_verify_v3_sig_does_not_verify() {
     let path_list = [
-        "tests/data/v3-only-with-dsa-sha256-2048-sig-does-not-verify.apk",
         "tests/data/v3-only-with-ecdsa-sha512-p521-sig-does-not-verify.apk",
         "tests/data/v3-only-with-rsa-pkcs1-sha256-3072-sig-does-not-verify.apk",
     ];
     for path in path_list.iter() {
         let res = verify(path);
         assert!(res.is_err());
-        let error_msg = &res.unwrap_err().to_string();
-        assert!(
-            error_msg.contains("Signature is invalid") || error_msg.contains("not implemented")
-        );
+        assert_contains(&res.unwrap_err().to_string(), "Signature is invalid");
     }
 }
 
 #[test]
 fn test_verify_v3_digest_mismatch() {
-    let path_list = [
-        "tests/data/v3-only-with-dsa-sha256-3072-digest-mismatch.apk",
-        "tests/data/v3-only-with-rsa-pkcs1-sha512-8192-digest-mismatch.apk",
-    ];
-    for path in path_list.iter() {
-        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("not implemented"));
-    }
+    let res = verify("tests/data/v3-only-with-rsa-pkcs1-sha512-8192-digest-mismatch.apk");
+    assert!(res.is_err());
+    assert_contains(&res.unwrap_err().to_string(), "Digest mismatch");
 }
 
 #[test]
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
deleted file mode 100644
index c5aec18..0000000
--- a/libs/apkverify/tests/data/v3-only-with-dsa-sha256-1024.apk.apk_digest
+++ /dev/null
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
deleted file mode 100644
index c5aec18..0000000
--- a/libs/apkverify/tests/data/v3-only-with-dsa-sha256-2048.apk.apk_digest
+++ /dev/null
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
deleted file mode 100644
index c5aec18..0000000
--- a/libs/apkverify/tests/data/v3-only-with-dsa-sha256-3072.apk.apk_digest
+++ /dev/null
Binary files differ
diff --git a/tests/helper/src/java/com/android/microdroid/test/device/MicrodroidDeviceTestBase.java b/tests/helper/src/java/com/android/microdroid/test/device/MicrodroidDeviceTestBase.java
index a07731e..fdc846e 100644
--- a/tests/helper/src/java/com/android/microdroid/test/device/MicrodroidDeviceTestBase.java
+++ b/tests/helper/src/java/com/android/microdroid/test/device/MicrodroidDeviceTestBase.java
@@ -19,6 +19,7 @@
 
 import static org.junit.Assume.assumeNoException;
 
+import android.app.UiAutomation;
 import android.content.Context;
 import android.os.ParcelFileDescriptor;
 import android.os.SystemProperties;
@@ -35,6 +36,8 @@
 import com.android.virt.VirtualizationTestHelper;
 
 import java.io.BufferedReader;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.util.OptionalLong;
@@ -334,4 +337,20 @@
                 listener.getInitStartedNanoTime(),
                 listener.getPayloadStartedNanoTime());
     }
+
+    /** Execute a command. Returns stdout. */
+    protected String runInShell(String tag, UiAutomation uiAutomation, String command) {
+        try (InputStream is =
+                        new ParcelFileDescriptor.AutoCloseInputStream(
+                                uiAutomation.executeShellCommand(command));
+                ByteArrayOutputStream out = new ByteArrayOutputStream()) {
+            is.transferTo(out);
+            String stdout = out.toString("UTF-8");
+            Log.i(tag, "Got stdout : " + stdout);
+            return stdout;
+        } catch (IOException e) {
+            Log.e(tag, "Error executing: " + command, e);
+            throw new RuntimeException("Failed to run the command.");
+        }
+    }
 }