Add --mem option to the vm tool

The option is used to override the memoryMib value of the vm config. The
Microdroid tests are modified to use the minimum amount of memory so
that we keep track of the memory requirement.

Bug: 199703022
Bug: 194961381
Test: atest MicrodroidHostTestCases
Change-Id: Id71c756f689e8da50ea19235264cd788ea4c5f70
diff --git a/authfs/tests/java/src/com/android/fs/AuthFsHostTest.java b/authfs/tests/java/src/com/android/fs/AuthFsHostTest.java
index 1b4fa4a..2c13ecb 100644
--- a/authfs/tests/java/src/com/android/fs/AuthFsHostTest.java
+++ b/authfs/tests/java/src/com/android/fs/AuthFsHostTest.java
@@ -108,7 +108,8 @@
                         apkName,
                         packageName,
                         configPath,
-                        /* debug */ false);
+                        /* debug */ false,
+                        /* use default memoryMib */ 0);
         adbConnectToMicrodroid(androidDevice, sCid);
 
         // Root because authfs (started from shell in this test) currently require root to open
diff --git a/compos/tests/java/android/compos/test/ComposKeyTestCase.java b/compos/tests/java/android/compos/test/ComposKeyTestCase.java
index 00b002f..d9f7065 100644
--- a/compos/tests/java/android/compos/test/ComposKeyTestCase.java
+++ b/compos/tests/java/android/compos/test/ComposKeyTestCase.java
@@ -159,7 +159,8 @@
                         apkName,
                         packageName,
                         "assets/vm_test_config.json",
-                        /* debug */ false);
+                        /* debug */ false,
+                        /* use default memoryMib */ 0);
         adbConnectToMicrodroid(getDevice(), mCid);
     }
 
diff --git a/compos/tests/java/android/compos/test/ComposTestCase.java b/compos/tests/java/android/compos/test/ComposTestCase.java
index 42a323f..40f95c3 100644
--- a/compos/tests/java/android/compos/test/ComposTestCase.java
+++ b/compos/tests/java/android/compos/test/ComposTestCase.java
@@ -150,7 +150,8 @@
                         apkName,
                         packageName,
                         "assets/vm_test_config.json",
-                        /* debug */ false);
+                        /* debug */ false,
+                        /* Use default memory */ 0);
         adbConnectToMicrodroid(getDevice(), mCid);
     }
 
diff --git a/tests/hostside/helper/java/android/virt/test/VirtualizationTestCaseBase.java b/tests/hostside/helper/java/android/virt/test/VirtualizationTestCaseBase.java
index 4c8f5eb..24a955b 100644
--- a/tests/hostside/helper/java/android/virt/test/VirtualizationTestCaseBase.java
+++ b/tests/hostside/helper/java/android/virt/test/VirtualizationTestCaseBase.java
@@ -167,7 +167,8 @@
             String apkName,
             String packageName,
             String configPath,
-            boolean debug)
+            boolean debug,
+            int memoryMib)
             throws DeviceNotAvailableException {
         CommandRunner android = new CommandRunner(androidDevice);
 
@@ -198,6 +199,7 @@
                         "run-app",
                         "--daemonize",
                         "--log " + logPath,
+                        "--mem " + memoryMib,
                         debugFlag,
                         apkPath,
                         outApkIdsigPath,
diff --git a/tests/hostside/java/android/virt/test/MicrodroidTestCase.java b/tests/hostside/java/android/virt/test/MicrodroidTestCase.java
index a7b855a..6548428 100644
--- a/tests/hostside/java/android/virt/test/MicrodroidTestCase.java
+++ b/tests/hostside/java/android/virt/test/MicrodroidTestCase.java
@@ -18,7 +18,10 @@
 
 import static org.hamcrest.CoreMatchers.is;
 import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
+import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 
 import org.junit.After;
@@ -31,6 +34,22 @@
     private static final String APK_NAME = "MicrodroidTestApp.apk";
     private static final String PACKAGE_NAME = "com.android.microdroid.test";
 
+    private static final int MIN_MEM_ARM64 = 125;
+    private static final int MIN_MEM_X86_64 = 270;
+
+    private int minMemorySize() throws DeviceNotAvailableException {
+        CommandRunner android = new CommandRunner(getDevice());
+        String abi = android.run("getprop", "ro.product.cpu.abi");
+        assertTrue(abi != null && !abi.isEmpty());
+        if (abi.startsWith("arm64")) {
+            return MIN_MEM_ARM64;
+        } else if (abi.startsWith("x86_64")) {
+            return MIN_MEM_X86_64;
+        }
+        fail("Unsupported ABI: " + abi);
+        return 0;
+    }
+
     @Test
     public void testMicrodroidBoots() throws Exception {
         final String configPath = "assets/vm_config.json"; // path inside the APK
@@ -41,7 +60,8 @@
                         APK_NAME,
                         PACKAGE_NAME,
                         configPath,
-                        /* debug */ false);
+                        /* debug */ false,
+                        minMemorySize());
         adbConnectToMicrodroid(getDevice(), cid);
 
         // Wait until logd-init starts. The service is one of the last services that are started in
