Snap for 13248265 from 78f66e0b66732695896777df0822c363d5597c1c to 25Q2-release

Change-Id: I4d96b3f0992b29446dfb99326342d08bf44ae2ed
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/InstalledImage.kt b/android/TerminalApp/java/com/android/virtualization/terminal/InstalledImage.kt
index 59be4ae..50aaa33 100644
--- a/android/TerminalApp/java/com/android/virtualization/terminal/InstalledImage.kt
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/InstalledImage.kt
@@ -19,6 +19,7 @@
 import android.os.FileUtils
 import android.system.ErrnoException
 import android.system.Os
+import android.system.OsConstants
 import android.util.Log
 import com.android.virtualization.terminal.MainActivity.Companion.TAG
 import java.io.BufferedReader
@@ -127,13 +128,36 @@
         }
 
         if (roundedUpDesiredSize > curSize) {
-            allocateSpace(rootPartition, roundedUpDesiredSize)
+            if (!allocateSpace(roundedUpDesiredSize)) {
+                return curSize
+            }
         }
         resizeFilesystem(rootPartition, roundedUpDesiredSize)
         return getApparentSize()
     }
 
     @Throws(IOException::class)
+    private fun allocateSpace(sizeInBytes: Long): Boolean {
+        val curSizeInBytes = getApparentSize()
+        try {
+            RandomAccessFile(rootPartition.toFile(), "rw").use { raf ->
+                Os.posix_fallocate(raf.fd, 0, sizeInBytes)
+            }
+            Log.d(TAG, "Allocated space to: $sizeInBytes bytes")
+            return true
+        } catch (e: ErrnoException) {
+            Log.e(TAG, "Failed to allocate space", e)
+            if (e.errno == OsConstants.ENOSPC) {
+                Log.d(TAG, "Trying to truncate disk into the original size")
+                truncate(curSizeInBytes)
+                return false
+            } else {
+                throw IOException("Failed to allocate space", e)
+            }
+        }
+    }
+
+    @Throws(IOException::class)
     fun shrinkToMinimumSize(): Long {
         // Fix filesystem before resizing.
         runE2fsck(rootPartition)
@@ -153,8 +177,8 @@
             RandomAccessFile(rootPartition.toFile(), "rw").use { raf -> Os.ftruncate(raf.fd, size) }
             Log.d(TAG, "Truncated space to: $size bytes")
         } catch (e: ErrnoException) {
-            Log.e(TAG, "Failed to allocate space", e)
-            throw IOException("Failed to allocate space", e)
+            Log.e(TAG, "Failed to truncate space", e)
+            throw IOException("Failed to truncate space", e)
         }
     }
 
@@ -175,19 +199,6 @@
         }
 
         @Throws(IOException::class)
-        private fun allocateSpace(path: Path, sizeInBytes: Long) {
-            try {
-                RandomAccessFile(path.toFile(), "rw").use { raf ->
-                    Os.posix_fallocate(raf.fd, 0, sizeInBytes)
-                }
-                Log.d(TAG, "Allocated space to: $sizeInBytes bytes")
-            } catch (e: ErrnoException) {
-                Log.e(TAG, "Failed to allocate space", e)
-                throw IOException("Failed to allocate space", e)
-            }
-        }
-
-        @Throws(IOException::class)
         private fun runE2fsck(path: Path) {
             val p: String = path.toAbsolutePath().toString()
             runCommand("/system/bin/e2fsck", "-y", "-f", p)
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/SettingsDiskResizeActivity.kt b/android/TerminalApp/java/com/android/virtualization/terminal/SettingsDiskResizeActivity.kt
index 144db40..da07b19 100644
--- a/android/TerminalApp/java/com/android/virtualization/terminal/SettingsDiskResizeActivity.kt
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/SettingsDiskResizeActivity.kt
@@ -33,6 +33,7 @@
 import androidx.core.view.isVisible
 import com.android.virtualization.terminal.VmLauncherService.VmLauncherServiceCallback
 import com.google.android.material.dialog.MaterialAlertDialogBuilder
+import com.google.android.material.snackbar.Snackbar
 import java.util.Locale
 import java.util.regex.Pattern
 
@@ -46,6 +47,7 @@
     private lateinit var cancelButton: View
     private lateinit var resizeButton: View
     private lateinit var diskSizeText: TextView
+    private lateinit var diskMaxSizeText: TextView
     private lateinit var diskSizeSlider: SeekBar
 
     private fun bytesToMb(bytes: Long): Long {
@@ -56,6 +58,13 @@
         return bytes shl 20
     }
 
+    private fun getAvailableSizeMb(): Long {
+        val usableSpaceMb =
+            bytesToMb(Environment.getDataDirectory().getUsableSpace()) and
+                (diskSizeStepMb - 1).inv()
+        return diskSizeMb + usableSpaceMb
+    }
+
     private fun mbToProgress(bytes: Long): Int {
         return (bytes / diskSizeStepMb).toInt()
     }
@@ -73,13 +82,10 @@
         val image = InstalledImage.getDefault(this)
         diskSizeMb = bytesToMb(image.getApparentSize())
         val minDiskSizeMb = bytesToMb(image.getSmallestSizePossible()).coerceAtMost(diskSizeMb)
-        val usableSpaceMb =
-            bytesToMb(Environment.getDataDirectory().getUsableSpace()) and
-                (diskSizeStepMb - 1).inv()
-        val maxDiskSizeMb = defaultMaxDiskSizeMb.coerceAtMost(diskSizeMb + usableSpaceMb)
+        val maxDiskSizeMb = defaultMaxDiskSizeMb.coerceAtMost(getAvailableSizeMb())
 
         diskSizeText = findViewById<TextView>(R.id.settings_disk_resize_resize_gb_assigned)!!
-        val diskMaxSizeText = findViewById<TextView>(R.id.settings_disk_resize_resize_gb_max)
+        diskMaxSizeText = findViewById<TextView>(R.id.settings_disk_resize_resize_gb_max)
         diskMaxSizeText.text =
             getString(
                 R.string.settings_disk_resize_resize_gb_max_format,
@@ -138,7 +144,22 @@
     }
 
     private fun resize() {
-        diskSizeMb = progressToMb(diskSizeSlider.progress)
+        val desiredDiskSizeMb = progressToMb(diskSizeSlider.progress)
+        val availableSizeMb = getAvailableSizeMb()
+        if (availableSizeMb < desiredDiskSizeMb) {
+            Snackbar.make(
+                    findViewById(android.R.id.content),
+                    R.string.settings_disk_resize_not_enough_space_message,
+                    Snackbar.LENGTH_SHORT,
+                )
+                .show()
+            diskSizeSlider.max = mbToProgress(availableSizeMb)
+            updateMaxSizeText(availableSizeMb)
+            cancel()
+            return
+        }
+
+        diskSizeMb = desiredDiskSizeMb
         buttons.isVisible = false
 
         // Note: we first stop the VM, and wait for it to fully stop. Then we (re) start the Main
@@ -186,6 +207,14 @@
             )
     }
 