@@ -89,7 +109,14 @@
         final String configPath = "assets/vm_config.json"; // path inside the APK
         final boolean debug = true;
         final String cid =
-                startMicrodroid(getDevice(), getBuild(), APK_NAME, PACKAGE_NAME, configPath, debug);
+                startMicrodroid(
+                        getDevice(),
+                        getBuild(),
+                        APK_NAME,
+                        PACKAGE_NAME,
+                        configPath,
+                        debug,
+                        minMemorySize());
         adbConnectToMicrodroid(getDevice(), cid);
 
         assertThat(runOnMicrodroid("getenforce"), is("Permissive"));
diff --git a/vm/src/main.rs b/vm/src/main.rs
index fe47d2c..062773b 100644
--- a/vm/src/main.rs
+++ b/vm/src/main.rs
@@ -62,6 +62,11 @@
         /// Whether to run VM in debug mode.
         #[structopt(short, long)]
         debug: bool,
+
+        /// Memory size (in MiB) of the VM. If unspecified, defaults to the value of `memory_mib`
+        /// in the VM config file.
+        #[structopt(short, long)]
+        mem: Option<u32>,
     },
     /// Run a virtual machine
     Run {
@@ -118,7 +123,7 @@
         .context("Failed to find VirtualizationService")?;
 
     match opt {
-        Opt::RunApp { apk, idsig, instance, config_path, daemonize, log, debug } => {
+        Opt::RunApp { apk, idsig, instance, config_path, daemonize, log, debug, mem } => {
             command_run_app(
                 service,
                 &apk,
@@ -128,10 +133,11 @@
                 daemonize,
                 log.as_deref(),
                 debug,
+                mem,
             )
         }
         Opt::Run { config, daemonize, log } => {
-            command_run(service, &config, daemonize, log.as_deref())
+            command_run(service, &config, daemonize, log.as_deref(), /* mem */ None)
         }
         Opt::Stop { cid } => command_stop(service, cid),
         Opt::List => command_list(service),
diff --git a/vm/src/run.rs b/vm/src/run.rs
index 0d34a97..42da6a3 100644
--- a/vm/src/run.rs
+++ b/vm/src/run.rs
@@ -49,6 +49,7 @@
     daemonize: bool,
     log_path: Option<&Path>,
     debug: bool,
+    mem: Option<u32>,
 ) -> Result<(), Error> {
     let apk_file = File::open(apk).context("Failed to open APK file")?;
     let idsig_file = File::create(idsig).context("Failed to create idsig file")?;
@@ -76,8 +77,7 @@
         instanceImage: open_parcel_file(instance, true /* writable */)?.into(),
         configPath: config_path.to_owned(),
         debug,
-        // Use the default.
-        memoryMib: 0,
+        memoryMib: mem.unwrap_or(0) as i32, // 0 means use the VM default
     });
     run(service, &config, &format!("{:?}!{:?}", apk, config_path), daemonize, log_path)
 }
@@ -88,10 +88,14 @@
     config_path: &Path,
     daemonize: bool,
     log_path: Option<&Path>,
+    mem: Option<u32>,
 ) -> Result<(), Error> {
     let config_file = File::open(config_path).context("Failed to open config file")?;
-    let config =
+    let mut config =
         VmConfig::load(&config_file).context("Failed to parse config file")?.to_parcelable()?;
+    if let Some(mem) = mem {
+        config.memoryMib = mem as i32;
+    }
     run(
         service,
         &VirtualMachineConfig::RawConfig(config),