+    fun updateMaxSizeText(sizeMb: Long) {
+        diskMaxSizeText.text =
+            getString(
+                R.string.settings_disk_resize_resize_gb_max_format,
+                localizedFileSize(sizeMb, /* isShort= */ true),
+            )
+    }
+
     fun localizedFileSize(sizeMb: Long, isShort: Boolean): String {
         val sizeGb = sizeMb / (1 shl 10).toFloat()
         val measure = Measure(sizeGb, MeasureUnit.GIGABYTE)
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/VmLauncherService.kt b/android/TerminalApp/java/com/android/virtualization/terminal/VmLauncherService.kt
index d43a8d1..84168e5 100644
--- a/android/TerminalApp/java/com/android/virtualization/terminal/VmLauncherService.kt
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/VmLauncherService.kt
@@ -463,15 +463,32 @@
 
     @WorkerThread
     private fun doShutdown(resultReceiver: ResultReceiver?) {
-        stopForeground(STOP_FOREGROUND_REMOVE)
+        runner?.exitStatus?.thenAcceptAsync { success: Boolean ->
+            resultReceiver?.send(if (success) RESULT_STOP else RESULT_ERROR, null)
+            stopSelf()
+        }
         if (debianService != null && debianService!!.shutdownDebian()) {
             // During shutdown, change the notification content to indicate that it's closing
             val notification = createNotificationForTerminalClose()
             getSystemService<NotificationManager?>(NotificationManager::class.java)
                 .notify(this.hashCode(), notification)
-            runner?.exitStatus?.thenAcceptAsync { success: Boolean ->
-                resultReceiver?.send(if (success) RESULT_STOP else RESULT_ERROR, null)
-                stopSelf()
+
+            runner?.also { r ->
+                // For the case that shutdown from the guest agent fails.
+                // When timeout is set, the original CompletableFuture's every `thenAcceptAsync` is
+                // canceled as well. So add empty `thenAcceptAsync` to avoid interference.
+                r.exitStatus
+                    .thenAcceptAsync {}
+                    .orTimeout(SHUTDOWN_TIMEOUT_SECONDS, TimeUnit.SECONDS)
+                    .exceptionally {
+                        Log.e(
+                            TAG,
+                            "Stop the service directly because the VM instance isn't stopped with " +
+                                "graceful shutdown",
+                        )
+                        r.vm.stop()
+                        null
+                    }
             }
             runner = null
         } else {
@@ -498,6 +515,7 @@
         stopDebianServer()
         bgThreads.shutdownNow()
         mainWorkerThread.shutdown()
+        stopForeground(STOP_FOREGROUND_REMOVE)
         super.onDestroy()
     }
 
@@ -517,6 +535,8 @@
         private const val KEY_TERMINAL_IPADDRESS = "address"
         private const val KEY_TERMINAL_PORT = "port"
 
+        private const val SHUTDOWN_TIMEOUT_SECONDS = 3L
+
         private const val GUEST_SPARSE_DISK_SIZE_PERCENTAGE = 95
         private const val EXPECTED_PHYSICAL_SIZE_PERCENTAGE_FOR_NON_SPARSE = 90
 
diff --git a/android/TerminalApp/res/values/strings.xml b/android/TerminalApp/res/values/strings.xml
index a6d461e..273032e 100644
--- a/android/TerminalApp/res/values/strings.xml
+++ b/android/TerminalApp/res/values/strings.xml
@@ -75,6 +75,8 @@
     <string name="settings_disk_resize_resize_gb_assigned_format"><xliff:g id="assigned_size" example="10GB">\u200E%1$s\u200E</xliff:g> assigned</string>
     <!-- Settings menu option description format of the maximum resizable disk size. [CHAR LIMIT=none] -->
     <string name="settings_disk_resize_resize_gb_max_format"><xliff:g id="max_size" example="256GB">\u200E%1$s\u200E</xliff:g> max</string>
+    <!-- Snackbar message for showing not enough space error. [CHAR LIMIT=none] -->
+    <string name="settings_disk_resize_not_enough_space_message">Not enough space on the device to resize disk</string>
     <!-- Settings menu button to cancel disk resize. [CHAR LIMIT=16] -->
     <string name="settings_disk_resize_resize_cancel">Cancel</string>
     <!-- Settings menu button to apply change Terminal app. This will launch a confirmation dialog [CHAR LIMIT=16] -->
diff --git a/build/apex/Android.bp b/build/apex/Android.bp
index 8934de0..f0eba7f 100644
--- a/build/apex/Android.bp
+++ b/build/apex/Android.bp
@@ -56,6 +56,7 @@
         "libvirtualizationservice_jni",
         "libvirtualmachine_jni",
     ],
+    native_shared_libs: ["libavf"],
     // TODO(b/295593640) Unfortunately these are added to the apex even though they are unused.
     // Once the build system is fixed, remove this.
     unwanted_transitive_deps: [
@@ -108,7 +109,6 @@
                 "rialto_bin",
                 "android_bootloader_crosvm_aarch64",
             ],
-            native_shared_libs: ["libavf"],
         },
         x86_64: {
             binaries: [
@@ -129,7 +129,6 @@
             prebuilts: [
                 "android_bootloader_crosvm_x86_64",
             ],
-            native_shared_libs: ["libavf"],
         },
     },
     binaries: [
diff --git a/guest/pvmfw/Android.bp b/guest/pvmfw/Android.bp
index 6f113c8..cd32f8f 100644
--- a/guest/pvmfw/Android.bp
+++ b/guest/pvmfw/Android.bp
@@ -113,13 +113,15 @@
 
 rust_test {
     name: "libpvmfw.dice.test",
-    srcs: ["src/dice.rs"],
+    srcs: ["src/dice/mod.rs"],
     defaults: ["libpvmfw.test.defaults"],
     rustlibs: [
         "libcbor_util",
         "libciborium",
+        "libcoset_nostd",
         "libdiced_open_dice_nostd",
         "libhwtrust",
+        "liblog_rust",
         "libpvmfw_avb_nostd",
         "libdiced_sample_inputs_nostd",
         "libzerocopy_nostd",
diff --git a/guest/pvmfw/README.md b/guest/pvmfw/README.md
index c7f3dd6..08b0d5c 100644
--- a/guest/pvmfw/README.md
+++ b/guest/pvmfw/README.md
@@ -1,19 +1,30 @@
 # Protected Virtual Machine Firmware
 
+## Protected VM (_"pVM"_)
+
 In the context of the [Android Virtualization Framework][AVF], a hypervisor
 (_e.g._ [pKVM]) enforces full memory isolation between its virtual machines
-(VMs) and the host.  As a result, the host is only allowed to access memory that
-has been explicitly shared back by a VM. Such _protected VMs_ (“pVMs”) are
-therefore able to manipulate secrets without being at risk of an attacker
-stealing them by compromising the Android host.
+(VMs) and the host. As a result, such VMs are given strong guarantees regarding
+their confidentiality and integrity.
 
-As pVMs are started dynamically by a _virtual machine manager_ (“VMM”) running
-as a host process and as pVMs must not trust the host (see [_Why
-AVF?_][why-avf]), the virtual machine it configures can't be trusted either.
-Furthermore, even though the isolation mentioned above allows pVMs to protect
-their secrets from the host, it does not help with provisioning them during
-boot. In particular, the threat model would prohibit the host from ever having
-access to those secrets, preventing the VMM from passing them to the pVM.
+A _protected VMs_ (“pVMs”) is a VM running in the non-secure or realm world,
+started dynamically by a _virtual machine manager_ (“VMM”) running as a process
+of the untrusted Android host (see [_Why AVF?_][why-avf]) and which is isolated
+from the host OS so that access to its memory is restricted, even in the event
+of a compromised Android host. pVMs support rich environments, including
+Linux-based distributions.
+
+The pVM concept is not Google-exclusive. Partner-defined VMs (SoC/OEM) meeting
+isolation/memory access restrictions are also pVMs.
+
+## Protected VM Root-of-Trust: pvmfw
+
+As pVMs are managed by a VMM running on the untrusted host, the virtual machine
+it configures can't be trusted either. Furthermore, even though the isolation
+mentioned above allows pVMs to protect their secrets from the host, it does not
+help with provisioning them during boot. In particular, the threat model would
+prohibit the host from ever having access to those secrets, preventing the VMM
+from passing them to the pVM.
 
 To address these concerns the hypervisor securely loads the pVM firmware
 (“pvmfw”) in the pVM from a protected memory region (this prevents the host or
diff --git a/guest/pvmfw/avb/tests/api_test.rs b/guest/pvmfw/avb/tests/api_test.rs
index b3899d9..b8ec0bf 100644
--- a/guest/pvmfw/avb/tests/api_test.rs
+++ b/guest/pvmfw/avb/tests/api_test.rs
@@ -71,6 +71,7 @@
         expected_rollback_index,
         vec![Capability::TrustySecurityVm],
         None,
+        Some("trusty_test_vm".to_owned()),
     )
 }
 
diff --git a/guest/pvmfw/avb/tests/utils.rs b/guest/pvmfw/avb/tests/utils.rs
index 227daa2..38541c5 100644
--- a/guest/pvmfw/avb/tests/utils.rs
+++ b/guest/pvmfw/avb/tests/utils.rs
@@ -148,6 +148,7 @@
     expected_rollback_index: u64,
     capabilities: Vec<Capability>,
     page_size: Option<usize>,
+    expected_name: Option<String>,
 ) -> Result<()> {
     let public_key = load_trusted_public_key()?;
     let verified_boot_data = verify_payload(
@@ -168,7 +169,7 @@
         capabilities,
         rollback_index: expected_rollback_index,
         page_size,
-        name: None,
+        name: expected_name,
     };
     assert_eq!(expected_boot_data, verified_boot_data);
 
diff --git a/guest/pvmfw/src/bcc.rs b/guest/pvmfw/src/dice/chain.rs
similarity index 98%
rename from guest/pvmfw/src/bcc.rs
rename to guest/pvmfw/src/dice/chain.rs
index 7ce50e9..0f5b058 100644
--- a/guest/pvmfw/src/bcc.rs
+++ b/guest/pvmfw/src/dice/chain.rs
@@ -59,6 +59,7 @@
 
 /// Return a new CBOR encoded BccHandover that is based on the incoming CDIs but does not chain
 /// from the received BCC.
+#[cfg_attr(test, allow(dead_code))]
 pub fn truncate(bcc_handover: BccHandover) -> Result<Vec<u8>> {
     // Note: The strings here are deliberately different from those used in a normal DICE handover
     // because we want this to not be equivalent to any valid DICE derivation.
@@ -75,6 +76,7 @@
     cbor_util::serialize(&bcc_handover).map_err(|_| BccError::CborEncodeError)
 }
 
+#[cfg_attr(test, allow(dead_code))]
 fn taint_cdi(cdi: &Cdi, info: &str) -> Result<Cdi> {
     // An arbitrary value generated randomly.
     const SALT: [u8; 64] = [
diff --git a/guest/pvmfw/src/dice.rs b/guest/pvmfw/src/dice/mod.rs
similarity index 99%
rename from guest/pvmfw/src/dice.rs
rename to guest/pvmfw/src/dice/mod.rs
index 49a3807..94348a5 100644
--- a/guest/pvmfw/src/dice.rs
+++ b/guest/pvmfw/src/dice/mod.rs
@@ -15,9 +15,12 @@
 //! Support for DICE derivation and BCC generation.
 extern crate alloc;
 
+pub(crate) mod chain;
+
 use alloc::format;
 use alloc::string::String;
 use alloc::vec::Vec;
+pub use chain::Bcc;
 use ciborium::cbor;
 use ciborium::Value;
 use core::mem::size_of;
diff --git a/guest/pvmfw/src/main.rs b/guest/pvmfw/src/main.rs
index 30624cd..d3d5527 100644
--- a/guest/pvmfw/src/main.rs
+++ b/guest/pvmfw/src/main.rs
@@ -20,7 +20,6 @@
 extern crate alloc;
 
 mod arch;
-mod bcc;
 mod bootargs;
 mod config;
 mod device_assignment;
@@ -32,8 +31,7 @@
 mod memory;
 mod rollback;
 
-use crate::bcc::Bcc;
-use crate::dice::PartialInputs;
+use crate::dice::{Bcc, PartialInputs};
 use crate::entry::RebootReason;
 use crate::fdt::{modify_for_next_stage, read_instance_id, sanitize_device_tree};
 use crate::rollback::perform_rollback_protection;
@@ -134,7 +132,7 @@
         // entire chain we were given and taint the CDIs. Note that the resulting CDIs are
         // still deterministically derived from those we received, so will vary iff they do.
         // TODO(b/280405545): Remove this post Android 14.
-        let truncated_bcc_handover = bcc::truncate(bcc_handover).map_err(|e| {
+        let truncated_bcc_handover = dice::chain::truncate(bcc_handover).map_err(|e| {
             error!("{e}");
             RebootReason::InternalError
         })?;
diff --git a/guest/trusty/security_vm/vm/Android.bp b/guest/trusty/security_vm/vm/Android.bp
index 6fa0c32..9928b56 100644
--- a/guest/trusty/security_vm/vm/Android.bp
+++ b/guest/trusty/security_vm/vm/Android.bp
@@ -123,6 +123,10 @@
             name: "com.android.virt.cap",
             value: "trusty_security_vm",
         },
+        {
+            name: "com.android.virt.name",
+            value: "trusty_security_vm",
+        },
     ],
     src: select(soong_config_variable("trusty_system_vm", "enabled"), {
         true: ":trusty_security_vm_unsigned",
diff --git a/guest/trusty/test_vm/vm/Android.bp b/guest/trusty/test_vm/vm/Android.bp
index f978c92..b919599 100644
--- a/guest/trusty/test_vm/vm/Android.bp
+++ b/guest/trusty/test_vm/vm/Android.bp
@@ -101,6 +101,10 @@
             name: "com.android.virt.cap",
             value: "trusty_security_vm",
         },
+        {
+            name: "com.android.virt.name",
+            value: "trusty_test_vm",
+        },
     ],
     src: ":trusty_test_vm_unsigned",
     enabled: false,
diff --git a/guest/trusty/test_vm_os/vm/Android.bp b/guest/trusty/test_vm_os/vm/Android.bp
index 2e81828..0e4116c 100644
--- a/guest/trusty/test_vm_os/vm/Android.bp
+++ b/guest/trusty/test_vm_os/vm/Android.bp
@@ -100,6 +100,10 @@
             name: "com.android.virt.cap",
             value: "trusty_security_vm",
         },
+        {
+            name: "com.android.virt.name",
+            value: "trusty_test_vm_os",
+        },
     ],
     src: ":trusty_test_vm_os_unsigned",
     enabled: false,
diff --git a/libs/dice/sample_inputs/Android.bp b/libs/dice/sample_inputs/Android.bp
index c1c4566..4010741 100644
--- a/libs/dice/sample_inputs/Android.bp
+++ b/libs/dice/sample_inputs/Android.bp
@@ -80,3 +80,9 @@
     ],
     static_libs: ["libopen_dice_clear_memory"],
 }
+
+dirgroup {
+    name: "trusty_dirgroup_packages_modules_virtualization_libs_dice_sample_inputs",
+    visibility: ["//trusty/vendor/google/aosp/scripts"],
+    dirs: ["."],
+}
diff --git a/libs/framework-virtualization/src/android/system/virtualmachine/VirtualMachine.java b/libs/framework-virtualization/src/android/system/virtualmachine/VirtualMachine.java
index eba6fcc..9641882 100644
--- a/libs/framework-virtualization/src/android/system/virtualmachine/VirtualMachine.java
+++ b/libs/framework-virtualization/src/android/system/virtualmachine/VirtualMachine.java
@@ -813,6 +813,10 @@
      */
     @GuardedBy("mLock")
     private void dropVm() {
+        if (mInputEventExecutor != null) {
+            mInputEventExecutor.shutdownNow();
+            mInputEventExecutor = null;
+        }
         if (mMemoryManagementCallbacks != null) {
             mContext.unregisterComponentCallbacks(mMemoryManagementCallbacks);
         }
@@ -1825,9 +1829,6 @@
             try {
                 mVirtualMachine.stop();
                 dropVm();
-                if (mInputEventExecutor != null) {
-                    mInputEventExecutor.shutdownNow();
-                }
             } catch (RemoteException e) {
                 throw e.rethrowAsRuntimeException();
             } catch (ServiceSpecificException e) {
diff --git a/libs/libavf/Android.bp b/libs/libavf/Android.bp
index aceb927..4469af5 100644
--- a/libs/libavf/Android.bp
+++ b/libs/libavf/Android.bp
@@ -40,30 +40,8 @@
     defaults: ["libavf.default"],
 }
 
-soong_config_module_type {
-    name: "virt_cc_defaults",
-    module_type: "cc_defaults",
-    config_namespace: "ANDROID",
-    bool_variables: [
-        "avf_enabled",
-    ],
-    properties: [
-        "apex_available",
-    ],
-}
-
-virt_cc_defaults {
-    name: "libavf_apex_available_defaults",
-    soong_config_variables: {
-        avf_enabled: {
-            apex_available: ["com.android.virt"],
-        },
-    },
-}
-
 cc_library {
     name: "libavf",
-    defaults: ["libavf_apex_available_defaults"],
     llndk: {
         symbol_file: "libavf.map.txt",
         moved_to_apex: true,
@@ -79,4 +57,5 @@
     stubs: {
         symbol_file: "libavf.map.txt",
     },
+    apex_available: ["com.android.virt"],
 }
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 fcef19a..7eb7748 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
@@ -262,6 +262,11 @@
     }
 
     public List<String> getSupportedOSList() throws Exception {
+        // The --os flag was introduced in SDK level 36. When running tests on earlier dessert
+        // releases only use "microdroid" OS.
+        if (getAndroidDevice().getApiLevel() < 36) {
+            return Arrays.asList("microdroid");
+        }
         return parseStringArrayFieldsFromVmInfo("Available OS list: ");
     }
 
diff --git a/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java b/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
index 2434ed0..379ac98 100644
--- a/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
+++ b/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
@@ -496,15 +496,20 @@
         final String configPath = "assets/vm_config_apex.json";
 
         // Act
-        mMicrodroidDevice =
+        MicrodroidBuilder microdroidBuilder =
                 MicrodroidBuilder.fromDevicePath(getPathForPackage(PACKAGE_NAME), configPath)
                         .debugLevel(DEBUG_LEVEL_FULL)
                         .memoryMib(minMemorySize())
                         .cpuTopology("match_host")
                         .protectedVm(true)
-                        .os(SUPPORTED_OSES.get(os))
-                        .name("protected_vm_runs_pvmfw")
-                        .build(getAndroidDevice());
+                        .name("protected_vm_runs_pvmfw");
+
+        // --os flag was introduced in SDK 36
+        if (getAndroidDevice().getApiLevel() >= 36) {
+            microdroidBuilder.os(SUPPORTED_OSES.get(os));
+        }
+
+        mMicrodroidDevice = microdroidBuilder.build(getAndroidDevice());
 
         // Assert
         mMicrodroidDevice.waitForBootComplete(BOOT_COMPLETE_TIMEOUT);
@@ -647,14 +652,18 @@
         CommandRunner android = new CommandRunner(getDevice());
         String testStartTime = android.runWithTimeout(1000, "date", "'+%Y-%m-%d %H:%M:%S.%N'");
 
-        mMicrodroidDevice =
+        MicrodroidBuilder microdroidBuilder =
                 MicrodroidBuilder.fromDevicePath(getPathForPackage(PACKAGE_NAME), configPath)
                         .debugLevel(DEBUG_LEVEL_FULL)
                         .memoryMib(minMemorySize())
                         .cpuTopology("match_host")
-                        .protectedVm(protectedVm)
-                        .os(SUPPORTED_OSES.get(os))
-                        .build(getAndroidDevice());
+                        .protectedVm(protectedVm);
+
+        if (getAndroidDevice().getApiLevel() >= 36) {
+            microdroidBuilder.os(SUPPORTED_OSES.get(os));
+        }
+
+        mMicrodroidDevice = microdroidBuilder.build(getAndroidDevice());
         mMicrodroidDevice.waitForBootComplete(BOOT_COMPLETE_TIMEOUT);
         mMicrodroidDevice.enableAdbRoot();
 
@@ -874,15 +883,20 @@
         // Create VM with microdroid
         TestDevice device = getAndroidDevice();
         final String configPath = "assets/vm_config_apex.json"; // path inside the APK
-        ITestDevice microdroid =
+        MicrodroidBuilder microdroidBuilder =
                 MicrodroidBuilder.fromDevicePath(getPathForPackage(PACKAGE_NAME), configPath)
                         .debugLevel(DEBUG_LEVEL_FULL)
                         .memoryMib(minMemorySize())
                         .cpuTopology("match_host")
                         .protectedVm(protectedVm)
-                        .os(SUPPORTED_OSES.get(os))
-                        .name("test_telemetry_pushed_atoms")
-                        .build(device);
+                        .name("test_telemetry_pushed_atoms");
+
+        if (device.getApiLevel() >= 36) {
+            microdroidBuilder.os(SUPPORTED_OSES.get(os));
+        }
+
+        ITestDevice microdroid = microdroidBuilder.build(device);
+
         microdroid.waitForBootComplete(BOOT_COMPLETE_TIMEOUT);
         device.shutdownMicrodroid(microdroid);
 
@@ -1026,14 +1040,18 @@
         assumeVmTypeSupported(os, protectedVm);
 
         final String configPath = "assets/vm_config.json"; // path inside the APK
-        testMicrodroidBootsWithBuilder(
+        MicrodroidBuilder microdroidBuilder =
                 MicrodroidBuilder.fromDevicePath(getPathForPackage(PACKAGE_NAME), configPath)
                         .debugLevel(DEBUG_LEVEL_FULL)
                         .memoryMib(minMemorySize())
                         .cpuTopology("match_host")
                         .protectedVm(protectedVm)
-                        .name("test_microdroid_boots")
-                        .os(SUPPORTED_OSES.get(os)));
+                        .name("test_microdroid_boots");
+        if (getAndroidDevice().getApiLevel() >= 36) {
+            microdroidBuilder.os(SUPPORTED_OSES.get(os));
+        }
+
+        testMicrodroidBootsWithBuilder(microdroidBuilder);
     }
 
     @Test
@@ -1064,15 +1082,18 @@
         assumeVmTypeSupported(os, protectedVm);
 
         final String configPath = "assets/vm_config.json";
-        mMicrodroidDevice =
+        MicrodroidBuilder microdroidBuilder =
                 MicrodroidBuilder.fromDevicePath(getPathForPackage(PACKAGE_NAME), configPath)
                         .debugLevel(DEBUG_LEVEL_FULL)
                         .memoryMib(minMemorySize())
                         .cpuTopology("match_host")
                         .protectedVm(protectedVm)
-                        .os(SUPPORTED_OSES.get(os))
-                        .name("test_microdroid_ram_usage")
-                        .build(getAndroidDevice());
+                        .name("test_microdroid_ram_usage");
+        if (getAndroidDevice().getApiLevel() >= 36) {
+            microdroidBuilder.os(SUPPORTED_OSES.get(os));
+        }
+
+        mMicrodroidDevice = microdroidBuilder.build(getAndroidDevice());
         mMicrodroidDevice.waitForBootComplete(BOOT_COMPLETE_TIMEOUT);
         mMicrodroidDevice.enableAdbRoot();
 
@@ -1362,16 +1383,18 @@
         Objects.requireNonNull(device);
         final String configPath = "assets/vm_config.json";
 
-        mMicrodroidDevice =
+        MicrodroidBuilder microdroidBuilder =
                 MicrodroidBuilder.fromDevicePath(getPathForPackage(PACKAGE_NAME), configPath)
                         .debugLevel(DEBUG_LEVEL_FULL)
                         .memoryMib(minMemorySize())
                         .cpuTopology("match_host")
                         .protectedVm(protectedVm)
-                        .os(SUPPORTED_OSES.get(os))
-                        .addAssignableDevice(device)
-                        .build(getAndroidDevice());
+                        .addAssignableDevice(device);
+        if (getAndroidDevice().getApiLevel() >= 36) {
+            microdroidBuilder.os(SUPPORTED_OSES.get(os));
+        }
 
+        mMicrodroidDevice = microdroidBuilder.build(getAndroidDevice());
         assertThat(mMicrodroidDevice.waitForBootComplete(BOOT_COMPLETE_TIMEOUT)).isTrue();
         assertThat(mMicrodroidDevice.enableAdbRoot()).isTrue();
     }
@@ -1408,16 +1431,19 @@
         android.run("echo advise > " + SHMEM_ENABLED_PATH);
 
         final String configPath = "assets/vm_config.json";
-        mMicrodroidDevice =
+        MicrodroidBuilder microdroidBuilder =
                 MicrodroidBuilder.fromDevicePath(getPathForPackage(PACKAGE_NAME), configPath)
                         .debugLevel(DEBUG_LEVEL_FULL)
                         .memoryMib(minMemorySize())
                         .cpuTopology("match_host")
                         .protectedVm(protectedVm)
-                        .os(SUPPORTED_OSES.get(os))
                         .hugePages(true)
-                        .name("test_huge_pages")
-                        .build(getAndroidDevice());
+                        .name("test_huge_pages");
+        if (getAndroidDevice().getApiLevel() >= 36) {
+            microdroidBuilder.os(SUPPORTED_OSES.get(os));
+        }
+
+        mMicrodroidDevice = microdroidBuilder.build(getAndroidDevice());
         mMicrodroidDevice.waitForBootComplete(BOOT_COMPLETE_TIMEOUT);
 
         android.run("echo never >" + SHMEM_ENABLED_PATH);