Merge changes from topic "rust_libfdt"

* changes:
  libfdt: Add CompatibleIterator
  libfdt: Create Rust wrapper
diff --git a/OWNERS b/OWNERS
index ecd24ed..310add7 100644
--- a/OWNERS
+++ b/OWNERS
@@ -1,5 +1,7 @@
 # Welcome to Android KVM!
 #
+# Bug component: 867125
+#
 # If you are not a member of the project please send review requests
 # to one of those listed below.
 dbrazdil@google.com
diff --git a/apex/Android.bp b/apex/Android.bp
index 2d6c757..4e64e50 100644
--- a/apex/Android.bp
+++ b/apex/Android.bp
@@ -57,6 +57,9 @@
     bootclasspath_fragments: [
         "com.android.virt-bootclasspath-fragment",
     ],
+    jni_libs: [
+        "libvirtualmachine_jni",
+    ],
 }
 
 apex_defaults {
@@ -87,9 +90,6 @@
         "fd_server",
         "vm",
     ],
-    jni_libs: [
-        "libvirtualmachine_jni",
-    ],
     prebuilts: [
         "com.android.virt.init.rc",
         "features_com.android.virt.xml",
diff --git a/authfs/fd_server/src/main.rs b/authfs/fd_server/src/main.rs
index f1fffdd..21d0e64 100644
--- a/authfs/fd_server/src/main.rs
+++ b/authfs/fd_server/src/main.rs
@@ -29,7 +29,7 @@
 use clap::Parser;
 use log::debug;
 use nix::sys::stat::{umask, Mode};
-use rpcbinder::run_vsock_rpc_server;
+use rpcbinder::RpcServer;
 use std::collections::BTreeMap;
 use std::fs::File;
 use std::os::unix::io::{FromRawFd, OwnedFd};
@@ -135,18 +135,14 @@
     let old_umask = umask(Mode::empty());
     debug!("Setting umask to 0 (old: {:03o})", old_umask.bits());
 
-    let service = FdService::new_binder(fd_pool).as_binder();
     debug!("fd_server is starting as a rpc service.");
-    let retval = run_vsock_rpc_server(service, RPC_SERVICE_PORT, || {
-        debug!("fd_server is ready");
-        // Close the ready-fd if we were given one to signal our readiness.
-        drop(ready_fd.take());
-    });
+    let service = FdService::new_binder(fd_pool).as_binder();
+    let server = RpcServer::new_vsock(service, RPC_SERVICE_PORT)?;
+    debug!("fd_server is ready");
 
-    if retval {
-        debug!("RPC server has shut down gracefully");
-        Ok(())
-    } else {
-        bail!("Premature termination of RPC server");
-    }
+    // Close the ready-fd if we were given one to signal our readiness.
+    drop(ready_fd.take());
+
+    server.join();
+    Ok(())
 }
diff --git a/authfs/service/src/main.rs b/authfs/service/src/main.rs
index 671c06a..e710f07 100644
--- a/authfs/service/src/main.rs
+++ b/authfs/service/src/main.rs
@@ -24,7 +24,7 @@
 
 use anyhow::{bail, Result};
 use log::*;
-use rpcbinder::run_init_unix_domain_rpc_server;
+use rpcbinder::RpcServer;
 use std::ffi::OsString;
 use std::fs::{create_dir, read_dir, remove_dir_all, remove_file};
 use std::sync::atomic::{AtomicUsize, Ordering};
@@ -117,15 +117,11 @@
 
     let service = AuthFsService::new_binder(debuggable).as_binder();
     debug!("{} is starting as a rpc service.", AUTHFS_SERVICE_SOCKET_NAME);
-    let retval = run_init_unix_domain_rpc_server(service, AUTHFS_SERVICE_SOCKET_NAME, || {
-        info!("The RPC server '{}' is running.", AUTHFS_SERVICE_SOCKET_NAME);
-    });
-    if retval {
-        info!("The RPC server at '{}' has shut down gracefully.", AUTHFS_SERVICE_SOCKET_NAME);
-        Ok(())
-    } else {
-        bail!("Premature termination of the RPC server '{}'.", AUTHFS_SERVICE_SOCKET_NAME)
-    }
+    let server = RpcServer::new_init_unix_domain(service, AUTHFS_SERVICE_SOCKET_NAME)?;
+    info!("The RPC server '{}' is running.", AUTHFS_SERVICE_SOCKET_NAME);
+    server.join();
+    info!("The RPC server at '{}' has shut down gracefully.", AUTHFS_SERVICE_SOCKET_NAME);
+    Ok(())
 }
 
 fn main() {
diff --git a/authfs/tests/benchmarks/src/java/com/android/fs/benchmarks/AuthFsBenchmarks.java b/authfs/tests/benchmarks/src/java/com/android/fs/benchmarks/AuthFsBenchmarks.java
index 8cee496..e67a309 100644
--- a/authfs/tests/benchmarks/src/java/com/android/fs/benchmarks/AuthFsBenchmarks.java
+++ b/authfs/tests/benchmarks/src/java/com/android/fs/benchmarks/AuthFsBenchmarks.java
@@ -28,12 +28,13 @@
 import android.platform.test.annotations.RootPermissionTest;
 
 import com.android.fs.common.AuthFsTestRule;
+import com.android.microdroid.test.common.DeviceProperties;
 import com.android.microdroid.test.common.MetricsProcessor;
-import com.android.microdroid.test.host.MicrodroidHostTestCaseBase;
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.metrics.proto.MetricMeasurement.DataType;
 import com.android.tradefed.metrics.proto.MetricMeasurement.Measurements;
 import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
 
 import org.junit.After;
 import org.junit.AfterClass;
@@ -53,7 +54,7 @@
 @RootPermissionTest
 @RunWith(DeviceJUnit4Parameterized.class)
 @UseParametersRunnerFactory(DeviceJUnit4ClassRunnerWithParameters.RunnerFactory.class)
-public class AuthFsBenchmarks extends MicrodroidHostTestCaseBase {
+public class AuthFsBenchmarks extends BaseHostJUnit4Test {
     private static final int TRIAL_COUNT = 5;
 
     /** Name of the measure_io binary on host. */
@@ -83,10 +84,10 @@
         AuthFsTestRule.setUpAndroid(getTestInformation());
         mAuthFsTestRule.setUpTest();
         assumeTrue(AuthFsTestRule.getDevice().supportsMicrodroid(mProtectedVm));
-        assumeFalse("Skip on CF; protected VM not supported", isCuttlefish());
-        String metricsPrefix =
-                MetricsProcessor.getMetricPrefix(
-                        getDevice().getProperty("debug.hypervisor.metrics_tag"));
+        DeviceProperties deviceProperties = DeviceProperties.create(getDevice()::getProperty);
+        assumeFalse(
+                "Skip on CF; no need to collect metrics on CF", deviceProperties.isCuttlefish());
+        String metricsPrefix = MetricsProcessor.getMetricPrefix(deviceProperties.getMetricsTag());
         mMetricsProcessor = new MetricsProcessor(metricsPrefix + "authfs/");
         AuthFsTestRule.startMicrodroid(mProtectedVm);
     }
diff --git a/compos/common/compos_client.rs b/compos/common/compos_client.rs
index 02459b2..601c6fc 100644
--- a/compos/common/compos_client.rs
+++ b/compos/common/compos_client.rs
@@ -32,10 +32,8 @@
 use log::{info, warn};
 use rustutils::system_properties;
 use std::fs::{self, File};
-use std::io::{BufRead, BufReader};
 use std::num::NonZeroU32;
 use std::path::{Path, PathBuf};
-use std::thread;
 use vmclient::{DeathReason, ErrorCode, VmInstance, VmWaitError};
 
 /// This owns an instance of the CompOS VM.
@@ -244,13 +242,6 @@
         log::info!("VM payload started, cid = {}", cid);
     }
 
-    fn on_payload_stdio(&self, cid: i32, stream: &File) {
-        if let Err(e) = start_logging(stream) {
-            log::warn!("Can't log vm output: {}", e);
-        };
-        log::info!("VM payload forwarded its stdio, cid = {}", cid);
-    }
-
     fn on_payload_ready(&self, cid: i32) {
         log::info!("VM payload ready, cid = {}", cid);
     }
@@ -267,19 +258,3 @@
         log::warn!("VM died, cid = {}, reason = {:?}", cid, death_reason);
     }
 }
-
-fn start_logging(file: &File) -> Result<()> {
-    let reader = BufReader::new(file.try_clone().context("Cloning file failed")?);
-    thread::spawn(move || {
-        for line in reader.lines() {
-            match line {
-                Ok(line) => info!("VM: {}", line),
-                Err(e) => {
-                    warn!("Reading VM output failed: {}", e);
-                    break;
-                }
-            }
-        }
-    });
-    Ok(())
-}
diff --git a/compos/compos_key_helper/compos_key_main.cpp b/compos/compos_key_helper/compos_key_main.cpp
index 9417584..f1637e0 100644
--- a/compos/compos_key_helper/compos_key_main.cpp
+++ b/compos/compos_key_helper/compos_key_main.cpp
@@ -38,11 +38,8 @@
 
 Result<Ed25519KeyPair> getSigningKey() {
     Seed seed;
-    if (!AVmPayload_getVmInstanceSecret(kSigningKeySeedIdentifier,
-                                        strlen(kSigningKeySeedIdentifier), seed.data(),
-                                        seed.size())) {
-        return Error() << "Failed to get signing key seed";
-    }
+    AVmPayload_getVmInstanceSecret(kSigningKeySeedIdentifier, strlen(kSigningKeySeedIdentifier),
+                                   seed.data(), seed.size());
     return compos_key::keyFromSeed(seed);
 }
 
@@ -60,16 +57,9 @@
 }
 
 int write_bcc() {
-    size_t bcc_size;
-    if (!AVmPayload_getDiceAttestationChain(nullptr, 0, &bcc_size)) {
-        LOG(ERROR) << "Failed to measure attestation chain";
-        return 1;
-    }
+    size_t bcc_size = AVmPayload_getDiceAttestationChain(nullptr, 0);
     std::vector<uint8_t> bcc(bcc_size);
-    if (!AVmPayload_getDiceAttestationChain(bcc.data(), bcc.size(), &bcc_size)) {
-        LOG(ERROR) << "Failed to get attestation chain";
-        return 1;
-    }
+    AVmPayload_getDiceAttestationChain(bcc.data(), bcc.size());
 
     if (!WriteFully(STDOUT_FILENO, bcc.data(), bcc.size())) {
         PLOG(ERROR) << "Write failed";
diff --git a/compos/src/compsvc_main.rs b/compos/src/compsvc_main.rs
index c280956..206dd4b 100644
--- a/compos/src/compsvc_main.rs
+++ b/compos/src/compsvc_main.rs
@@ -22,12 +22,12 @@
 mod compsvc;
 mod fsverity;
 
-use anyhow::{bail, Result};
+use anyhow::Result;
 use compos_common::COMPOS_VSOCK_PORT;
-use log::{debug, error, warn};
-use rpcbinder::run_vsock_rpc_server;
+use log::{debug, error};
+use rpcbinder::RpcServer;
 use std::panic;
-use vm_payload_bindgen::{AVmPayload_notifyPayloadReady, AVmPayload_setupStdioProxy};
+use vm_payload_bindgen::AVmPayload_notifyPayloadReady;
 
 fn main() {
     if let Err(e) = try_main() {
@@ -44,21 +44,12 @@
     panic::set_hook(Box::new(|panic_info| {
         error!("{}", panic_info);
     }));
-    // Redirect stdio to the host.
-    if !unsafe { AVmPayload_setupStdioProxy() } {
-        warn!("Failed to setup stdio proxy");
-    }
 
-    let service = compsvc::new_binder()?.as_binder();
     debug!("compsvc is starting as a rpc service.");
+    let service = compsvc::new_binder()?.as_binder();
+    let server = RpcServer::new_vsock(service, COMPOS_VSOCK_PORT)?;
     // SAFETY: Invokes a method from the bindgen library `vm_payload_bindgen`.
-    let retval = run_vsock_rpc_server(service, COMPOS_VSOCK_PORT, || unsafe {
-        AVmPayload_notifyPayloadReady();
-    });
-    if retval {
-        debug!("RPC server has shut down gracefully");
-        Ok(())
-    } else {
-        bail!("Premature termination of RPC server");
-    }
+    unsafe { AVmPayload_notifyPayloadReady() };
+    server.join();
+    Ok(())
 }
diff --git a/demo/Android.bp b/demo/Android.bp
index 5241e25..a291ee1 100644
--- a/demo/Android.bp
+++ b/demo/Android.bp
@@ -12,11 +12,9 @@
         "com.android.microdroid.testservice-java",
         "com.google.android.material_material",
     ],
-    libs: [
-        "framework-virtualization",
-    ],
+    sdk_version: "system_current",
     jni_libs: ["MicrodroidTestNativeLib"],
-    platform_apis: true,
+    jni_uses_platform_apis: true,
     use_embedded_native_libs: true,
     v4_signature: true,
     min_sdk_version: "33",
diff --git a/demo/java/com/android/microdroid/demo/MainActivity.java b/demo/java/com/android/microdroid/demo/MainActivity.java
index ebc2bb3..8e870ea 100644
--- a/demo/java/com/android/microdroid/demo/MainActivity.java
+++ b/demo/java/com/android/microdroid/demo/MainActivity.java
@@ -42,7 +42,6 @@
 import com.android.microdroid.testservice.ITestService;
 
 import java.io.BufferedReader;
-import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
@@ -172,13 +171,6 @@
                         public void onPayloadStarted(VirtualMachine vm) {}
 
                         @Override
-                        public void onPayloadStdio(VirtualMachine vm, ParcelFileDescriptor stream) {
-                            mPayloadOutput.postValue("(Payload connected standard output...)");
-                            InputStream input = new FileInputStream(stream.getFileDescriptor());
-                            mService.execute(new Reader("payload", mPayloadOutput, input));
-                        }
-
-                        @Override
                         public void onPayloadReady(VirtualMachine vm) {
                             // This check doesn't 100% prevent race condition or UI hang.
                             // However, it's fine for demo.
diff --git a/encryptedstore/Android.bp b/encryptedstore/Android.bp
new file mode 100644
index 0000000..13ef1b9
--- /dev/null
+++ b/encryptedstore/Android.bp
@@ -0,0 +1,31 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+rust_defaults {
+    name: "encryptedstore.defaults",
+    srcs: ["src/main.rs"],
+    edition: "2021",
+    prefer_rlib: true,
+    rustlibs: [
+        "libandroid_logger",
+        "libanyhow",
+        "liblibc",
+        "libclap",
+        "libhex",
+        "liblog_rust",
+        "libnix",
+        "libdm_rust",
+    ],
+    multilib: {
+        lib32: {
+            enabled: false,
+        },
+    },
+}
+
+rust_binary {
+    name: "encryptedstore",
+    defaults: ["encryptedstore.defaults"],
+    bootstrap: true,
+}
diff --git a/encryptedstore/src/main.rs b/encryptedstore/src/main.rs
new file mode 100644
index 0000000..d7d2382
--- /dev/null
+++ b/encryptedstore/src/main.rs
@@ -0,0 +1,159 @@
+/*
+ * 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.
+ */
+
+//! `encryptedstore` is a program that (as the name indicates) provides encrypted storage
+//! solution in a VM. This is based on dm-crypt & requires the (64 bytes') key & the backing device.
+//! It uses dm_rust lib.
+
+use anyhow::{ensure, Context, Result};
+use clap::{arg, App};
+use dm::{crypt::CipherType, util};
+use log::info;
+use std::ffi::CString;
+use std::fs::{create_dir_all, OpenOptions};
+use std::io::{Error, Read, Write};
+use std::os::unix::ffi::OsStrExt;
+use std::os::unix::fs::FileTypeExt;
+use std::path::{Path, PathBuf};
+use std::process::Command;
+
+const MK2FS_BIN: &str = "/system/bin/mke2fs";
+const UNFORMATTED_STORAGE_MAGIC: &str = "UNFORMATTED-STORAGE";
+
+fn main() -> Result<()> {
+    android_logger::init_once(
+        android_logger::Config::default()
+            .with_tag("encryptedstore")
+            .with_min_level(log::Level::Info),
+    );
+    info!("Starting encryptedstore binary");
+
+    let matches = App::new("encryptedstore")
+        .args(&[
+            arg!(--blkdevice <FILE> "the block device backing the encrypted storage")
+                .required(true),
+            arg!(--key <KEY> "key (in hex) equivalent to 64 bytes)").required(true),
+            arg!(--mountpoint <MOUNTPOINT> "mount point for the storage").required(true),
+        ])
+        .get_matches();
+
+    let blkdevice = Path::new(matches.value_of("blkdevice").unwrap());
+    let key = matches.value_of("key").unwrap();
+    let mountpoint = Path::new(matches.value_of("mountpoint").unwrap());
+    encryptedstore_init(blkdevice, key, mountpoint).context(format!(
+        "Unable to initialize encryptedstore on {:?} & mount at {:?}",
+        blkdevice, mountpoint
+    ))?;
+    Ok(())
+}
+
+fn encryptedstore_init(blkdevice: &Path, key: &str, mountpoint: &Path) -> Result<()> {
+    ensure!(
+        std::fs::metadata(&blkdevice)
+            .context(format!("Failed to get metadata of {:?}", blkdevice))?
+            .file_type()
+            .is_block_device(),
+        "The path:{:?} is not of a block device",
+        blkdevice
+    );
+
+    let needs_formatting =
+        needs_formatting(blkdevice).context("Unable to check if formatting is required")?;
+    let crypt_device =
+        enable_crypt(blkdevice, key, "cryptdev").context("Unable to map crypt device")?;
+
+    // We might need to format it with filesystem if this is a "seen-for-the-first-time" device.
+    if needs_formatting {
+        info!("Freshly formatting the crypt device");
+        format_ext4(&crypt_device)?;
+    }
+    mount(&crypt_device, mountpoint).context(format!("Unable to mount {:?}", crypt_device))?;
+    Ok(())
+}
+
+fn enable_crypt(data_device: &Path, key: &str, name: &str) -> Result<PathBuf> {
+    let dev_size = util::blkgetsize64(data_device)?;
+    let key = hex::decode(key).context("Unable to decode hex key")?;
+    ensure!(key.len() == 64, "We need 64 bytes' key for aes-xts cipher for block encryption");
+
+    // Create the dm-crypt spec
+    let target = dm::crypt::DmCryptTargetBuilder::default()
+        .data_device(data_device, dev_size)
+        .cipher(CipherType::AES256XTS) // TODO(b/259253336) Move to HCTR2 based encryption.
+        .key(&key)
+        .build()
+        .context("Couldn't build the DMCrypt target")?;
+    let dm = dm::DeviceMapper::new()?;
+    dm.create_crypt_device(name, &target).context("Failed to create dm-crypt device")
+}
+
+// The disk contains UNFORMATTED_STORAGE_MAGIC to indicate we need to format the crypt device.
+// This function looks for it, zeroing it, if present.
+fn needs_formatting(data_device: &Path) -> Result<bool> {
+    let mut file = OpenOptions::new()
+        .read(true)
+        .write(true)
+        .open(data_device)
+        .with_context(|| format!("Failed to open {:?}", data_device))?;
+
+    let mut buf = [0; UNFORMATTED_STORAGE_MAGIC.len()];
+    file.read_exact(&mut buf)?;
+
+    if buf == UNFORMATTED_STORAGE_MAGIC.as_bytes() {
+        buf.fill(0);
+        file.write_all(&buf)?;
+        return Ok(true);
+    }
+    Ok(false)
+}
+
+fn format_ext4(device: &Path) -> Result<()> {
+    let mkfs_options = [
+        "-j",               // Create appropriate sized journal
+        "-O metadata_csum", // Metadata checksum for filesystem integrity
+    ];
+    let mut cmd = Command::new(MK2FS_BIN);
+    let status = cmd
+        .args(mkfs_options)
+        .arg(device)
+        .status()
+        .context(format!("failed to execute {}", MK2FS_BIN))?;
+    ensure!(status.success(), "mkfs failed with {:?}", status);
+    Ok(())
+}
+
+fn mount(source: &Path, mountpoint: &Path) -> Result<()> {
+    create_dir_all(mountpoint).context(format!("Failed to create {:?}", &mountpoint))?;
+    let mount_options = CString::new("").unwrap();
+    let source = CString::new(source.as_os_str().as_bytes())?;
+    let mountpoint = CString::new(mountpoint.as_os_str().as_bytes())?;
+    let fstype = CString::new("ext4").unwrap();
+
+    let ret = unsafe {
+        libc::mount(
+            source.as_ptr(),
+            mountpoint.as_ptr(),
+            fstype.as_ptr(),
+            libc::MS_NOSUID | libc::MS_NODEV | libc::MS_NOEXEC,
+            mount_options.as_ptr() as *const std::ffi::c_void,
+        )
+    };
+    if ret < 0 {
+        Err(Error::last_os_error()).context("mount failed")
+    } else {
+        Ok(())
+    }
+}
diff --git a/javalib/Android.bp b/javalib/Android.bp
index 04ed273..8421231 100644
--- a/javalib/Android.bp
+++ b/javalib/Android.bp
@@ -12,19 +12,10 @@
 
 java_sdk_library {
     name: "framework-virtualization",
-    installable: false,
-    compile_dex: true,
 
     // TODO(b/243512044): introduce non-updatable-framework-module-defaults
-
     defaults: ["framework-module-defaults"],
 
-    shared_library: false,
-
-    default_to_stubs: false,
-
-    dist_group: "android",
-
     jarjar_rules: "jarjar-rules.txt",
 
     srcs: ["src/**/*.java"],
@@ -43,44 +34,31 @@
         "com.android.system.virtualmachine.sysprop",
     ],
     errorprone: {
-        // We use @GuardedBy and we want a test failure if our locking isn't consistent with it.
         enabled: true,
         javacflags: [
+            // We use @GuardedBy and we want a test failure if our locking isn't consistent with it.
             "-Xep:GuardedBy:ERROR",
+            // JavaApiUsedByMainlineModule is quite spammy, and since we com.android.virt is not
+            // an updatable module we don't need it.
+            "-Xep:JavaApiUsedByMainlineModule:OFF",
         ],
     },
 
-    public: {
-        enabled: true,
-        sdk_version: "module_current",
-    },
-
-    system: {
-        enabled: true,
-        sdk_version: "module_current",
-    },
-
-    module_lib: {
-        enabled: true,
-        sdk_version: "module_current",
-    },
-
     test: {
         enabled: true,
         sdk_version: "module_current",
     },
 
     sdk_version: "core_platform",
-    platform_apis: true,
+    stub_only_libs: [
+        "android_module_lib_stubs_current",
+    ],
     impl_only_libs: [
         "framework",
     ],
     impl_library_visibility: [
-        "//frameworks/base",
+        "//packages/modules/Virtualization:__subpackages__",
     ],
-
-    // Temporary workaround, will be removed in a follow-up child cl.
-    unsafe_ignore_missing_latest_api: true,
 }
 
 prebuilt_apis {
diff --git a/javalib/api/system-current.txt b/javalib/api/system-current.txt
index d802177..ea2d23e 100644
--- a/javalib/api/system-current.txt
+++ b/javalib/api/system-current.txt
@@ -1 +1,109 @@
 // Signature format: 2.0
+package android.system.virtualmachine {
+
+  public class VirtualMachine implements java.lang.AutoCloseable {
+    method public void clearCallback();
+    method public void close() throws android.system.virtualmachine.VirtualMachineException;
+    method @NonNull public android.os.IBinder connectToVsockServer(int) throws android.system.virtualmachine.VirtualMachineException;
+    method @NonNull public android.os.ParcelFileDescriptor connectVsock(int) throws android.system.virtualmachine.VirtualMachineException;
+    method public int getCid() throws android.system.virtualmachine.VirtualMachineException;
+    method @NonNull public android.system.virtualmachine.VirtualMachineConfig getConfig();
+    method @NonNull public java.io.InputStream getConsoleOutput() throws android.system.virtualmachine.VirtualMachineException;
+    method @NonNull public java.io.InputStream getLogOutput() throws android.system.virtualmachine.VirtualMachineException;
+    method @NonNull public String getName();
+    method public int getStatus();
+    method @RequiresPermission(android.system.virtualmachine.VirtualMachine.MANAGE_VIRTUAL_MACHINE_PERMISSION) public void run() throws android.system.virtualmachine.VirtualMachineException;
+    method public void setCallback(@NonNull java.util.concurrent.Executor, @NonNull android.system.virtualmachine.VirtualMachineCallback);
+    method @NonNull public android.system.virtualmachine.VirtualMachineConfig setConfig(@NonNull android.system.virtualmachine.VirtualMachineConfig) throws android.system.virtualmachine.VirtualMachineException;
+    method public void stop() throws android.system.virtualmachine.VirtualMachineException;
+    method @NonNull public android.system.virtualmachine.VirtualMachineDescriptor toDescriptor() throws android.system.virtualmachine.VirtualMachineException;
+    field public static final String MANAGE_VIRTUAL_MACHINE_PERMISSION = "android.permission.MANAGE_VIRTUAL_MACHINE";
+    field public static final int STATUS_DELETED = 2; // 0x2
+    field public static final int STATUS_RUNNING = 1; // 0x1
+    field public static final int STATUS_STOPPED = 0; // 0x0
+    field public static final String USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION = "android.permission.USE_CUSTOM_VIRTUAL_MACHINE";
+  }
+
+  public interface VirtualMachineCallback {
+    method public void onError(@NonNull android.system.virtualmachine.VirtualMachine, int, @NonNull String);
+    method public void onPayloadFinished(@NonNull android.system.virtualmachine.VirtualMachine, int);
+    method public void onPayloadReady(@NonNull android.system.virtualmachine.VirtualMachine);
+    method public void onPayloadStarted(@NonNull android.system.virtualmachine.VirtualMachine);
+    method public void onRamdump(@NonNull android.system.virtualmachine.VirtualMachine, @NonNull android.os.ParcelFileDescriptor);
+    method public void onStopped(@NonNull android.system.virtualmachine.VirtualMachine, int);
+    field public static final int ERROR_PAYLOAD_CHANGED = 2; // 0x2
+    field public static final int ERROR_PAYLOAD_INVALID_CONFIG = 3; // 0x3
+    field public static final int ERROR_PAYLOAD_VERIFICATION_FAILED = 1; // 0x1
+    field public static final int ERROR_UNKNOWN = 0; // 0x0
+    field public static final int STOP_REASON_BOOTLOADER_INSTANCE_IMAGE_CHANGED = 10; // 0xa
+    field public static final int STOP_REASON_BOOTLOADER_PUBLIC_KEY_MISMATCH = 9; // 0x9
+    field public static final int STOP_REASON_CRASH = 6; // 0x6
+    field public static final int STOP_REASON_ERROR = 4; // 0x4
+    field public static final int STOP_REASON_HANGUP = 16; // 0x10
+    field public static final int STOP_REASON_INFRASTRUCTURE_ERROR = 0; // 0x0
+    field public static final int STOP_REASON_KILLED = 1; // 0x1
+    field public static final int STOP_REASON_MICRODROID_FAILED_TO_CONNECT_TO_VIRTUALIZATION_SERVICE = 11; // 0xb
+    field public static final int STOP_REASON_MICRODROID_INVALID_PAYLOAD_CONFIG = 14; // 0xe
+    field public static final int STOP_REASON_MICRODROID_PAYLOAD_HAS_CHANGED = 12; // 0xc
+    field public static final int STOP_REASON_MICRODROID_PAYLOAD_VERIFICATION_FAILED = 13; // 0xd
+    field public static final int STOP_REASON_MICRODROID_UNKNOWN_RUNTIME_ERROR = 15; // 0xf
+    field public static final int STOP_REASON_PVM_FIRMWARE_INSTANCE_IMAGE_CHANGED = 8; // 0x8
+    field public static final int STOP_REASON_PVM_FIRMWARE_PUBLIC_KEY_MISMATCH = 7; // 0x7
+    field public static final int STOP_REASON_REBOOT = 5; // 0x5
+    field public static final int STOP_REASON_SHUTDOWN = 3; // 0x3
+    field public static final int STOP_REASON_UNKNOWN = 2; // 0x2
+    field public static final int STOP_REASON_VIRTUALIZATION_SERVICE_DIED = -1; // 0xffffffff
+  }
+
+  public final class VirtualMachineConfig {
+    method @NonNull public String getApkPath();
+    method @NonNull public int getDebugLevel();
+    method @IntRange(from=0) public int getMemoryMib();
+    method @IntRange(from=1) public int getNumCpus();
+    method @Nullable public String getPayloadBinaryPath();
+    method @Nullable public String getPayloadConfigPath();
+    method public boolean isCompatibleWith(@NonNull android.system.virtualmachine.VirtualMachineConfig);
+    method public boolean isProtectedVm();
+    field public static final int DEBUG_LEVEL_APP_ONLY = 1; // 0x1
+    field public static final int DEBUG_LEVEL_FULL = 2; // 0x2
+    field public static final int DEBUG_LEVEL_NONE = 0; // 0x0
+  }
+
+  public static final class VirtualMachineConfig.Builder {
+    ctor public VirtualMachineConfig.Builder(@NonNull android.content.Context);
+    method @NonNull public android.system.virtualmachine.VirtualMachineConfig build();
+    method @NonNull public android.system.virtualmachine.VirtualMachineConfig.Builder setApkPath(@NonNull String);
+    method @NonNull public android.system.virtualmachine.VirtualMachineConfig.Builder setDebugLevel(int);
+    method @NonNull public android.system.virtualmachine.VirtualMachineConfig.Builder setMemoryMib(@IntRange(from=0) int);
+    method @NonNull public android.system.virtualmachine.VirtualMachineConfig.Builder setNumCpus(@IntRange(from=1) int);
+    method @NonNull public android.system.virtualmachine.VirtualMachineConfig.Builder setPayloadBinaryPath(@NonNull String);
+    method @NonNull @RequiresPermission(android.system.virtualmachine.VirtualMachine.USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION) public android.system.virtualmachine.VirtualMachineConfig.Builder setPayloadConfigPath(@NonNull String);
+    method @NonNull public android.system.virtualmachine.VirtualMachineConfig.Builder setProtectedVm(boolean);
+  }
+
+  public final class VirtualMachineDescriptor implements android.os.Parcelable {
+    method public int describeContents();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.system.virtualmachine.VirtualMachineDescriptor> CREATOR;
+  }
+
+  public class VirtualMachineException extends java.lang.Exception {
+    ctor public VirtualMachineException(@Nullable String);
+    ctor public VirtualMachineException(@Nullable String, @Nullable Throwable);
+    ctor public VirtualMachineException(@Nullable Throwable);
+  }
+
+  public class VirtualMachineManager {
+    method @NonNull @RequiresPermission(android.system.virtualmachine.VirtualMachine.MANAGE_VIRTUAL_MACHINE_PERMISSION) public android.system.virtualmachine.VirtualMachine create(@NonNull String, @NonNull android.system.virtualmachine.VirtualMachineConfig) throws android.system.virtualmachine.VirtualMachineException;
+    method public void delete(@NonNull String) throws android.system.virtualmachine.VirtualMachineException;
+    method @Nullable public android.system.virtualmachine.VirtualMachine get(@NonNull String) throws android.system.virtualmachine.VirtualMachineException;
+    method public int getCapabilities();
+    method @NonNull public static android.system.virtualmachine.VirtualMachineManager getInstance(@NonNull android.content.Context);
+    method @NonNull public android.system.virtualmachine.VirtualMachine getOrCreate(@NonNull String, @NonNull android.system.virtualmachine.VirtualMachineConfig) throws android.system.virtualmachine.VirtualMachineException;
+    method @NonNull public android.system.virtualmachine.VirtualMachine importFromDescriptor(@NonNull String, @NonNull android.system.virtualmachine.VirtualMachineDescriptor) throws android.system.virtualmachine.VirtualMachineException;
+    field public static final int CAPABILITY_NON_PROTECTED_VM = 2; // 0x2
+    field public static final int CAPABILITY_PROTECTED_VM = 1; // 0x1
+  }
+
+}
+
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachine.java b/javalib/src/android/system/virtualmachine/VirtualMachine.java
index c200d00..193d213 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachine.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachine.java
@@ -48,6 +48,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
 import android.content.Context;
 import android.os.Binder;
 import android.os.IBinder;
@@ -76,7 +77,6 @@
 import java.io.InputStreamReader;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
-import java.lang.ref.WeakReference;
 import java.nio.channels.FileChannel;
 import java.nio.file.FileAlreadyExistsException;
 import java.nio.file.FileVisitResult;
@@ -86,10 +86,7 @@
 import java.nio.file.attribute.BasicFileAttributes;
 import java.util.ArrayList;
 import java.util.Collections;
-import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
-import java.util.WeakHashMap;
 import java.util.concurrent.Executor;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.function.Consumer;
@@ -98,20 +95,16 @@
 /**
  * Represents an VM instance, with its own configuration and state. Instances are persistent and are
  * created or retrieved via {@link VirtualMachineManager}.
- * <p>
- * The {@link #run} method actually starts up the VM and allows the payload code to execute. It
- * will continue until it exits or {@link #stop} is called. Updates on the state of the VM can
- * be received using {@link #setCallback}. The app can communicate with the VM using
- * {@link #connectToVsockServer} or {@link #connectVsock}.
+ *
+ * <p>The {@link #run} method actually starts up the VM and allows the payload code to execute. It
+ * will continue until it exits or {@link #stop} is called. Updates on the state of the VM can be
+ * received using {@link #setCallback}. The app can communicate with the VM using {@link
+ * #connectToVsockServer} or {@link #connectVsock}.
  *
  * @hide
  */
+@SystemApi
 public class VirtualMachine implements AutoCloseable {
-    /** Map from context to a map of all that context's VMs by name. */
-    @GuardedBy("sCreateLock")
-    private static final Map<Context, Map<String, WeakReference<VirtualMachine>>> sInstances =
-            new WeakHashMap<>();
-
     /** Name of the directory under the files directory where all VMs created for the app exist. */
     private static final String VM_DIR = "vm";
 
@@ -208,17 +201,10 @@
     private static final long INSTANCE_FILE_SIZE = 10 * 1024 * 1024;
 
     // A note on lock ordering:
-    // You can take mLock while holding sCreateLock, but not vice versa.
+    // You can take mLock while holding VirtualMachineManager.sCreateLock, but not vice versa.
     // We never take any other lock while holding mCallbackLock; therefore you can
     // take mCallbackLock while holding any other lock.
 
-    /**
-     * A lock used to synchronize the creation of virtual machines. It protects
-     * {@link #sInstances}, but is also held throughout VM creation / retrieval / deletion, to
-     * prevent these actions racing with each other.
-     */
-    static final Object sCreateLock = new Object();
-
     /** Lock protecting our mutable state (other than callbacks). */
     private final Object mLock = new Object();
 
@@ -281,12 +267,6 @@
         mExtraApks = setupExtraApks(context, config, thisVmDir);
     }
 
-    @GuardedBy("sCreateLock")
-    @NonNull
-    private static Map<String, WeakReference<VirtualMachine>> getInstancesMap(Context context) {
-        return sInstances.computeIfAbsent(context, unused -> new HashMap<>());
-    }
-
     /**
      * Builds a virtual machine from an {@link VirtualMachineDescriptor} object and associates it
      * with the given name.
@@ -297,7 +277,7 @@
      * #delete}. The imported virtual machine is in {@link #STATUS_STOPPED} state. To run the VM,
      * call {@link #run}.
      */
-    @GuardedBy("sCreateLock")
+    @GuardedBy("VirtualMachineManager.sCreateLock")
     @NonNull
     static VirtualMachine fromDescriptor(
             @NonNull Context context,
@@ -315,7 +295,6 @@
                 throw new VirtualMachineException("failed to create instance image", e);
             }
             vm.importInstanceFrom(vmDescriptor.getInstanceImgFd());
-            getInstancesMap(context).put(name, new WeakReference<>(vm));
             return vm;
         } catch (VirtualMachineException | RuntimeException e) {
             // If anything goes wrong, delete any files created so far and the VM's directory
@@ -333,7 +312,7 @@
      * it is persisted until it is deleted by calling {@link #delete}. The created virtual machine
      * is in {@link #STATUS_STOPPED} state. To run the VM, call {@link #run}.
      */
-    @GuardedBy("sCreateLock")
+    @GuardedBy("VirtualMachineManager.sCreateLock")
     @NonNull
     static VirtualMachine create(
             @NonNull Context context, @NonNull String name, @NonNull VirtualMachineConfig config)
@@ -365,9 +344,6 @@
             } catch (ServiceSpecificException | IllegalArgumentException e) {
                 throw new VirtualMachineException("failed to create instance partition", e);
             }
-
-            getInstancesMap(context).put(name, new WeakReference<>(vm));
-
             return vm;
         } catch (VirtualMachineException | RuntimeException e) {
             // If anything goes wrong, delete any files created so far and the VM's directory
@@ -381,10 +357,10 @@
     }
 
     /** Loads a virtual machine that is already created before. */
-    @GuardedBy("sCreateLock")
+    @GuardedBy("VirtualMachineManager.sCreateLock")
     @Nullable
-    static VirtualMachine load(
-            @NonNull Context context, @NonNull String name) throws VirtualMachineException {
+    static VirtualMachine load(@NonNull Context context, @NonNull String name)
+            throws VirtualMachineException {
         File thisVmDir = getVmDir(context, name);
         if (!thisVmDir.exists()) {
             // The VM doesn't exist.
@@ -392,51 +368,32 @@
         }
         File configFilePath = new File(thisVmDir, CONFIG_FILE);
         VirtualMachineConfig config = VirtualMachineConfig.from(configFilePath);
-        Map<String, WeakReference<VirtualMachine>> instancesMap = getInstancesMap(context);
-
-        VirtualMachine vm = null;
-        if (instancesMap.containsKey(name)) {
-            vm = instancesMap.get(name).get();
-        }
-        if (vm == null) {
-            vm = new VirtualMachine(context, name, config);
-        }
+        VirtualMachine vm = new VirtualMachine(context, name, config);
 
         if (!vm.mInstanceFilePath.exists()) {
             throw new VirtualMachineException("instance image missing");
         }
 
-        instancesMap.put(name, new WeakReference<>(vm));
-
         return vm;
     }
 
-    @GuardedBy("sCreateLock")
-    static void delete(Context context, String name) throws VirtualMachineException {
-        Map<String, WeakReference<VirtualMachine>> instancesMap = sInstances.get(context);
-        VirtualMachine vm;
-        if (instancesMap != null && instancesMap.containsKey(name)) {
-            vm = instancesMap.get(name).get();
-        } else {
-            vm = null;
+    @GuardedBy("VirtualMachineManager.sCreateLock")
+    void delete(Context context, String name) throws VirtualMachineException {
+        synchronized (mLock) {
+            checkStopped();
+            deleteVmDirectory(context, name);
         }
+    }
 
-        if (vm != null) {
-            synchronized (vm.mLock) {
-                vm.checkStopped();
-            }
-        }
-
+    static void deleteVmDirectory(Context context, String name) throws VirtualMachineException {
         try {
             deleteRecursively(getVmDir(context, name));
         } catch (IOException e) {
             throw new VirtualMachineException(e);
         }
-
-        if (instancesMap != null) instancesMap.remove(name);
     }
 
-    @GuardedBy("sCreateLock")
+    @GuardedBy("VirtualMachineManager.sCreateLock")
     @NonNull
     private static File createVmDir(@NonNull Context context, @NonNull String name)
             throws VirtualMachineException {
@@ -469,6 +426,7 @@
      *
      * @hide
      */
+    @SystemApi
     @NonNull
     public String getName() {
         return mName;
@@ -483,6 +441,7 @@
      *
      * @hide
      */
+    @SystemApi
     @NonNull
     public VirtualMachineConfig getConfig() {
         synchronized (mLock) {
@@ -495,6 +454,7 @@
      *
      * @hide
      */
+    @SystemApi
     @Status
     public int getStatus() {
         IVirtualMachine virtualMachine;
@@ -569,7 +529,9 @@
      *
      * @hide
      */
-    public void setCallback(@NonNull @CallbackExecutor Executor executor,
+    @SystemApi
+    public void setCallback(
+            @NonNull @CallbackExecutor Executor executor,
             @NonNull VirtualMachineCallback callback) {
         synchronized (mCallbackLock) {
             mCallback = callback;
@@ -582,6 +544,7 @@
      *
      * @hide
      */
+    @SystemApi
     public void clearCallback() {
         synchronized (mCallbackLock) {
             mCallback = null;
@@ -615,9 +578,10 @@
      * calling {@code run()}.
      *
      * @throws VirtualMachineException if the virtual machine is not stopped or could not be
-     *         started.
+     *     started.
      * @hide
      */
+    @SystemApi
     @RequiresPermission(MANAGE_VIRTUAL_MACHINE_PERMISSION)
     public void run() throws VirtualMachineException {
         synchronized (mLock) {
@@ -686,12 +650,6 @@
                             }
 
                             @Override
-                            public void onPayloadStdio(int cid, ParcelFileDescriptor stream) {
-                                executeCallback(
-                                        (cb) -> cb.onPayloadStdio(VirtualMachine.this, stream));
-                            }
-
-                            @Override
                             public void onPayloadReady(int cid) {
                                 executeCallback((cb) -> cb.onPayloadReady(VirtualMachine.this));
                             }
@@ -767,6 +725,7 @@
      * @throws VirtualMachineException if the stream could not be created.
      * @hide
      */
+    @SystemApi
     @NonNull
     public InputStream getConsoleOutput() throws VirtualMachineException {
         synchronized (mLock) {
@@ -781,6 +740,7 @@
      * @throws VirtualMachineException if the stream could not be created.
      * @hide
      */
+    @SystemApi
     @NonNull
     public InputStream getLogOutput() throws VirtualMachineException {
         synchronized (mLock) {
@@ -792,12 +752,12 @@
     /**
      * Stops this virtual machine. Stopping a virtual machine is like pulling the plug on a real
      * computer; the machine halts immediately. Software running on the virtual machine is not
-     * notified of the event. A stopped virtual machine can be re-started by calling {@link
-     * #run()}.
+     * notified of the event. A stopped virtual machine can be re-started by calling {@link #run()}.
      *
      * @throws VirtualMachineException if the virtual machine could not be stopped.
      * @hide
      */
+    @SystemApi
     public void stop() throws VirtualMachineException {
         synchronized (mLock) {
             if (mVirtualMachine == null) {
@@ -820,6 +780,7 @@
      * @throws VirtualMachineException if the virtual machine could not be stopped.
      * @hide
      */
+    @SystemApi
     @Override
     public void close() throws VirtualMachineException {
         stop();
@@ -852,6 +813,7 @@
      * @throws VirtualMachineException if the virtual machine is not running.
      * @hide
      */
+    @SystemApi
     public int getCid() throws VirtualMachineException {
         synchronized (mLock) {
             try {
@@ -867,14 +829,15 @@
      * like the number of CPU and size of the RAM, depending on the situation (e.g. the size of the
      * application to run on the virtual machine, etc.)
      *
-     * The new config must be {@link VirtualMachineConfig#isCompatibleWith compatible with} the
+     * <p>The new config must be {@link VirtualMachineConfig#isCompatibleWith compatible with} the
      * existing config.
      *
      * @return the old config
      * @throws VirtualMachineException if the virtual machine is not stopped, or the new config is
-     *         incompatible.
+     *     incompatible.
      * @hide
      */
+    @SystemApi
     @NonNull
     public VirtualMachineConfig setConfig(@NonNull VirtualMachineConfig newConfig)
             throws VirtualMachineException {
@@ -884,6 +847,10 @@
                 throw new VirtualMachineException("incompatible config");
             }
             checkStopped();
+
+            // Delete any existing file before recreating; that ensures any VirtualMachineDescriptor
+            // that refers to the old file does not see the new config.
+            mConfigFilePath.delete();
             newConfig.serialize(mConfigFilePath);
             mConfig = newConfig;
             return oldConfig;
@@ -896,13 +863,14 @@
     /**
      * Connect to a VM's binder service via vsock and return the root IBinder object. Guest VMs are
      * expected to set up vsock servers in their payload. After the host app receives the {@link
-     * VirtualMachineCallback#onPayloadReady(VirtualMachine)}, it can use this method to
-     * establish a connection to the guest VM.
+     * VirtualMachineCallback#onPayloadReady(VirtualMachine)}, it can use this method to establish a
+     * connection to the guest VM.
      *
      * @throws VirtualMachineException if the virtual machine is not running or the connection
-     *         failed.
+     *     failed.
      * @hide
      */
+    @SystemApi
     @NonNull
     public IBinder connectToVsockServer(int port) throws VirtualMachineException {
         synchronized (mLock) {
@@ -920,6 +888,7 @@
      * @throws VirtualMachineException if connecting fails.
      * @hide
      */
+    @SystemApi
     @NonNull
     public ParcelFileDescriptor connectVsock(int port) throws VirtualMachineException {
         synchronized (mLock) {
@@ -937,22 +906,27 @@
      * Captures the current state of the VM in a {@link VirtualMachineDescriptor} instance. The VM
      * needs to be stopped to avoid inconsistency in its state representation.
      *
+     * <p>The state of the VM is not actually copied until {@link
+     * VirtualMachineManager#importFromDescriptor} is called. It is recommended that the VM not be
+     * started until that operation is complete.
+     *
      * @return a {@link VirtualMachineDescriptor} instance that represents the VM's state.
      * @throws VirtualMachineException if the virtual machine is not stopped, or the state could not
      *     be captured.
      * @hide
      */
+    @SystemApi
     @NonNull
     public VirtualMachineDescriptor toDescriptor() throws VirtualMachineException {
         synchronized (mLock) {
             checkStopped();
-        }
-        try {
-            return new VirtualMachineDescriptor(
-                    ParcelFileDescriptor.open(mConfigFilePath, MODE_READ_ONLY),
-                    ParcelFileDescriptor.open(mInstanceFilePath, MODE_READ_ONLY));
-        } catch (IOException e) {
-            throw new VirtualMachineException(e);
+            try {
+                return new VirtualMachineDescriptor(
+                        ParcelFileDescriptor.open(mConfigFilePath, MODE_READ_ONLY),
+                        ParcelFileDescriptor.open(mInstanceFilePath, MODE_READ_ONLY));
+            } catch (IOException e) {
+                throw new VirtualMachineException(e);
+            }
         }
     }
 
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachineCallback.java b/javalib/src/android/system/virtualmachine/VirtualMachineCallback.java
index 26b8ba2..f3c4831 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachineCallback.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachineCallback.java
@@ -19,6 +19,7 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
 import android.os.ParcelFileDescriptor;
 
 import java.lang.annotation.Retention;
@@ -30,7 +31,8 @@
  *
  * @hide
  */
-@SuppressLint("CallbackInterface")  // Guidance has changed, lint is out of date (b/245552641)
+@SystemApi
+@SuppressLint("CallbackInterface") // Guidance has changed, lint is out of date (b/245552641)
 public interface VirtualMachineCallback {
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
@@ -137,9 +139,6 @@
     /** Called when the payload starts in the VM. */
     void onPayloadStarted(@NonNull VirtualMachine vm);
 
-    /** Called when the payload creates a standard input/output stream. */
-    void onPayloadStdio(@NonNull VirtualMachine vm, @NonNull ParcelFileDescriptor stream);
-
     /**
      * Called when the payload in the VM is ready to serve. See
      * {@link VirtualMachine#connectToVsockServer(int)}.
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java b/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java
index a660306..8678b99 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java
@@ -26,6 +26,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
 import android.content.Context;
 import android.os.ParcelFileDescriptor;
 import android.os.PersistableBundle;
@@ -51,6 +52,7 @@
  *
  * @hide
  */
+@SystemApi
 public final class VirtualMachineConfig {
     // These define the schema of the config file persisted on disk.
     private static final int VERSION = 2;
@@ -76,12 +78,12 @@
     public @interface DebugLevel {}
 
     /**
-     * Not debuggable at all. No log is exported from the VM. Debugger can't be attached to the
-     * app process running in the VM. This is the default level.
+     * Not debuggable at all. No log is exported from the VM. Debugger can't be attached to the app
+     * process running in the VM. This is the default level.
      *
      * @hide
      */
-    public static final int DEBUG_LEVEL_NONE = 0;
+    @SystemApi public static final int DEBUG_LEVEL_NONE = 0;
 
     /**
      * Only the app is debuggable. Log from the app is exported from the VM. Debugger can be
@@ -89,7 +91,7 @@
      *
      * @hide
      */
-    public static final int DEBUG_LEVEL_APP_ONLY = 1;
+    @SystemApi public static final int DEBUG_LEVEL_APP_ONLY = 1;
 
     /**
      * Fully debuggable. All logs (both logcat and kernel message) are exported. All processes
@@ -97,7 +99,7 @@
      *
      * @hide
      */
-    public static final int DEBUG_LEVEL_FULL = 2;
+    @SystemApi public static final int DEBUG_LEVEL_FULL = 2;
 
     @DebugLevel private final int mDebugLevel;
 
@@ -270,28 +272,31 @@
      *
      * @hide
      */
+    @SystemApi
     @NonNull
     public String getApkPath() {
         return mApkPath;
     }
 
     /**
-     * Returns the path within the APK to the payload config file that defines software aspects
-     * of the VM.
+     * Returns the path within the APK to the payload config file that defines software aspects of
+     * the VM.
      *
      * @hide
      */
+    @SystemApi // TODO(b/243512115): Switch back to @TestApi
     @Nullable
     public String getPayloadConfigPath() {
         return mPayloadConfigPath;
     }
 
     /**
-     * Returns the path within the {@code lib/<ABI>} directory of the APK to the payload binary
-     * file that will be executed within the VM.
+     * Returns the path within the {@code lib/<ABI>} directory of the APK to the payload binary file
+     * that will be executed within the VM.
      *
      * @hide
      */
+    @SystemApi
     @Nullable
     public String getPayloadBinaryPath() {
         return mPayloadBinaryPath;
@@ -302,6 +307,7 @@
      *
      * @hide
      */
+    @SystemApi
     @NonNull
     @DebugLevel
     public int getDebugLevel() {
@@ -313,6 +319,7 @@
      *
      * @hide
      */
+    @SystemApi
     public boolean isProtectedVm() {
         return mProtectedVm;
     }
@@ -322,6 +329,7 @@
      *
      * @hide
      */
+    @SystemApi
     @IntRange(from = 0)
     public int getMemoryMib() {
         return mMemoryMib;
@@ -332,6 +340,7 @@
      *
      * @hide
      */
+    @SystemApi
     @IntRange(from = 1)
     public int getNumCpus() {
         return mNumCpus;
@@ -345,6 +354,7 @@
      *
      * @hide
      */
+    @SystemApi
     public boolean isCompatibleWith(@NonNull VirtualMachineConfig other) {
         return this.mDebugLevel == other.mDebugLevel
                 && this.mProtectedVm == other.mProtectedVm
@@ -397,6 +407,7 @@
      *
      * @hide
      */
+    @SystemApi
     public static final class Builder {
         private final Context mContext;
         @Nullable private String mApkPath;
@@ -413,6 +424,7 @@
          *
          * @hide
          */
+        @SystemApi
         public Builder(@NonNull Context context) {
             mContext = requireNonNull(context, "context must not be null");
             mDebugLevel = DEBUG_LEVEL_NONE;
@@ -424,6 +436,7 @@
          *
          * @hide
          */
+        @SystemApi
         @NonNull
         public VirtualMachineConfig build() {
             String apkPath = (mApkPath == null) ? mContext.getPackageCodePath() : mApkPath;
@@ -443,6 +456,7 @@
          *
          * @hide
          */
+        @SystemApi
         @NonNull
         public Builder setApkPath(@NonNull String apkPath) {
             mApkPath = requireNonNull(apkPath);
@@ -450,13 +464,14 @@
         }
 
         /**
-         * Sets the path within the APK to the payload config file that defines software aspects
-         * of the VM. The file is a JSON file; see
+         * Sets the path within the APK to the payload config file that defines software aspects of
+         * the VM. The file is a JSON file; see
          * packages/modules/Virtualization/microdroid/payload/config/src/lib.rs for the format.
          *
          * @hide
          */
         @RequiresPermission(VirtualMachine.USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION)
+        @SystemApi // TODO(b/243512115): Switch to @TestApi
         @NonNull
         public Builder setPayloadConfigPath(@NonNull String payloadConfigPath) {
             mPayloadConfigPath = requireNonNull(payloadConfigPath);
@@ -469,6 +484,7 @@
          *
          * @hide
          */
+        @SystemApi
         @NonNull
         public Builder setPayloadBinaryPath(@NonNull String payloadBinaryPath) {
             mPayloadBinaryPath = requireNonNull(payloadBinaryPath);
@@ -480,6 +496,7 @@
          *
          * @hide
          */
+        @SystemApi
         @NonNull
         public Builder setDebugLevel(@DebugLevel int debugLevel) {
             mDebugLevel = debugLevel;
@@ -487,12 +504,13 @@
         }
 
         /**
-         * Sets whether to protect the VM memory from the host. No default is provided, this
-         * must be set explicitly.
+         * Sets whether to protect the VM memory from the host. No default is provided, this must be
+         * set explicitly.
          *
          * @see VirtualMachineManager#getCapabilities
          * @hide
          */
+        @SystemApi
         @NonNull
         public Builder setProtectedVm(boolean protectedVm) {
             mProtectedVm = protectedVm;
@@ -501,11 +519,12 @@
         }
 
         /**
-         * Sets the amount of RAM to give the VM, in mebibytes. If zero or not explicitly set
-         * than a default size will be used.
+         * Sets the amount of RAM to give the VM, in mebibytes. If zero or not explicitly set then a
+         * default size will be used.
          *
          * @hide
          */
+        @SystemApi
         @NonNull
         public Builder setMemoryMib(@IntRange(from = 0) int memoryMib) {
             mMemoryMib = memoryMib;
@@ -513,11 +532,12 @@
         }
 
         /**
-         * Sets the number of vCPUs in the VM. Defaults to 1. Cannot be more than the number of
-         * real CPUs (as returned by {@link Runtime#availableProcessors()}).
+         * Sets the number of vCPUs in the VM. Defaults to 1. Cannot be more than the number of real
+         * CPUs (as returned by {@link Runtime#availableProcessors()}).
          *
          * @hide
          */
+        @SystemApi
         @NonNull
         public Builder setNumCpus(@IntRange(from = 1) int num) {
             mNumCpus = num;
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachineDescriptor.java b/javalib/src/android/system/virtualmachine/VirtualMachineDescriptor.java
index b51cbce..edaf5b4 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachineDescriptor.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachineDescriptor.java
@@ -19,6 +19,7 @@
 import static java.util.Objects.requireNonNull;
 
 import android.annotation.NonNull;
+import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.ParcelFileDescriptor;
 import android.os.Parcelable;
@@ -32,6 +33,7 @@
  *
  * @hide
  */
+@SystemApi
 public final class VirtualMachineDescriptor implements Parcelable {
     @NonNull private final ParcelFileDescriptor mConfigFd;
     @NonNull private final ParcelFileDescriptor mInstanceImgFd;
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachineException.java b/javalib/src/android/system/virtualmachine/VirtualMachineException.java
index 828775a..985eb70 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachineException.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachineException.java
@@ -17,12 +17,14 @@
 package android.system.virtualmachine;
 
 import android.annotation.Nullable;
+import android.annotation.SystemApi;
 
 /**
  * Exception thrown when operations on virtual machines fail.
  *
  * @hide
  */
+@SystemApi
 public class VirtualMachineException extends Exception {
     public VirtualMachineException(@Nullable String message) {
         super(message);
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachineManager.java b/javalib/src/android/system/virtualmachine/VirtualMachineManager.java
index c357f50..a520ab4 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachineManager.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachineManager.java
@@ -23,8 +23,10 @@
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
 import android.content.Context;
 import android.sysprop.HypervisorProperties;
+import android.util.ArrayMap;
 
 import com.android.internal.annotations.GuardedBy;
 
@@ -36,17 +38,25 @@
 
 /**
  * Manages {@link VirtualMachine virtual machine} instances created by an app. Each instance is
- * created from a {@link VirtualMachineConfig configuration} that defines the shape of the VM
- * (RAM, CPUs), the code to execute within it, etc.
- * <p>
- * Each virtual machine instance is named; the configuration and related state of each is
+ * created from a {@link VirtualMachineConfig configuration} that defines the shape of the VM (RAM,
+ * CPUs), the code to execute within it, etc.
+ *
+ * <p>Each virtual machine instance is named; the configuration and related state of each is
  * persisted in the app's private data directory and an instance can be retrieved given the name.
- * <p>
- * The app can then start, stop and otherwise interact with the VM.
+ *
+ * <p>The app can then start, stop and otherwise interact with the VM.
  *
  * @hide
  */
+@SystemApi
 public class VirtualMachineManager {
+    /**
+     * A lock used to synchronize the creation of virtual machines. It protects {@link #mVmsByName},
+     * but is also held throughout VM creation / retrieval / deletion, to prevent these actions
+     * racing with each other.
+     */
+    private static final Object sCreateLock = new Object();
+
     @NonNull private final Context mContext;
 
     private VirtualMachineManager(@NonNull Context context) {
@@ -57,6 +67,9 @@
     private static final Map<Context, WeakReference<VirtualMachineManager>> sInstances =
             new WeakHashMap<>();
 
+    @GuardedBy("sCreateLock")
+    private final Map<String, WeakReference<VirtualMachine>> mVmsByName = new ArrayMap<>();
+
     /**
      * Capabilities of the virtual machine implementation.
      *
@@ -84,8 +97,9 @@
      *
      * @hide
      */
+    @SystemApi
     @NonNull
-    @SuppressLint("ManagerLookup") // Optional API
+    @SuppressLint("ManagerLookup") // TODO(b/249093790): remove
     public static VirtualMachineManager getInstance(@NonNull Context context) {
         requireNonNull(context, "context must not be null");
         synchronized (sInstances) {
@@ -106,6 +120,7 @@
      * @see #CAPABILITY_NON_PROTECTED_VM
      * @hide
      */
+    @SystemApi
     @Capability
     public int getCapabilities() {
         @Capability int result = 0;
@@ -123,24 +138,62 @@
      * machine with the same name as an existing virtual machine is an error. The existing virtual
      * machine has to be deleted before its name can be reused.
      *
-     * Each successful call to this method creates a new (and different) virtual machine even if the
-     * name and the config are the same as a deleted one. The new virtual machine will initially
+     * <p>Each successful call to this method creates a new (and different) virtual machine even if
+     * the name and the config are the same as a deleted one. The new virtual machine will initially
      * be stopped.
      *
      * @throws VirtualMachineException if the VM cannot be created, or there is an existing VM with
-     *         the given name.
+     *     the given name.
      * @hide
      */
+    @SystemApi
     @NonNull
     @RequiresPermission(VirtualMachine.MANAGE_VIRTUAL_MACHINE_PERMISSION)
-    public VirtualMachine create(
-            @NonNull String name, @NonNull VirtualMachineConfig config)
+    public VirtualMachine create(@NonNull String name, @NonNull VirtualMachineConfig config)
             throws VirtualMachineException {
-        synchronized (VirtualMachine.sCreateLock) {
-            return VirtualMachine.create(mContext, name, config);
+        synchronized (sCreateLock) {
+            return createLocked(name, config);
         }
     }
 
+    @NonNull
+    @GuardedBy("sCreateLock")
+    private VirtualMachine createLocked(@NonNull String name, @NonNull VirtualMachineConfig config)
+            throws VirtualMachineException {
+        VirtualMachine vm = VirtualMachine.create(mContext, name, config);
+        mVmsByName.put(name, new WeakReference<>(vm));
+        return vm;
+    }
+
+    /**
+     * Returns an existing {@link VirtualMachine} with the given name. Returns null if there is no
+     * such virtual machine.
+     *
+     * @throws VirtualMachineException if the virtual machine exists but could not be successfully
+     *     retrieved.
+     * @hide
+     */
+    @SystemApi
+    @Nullable
+    public VirtualMachine get(@NonNull String name) throws VirtualMachineException {
+        synchronized (sCreateLock) {
+            return getLocked(name);
+        }
+    }
+
+    @Nullable
+    @GuardedBy("sCreateLock")
+    private VirtualMachine getLocked(@NonNull String name) throws VirtualMachineException {
+        VirtualMachine vm = getVmByName(name);
+        if (vm != null) return vm;
+
+        vm = VirtualMachine.load(mContext, name);
+        if (vm != null) {
+            mVmsByName.put(name, new WeakReference<>(vm));
+        }
+        return vm;
+    }
+
     /**
      * Imports a virtual machine from an {@link VirtualMachineDescriptor} object and associates it
      * with the given name.
@@ -151,26 +204,14 @@
      * @hide
      */
     @NonNull
+    @SystemApi
     public VirtualMachine importFromDescriptor(
             @NonNull String name, @NonNull VirtualMachineDescriptor vmDescriptor)
             throws VirtualMachineException {
-        synchronized (VirtualMachine.sCreateLock) {
-            return VirtualMachine.fromDescriptor(mContext, name, vmDescriptor);
-        }
-    }
-
-    /**
-     * Returns an existing {@link VirtualMachine} with the given name. Returns null if there is no
-     * such virtual machine.
-     *
-     * @throws VirtualMachineException if the virtual machine exists but could not be successfully
-     *                                 retrieved.
-     * @hide
-     */
-    @Nullable
-    public VirtualMachine get(@NonNull String name) throws VirtualMachineException {
-        synchronized (VirtualMachine.sCreateLock) {
-            return VirtualMachine.load(mContext, name);
+        synchronized (sCreateLock) {
+            VirtualMachine vm = VirtualMachine.fromDescriptor(mContext, name, vmDescriptor);
+            mVmsByName.put(name, new WeakReference<>(vm));
+            return vm;
         }
     }
 
@@ -181,18 +222,18 @@
      * @throws VirtualMachineException if the virtual machine could not be created or retrieved.
      * @hide
      */
+    @SystemApi
     @NonNull
-    public VirtualMachine getOrCreate(
-            @NonNull String name, @NonNull VirtualMachineConfig config)
+    public VirtualMachine getOrCreate(@NonNull String name, @NonNull VirtualMachineConfig config)
             throws VirtualMachineException {
-        VirtualMachine vm;
-        synchronized (VirtualMachine.sCreateLock) {
-            vm = get(name);
-            if (vm == null) {
-                vm = create(name, config);
+        synchronized (sCreateLock) {
+            VirtualMachine vm = getLocked(name);
+            if (vm != null) {
+                return vm;
+            } else {
+                return createLocked(name, config);
             }
         }
-        return vm;
     }
 
     /**
@@ -202,14 +243,34 @@
      * with the same name is different from an already deleted virtual machine even if it has the
      * same config.
      *
-     * @throws VirtualMachineException if the virtual machine does not exist, is not stopped,
-     *                                 or cannot be deleted.
+     * @throws VirtualMachineException if the virtual machine does not exist, is not stopped, or
+     *     cannot be deleted.
      * @hide
      */
+    @SystemApi
     public void delete(@NonNull String name) throws VirtualMachineException {
-        requireNonNull(name);
-        synchronized (VirtualMachine.sCreateLock) {
-            VirtualMachine.delete(mContext, name);
+        synchronized (sCreateLock) {
+            VirtualMachine vm = getVmByName(name);
+            if (vm == null) {
+                VirtualMachine.deleteVmDirectory(mContext, name);
+            } else {
+                vm.delete(mContext, name);
+            }
+            mVmsByName.remove(name);
         }
     }
+
+    @Nullable
+    @GuardedBy("sCreateLock")
+    private VirtualMachine getVmByName(@NonNull String name) {
+        requireNonNull(name);
+        WeakReference<VirtualMachine> weakReference = mVmsByName.get(name);
+        if (weakReference != null) {
+            VirtualMachine vm = weakReference.get();
+            if (vm != null && vm.getStatus() != VirtualMachine.STATUS_DELETED) {
+                return vm;
+            }
+        }
+        return null;
+    }
 }
diff --git a/microdroid/Android.bp b/microdroid/Android.bp
index c3e2692..af6031a 100644
--- a/microdroid/Android.bp
+++ b/microdroid/Android.bp
@@ -72,7 +72,6 @@
         "debuggerd",
         "linker",
         "linkerconfig",
-        "servicemanager.microdroid",
         "tombstoned.microdroid",
         "tombstone_transmit.microdroid",
         "cgroups.json",
@@ -84,7 +83,7 @@
         "microdroid_manifest",
         "microdroid_plat_sepolicy_and_mapping.sha256",
         "microdroid_property_contexts",
-        "microdroid_service_contexts",
+        "mke2fs",
 
         // TODO(b/195425111) these should be added automatically
         "libcrypto", // used by many (init_second_stage, microdroid_manager, toybox, etc)
@@ -108,6 +107,7 @@
                 "apkdmverity",
                 "authfs",
                 "authfs_service",
+                "encryptedstore",
                 "microdroid_crashdump_kernel",
                 "microdroid_kexec",
                 "microdroid_manager",
diff --git a/microdroid/init.rc b/microdroid/init.rc
index 9c62782..94ef940 100644
--- a/microdroid/init.rc
+++ b/microdroid/init.rc
@@ -26,20 +26,6 @@
     setprop ro.log.file_logger.path /dev/null
 
 on init
-    # Mount binderfs
-    mkdir /dev/binderfs
-    mount binder binder /dev/binderfs stats=global
-    chmod 0755 /dev/binderfs
-
-    symlink /dev/binderfs/binder /dev/binder
-    symlink /dev/binderfs/vndbinder /dev/vndbinder
-
-    chmod 0666 /dev/binderfs/binder
-    chmod 0666 /dev/binderfs/vndbinder
-
-    start servicemanager
-
-on init
     mkdir /mnt/apk 0755 system system
     mkdir /mnt/extra-apk 0755 root root
     # Microdroid_manager starts apkdmverity/zipfuse/apexd
diff --git a/microdroid/vm_payload/src/vm_payload_service.rs b/microdroid/vm_payload/src/vm_payload_service.rs
deleted file mode 100644
index e89f730..0000000
--- a/microdroid/vm_payload/src/vm_payload_service.rs
+++ /dev/null
@@ -1,241 +0,0 @@
-// Copyright 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.
-
-//! This module handles the interaction with virtual machine payload service.
-
-use android_system_virtualization_payload::aidl::android::system::virtualization::payload::IVmPayloadService::{
-    IVmPayloadService, VM_PAYLOAD_SERVICE_SOCKET_NAME, VM_APK_CONTENTS_PATH};
-use anyhow::{Context, Result};
-use binder::{Strong, unstable_api::{AIBinder, new_spibinder}};
-use lazy_static::lazy_static;
-use log::{error, info, Level};
-use rpcbinder::{get_unix_domain_rpc_interface, run_vsock_rpc_server};
-use std::io;
-use std::ffi::CString;
-use std::fs::File;
-use std::os::raw::{c_char, c_void};
-use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd};
-
-lazy_static! {
-    static ref VM_APK_CONTENTS_PATH_C: CString =
-        CString::new(VM_APK_CONTENTS_PATH).expect("CString::new failed");
-}
-
-/// Notifies the host that the payload is ready.
-/// Returns true if the notification succeeds else false.
-#[no_mangle]
-pub extern "C" fn AVmPayload_notifyPayloadReady() -> bool {
-    android_logger::init_once(
-        android_logger::Config::default().with_tag("vm_payload").with_min_level(Level::Debug),
-    );
-    if let Err(e) = try_notify_payload_ready() {
-        error!("{:?}", e);
-        false
-    } else {
-        info!("Notified host payload ready successfully");
-        true
-    }
-}
-
-/// Notifies the host that the payload is ready.
-/// Returns a `Result` containing error information if failed.
-fn try_notify_payload_ready() -> Result<()> {
-    get_vm_payload_service()?.notifyPayloadReady().context("Cannot notify payload ready")
-}
-
-/// Runs a binder RPC server, serving the supplied binder service implementation on the given vsock
-/// port.
-///
-/// If and when the server is ready for connections (it is listening on the port), `on_ready` is
-/// called to allow appropriate action to be taken - e.g. to notify clients that they may now
-/// attempt to connect.
-///
-/// The current thread is joined to the binder thread pool to handle incoming messages.
-///
-/// Returns true if the server has shutdown normally, false if it failed in some way.
-///
-/// # Safety
-///
-/// The `on_ready` callback is only called inside `run_vsock_rpc_server`, within the lifetime of
-/// `ReadyNotifier` (the last parameter of `run_vsock_rpc_server`). If `on_ready` is called with
-/// wrong param, the callback execution could go wrong.
-#[no_mangle]
-pub unsafe extern "C" fn AVmPayload_runVsockRpcServer(
-    service: *mut AIBinder,
-    port: u32,
-    on_ready: Option<unsafe extern "C" fn(param: *mut c_void)>,
-    param: *mut c_void,
-) -> bool {
-    // SAFETY: AIBinder returned has correct reference count, and the ownership can
-    // safely be taken by new_spibinder.
-    let service = new_spibinder(service);
-    if let Some(service) = service {
-        run_vsock_rpc_server(service, port, || {
-            if let Some(on_ready) = on_ready {
-                on_ready(param);
-            }
-        })
-    } else {
-        error!("Failed to convert the given service from AIBinder to SpIBinder.");
-        false
-    }
-}
-
-/// Get a secret that is uniquely bound to this VM instance.
-///
-/// # Safety
-///
-/// Behavior is undefined if any of the following conditions are violated:
-///
-/// * `identifier` must be [valid] for reads of `identifier_size` bytes.
-/// * `secret` must be [valid] for writes of `size` bytes.
-///
-/// [valid]: std::ptr#safety
-#[no_mangle]
-pub unsafe extern "C" fn AVmPayload_getVmInstanceSecret(
-    identifier: *const u8,
-    identifier_size: usize,
-    secret: *mut u8,
-    size: usize,
-) -> bool {
-    let identifier = std::slice::from_raw_parts(identifier, identifier_size);
-    match try_get_vm_instance_secret(identifier, size) {
-        Err(e) => {
-            error!("{:?}", e);
-            false
-        }
-        Ok(vm_secret) => {
-            if vm_secret.len() != size {
-                return false;
-            }
-            std::ptr::copy_nonoverlapping(vm_secret.as_ptr(), secret, size);
-            true
-        }
-    }
-}
-
-fn try_get_vm_instance_secret(identifier: &[u8], size: usize) -> Result<Vec<u8>> {
-    get_vm_payload_service()?
-        .getVmInstanceSecret(identifier, i32::try_from(size)?)
-        .context("Cannot get VM instance secret")
-}
-
-/// Get the VM's attestation chain.
-/// Returns true on success, else false.
-///
-/// # Safety
-///
-/// Behavior is undefined if any of the following conditions are violated:
-///
-/// * `data` must be [valid] for writes of `size` bytes.
-/// * `total` must be [valid] for writes.
-///
-/// [valid]: std::ptr#safety
-#[no_mangle]
-pub unsafe extern "C" fn AVmPayload_getDiceAttestationChain(
-    data: *mut u8,
-    size: usize,
-    total: *mut usize,
-) -> bool {
-    match try_get_dice_attestation_chain() {
-        Err(e) => {
-            error!("{:?}", e);
-            false
-        }
-        Ok(chain) => {
-            total.write(chain.len());
-            std::ptr::copy_nonoverlapping(chain.as_ptr(), data, std::cmp::min(chain.len(), size));
-            true
-        }
-    }
-}
-
-fn try_get_dice_attestation_chain() -> Result<Vec<u8>> {
-    get_vm_payload_service()?.getDiceAttestationChain().context("Cannot get attestation chain")
-}
-
-/// Get the VM's attestation CDI.
-/// Returns true on success, else false.
-///
-/// # Safety
-///
-/// Behavior is undefined if any of the following conditions are violated:
-///
-/// * `data` must be [valid] for writes of `size` bytes.
-/// * `total` must be [valid] for writes.
-///
-/// [valid]: std::ptr#safety
-#[no_mangle]
-pub unsafe extern "C" fn AVmPayload_getDiceAttestationCdi(
-    data: *mut u8,
-    size: usize,
-    total: *mut usize,
-) -> bool {
-    match try_get_dice_attestation_cdi() {
-        Err(e) => {
-            error!("{:?}", e);
-            false
-        }
-        Ok(cdi) => {
-            total.write(cdi.len());
-            std::ptr::copy_nonoverlapping(cdi.as_ptr(), data, std::cmp::min(cdi.len(), size));
-            true
-        }
-    }
-}
-
-/// Gets the path to the APK contents.
-#[no_mangle]
-pub extern "C" fn AVmPayload_getApkContentsPath() -> *const c_char {
-    (*VM_APK_CONTENTS_PATH_C).as_ptr()
-}
-
-fn try_get_dice_attestation_cdi() -> Result<Vec<u8>> {
-    get_vm_payload_service()?.getDiceAttestationCdi().context("Cannot get attestation CDI")
-}
-
-/// Creates a socket connection with the host and duplicates standard I/O
-/// file descriptors of the payload to that socket. Then notifies the host.
-#[no_mangle]
-pub extern "C" fn AVmPayload_setupStdioProxy() -> bool {
-    if let Err(e) = try_setup_stdio_proxy() {
-        error!("{:?}", e);
-        false
-    } else {
-        info!("Successfully set up stdio proxy to the host");
-        true
-    }
-}
-
-fn dup2(old_fd: &File, new_fd: BorrowedFd) -> Result<(), io::Error> {
-    // SAFETY - ownership does not change, only modifies the underlying raw FDs.
-    match unsafe { libc::dup2(old_fd.as_raw_fd(), new_fd.as_raw_fd()) } {
-        -1 => Err(io::Error::last_os_error()),
-        _ => Ok(()),
-    }
-}
-
-fn try_setup_stdio_proxy() -> Result<()> {
-    let fd =
-        get_vm_payload_service()?.setupStdioProxy().context("Could not connect a host socket")?;
-    dup2(fd.as_ref(), io::stdin().as_fd()).context("Failed to dup stdin")?;
-    dup2(fd.as_ref(), io::stdout().as_fd()).context("Failed to dup stdout")?;
-    dup2(fd.as_ref(), io::stderr().as_fd()).context("Failed to dup stderr")?;
-    Ok(())
-}
-
-fn get_vm_payload_service() -> Result<Strong<dyn IVmPayloadService>> {
-    get_unix_domain_rpc_interface(VM_PAYLOAD_SERVICE_SOCKET_NAME)
-        .context(format!("Failed to connect to service: {}", VM_PAYLOAD_SERVICE_SOCKET_NAME))
-}
diff --git a/microdroid_manager/Android.bp b/microdroid_manager/Android.bp
index 4b06b3e..44b4c01 100644
--- a/microdroid_manager/Android.bp
+++ b/microdroid_manager/Android.bp
@@ -23,6 +23,7 @@
         "libdiced_sample_inputs",
         "libdiced_utils",
         "libglob",
+        "libhex",
         "libitertools",
         "libkernlog",
         "libkeystore2_crypto_rust",
diff --git a/microdroid_manager/aidl/android/system/virtualization/payload/IVmPayloadService.aidl b/microdroid_manager/aidl/android/system/virtualization/payload/IVmPayloadService.aidl
index 1141965..f8e7d34 100644
--- a/microdroid_manager/aidl/android/system/virtualization/payload/IVmPayloadService.aidl
+++ b/microdroid_manager/aidl/android/system/virtualization/payload/IVmPayloadService.aidl
@@ -16,8 +16,6 @@
 
 package android.system.virtualization.payload;
 
-import android.os.ParcelFileDescriptor;
-
 /**
  * This interface regroups the tasks that payloads delegate to
  * Microdroid Manager for execution.
@@ -63,16 +61,4 @@
      * @throws SecurityException if the use of test APIs is not permitted.
      */
     byte[] getDiceAttestationCdi();
-
-    /**
-     * Sets up a standard I/O proxy to the host.
-     *
-     * Creates a socket with the host and notifies its listeners that the stdio
-     * proxy is ready.
-     *
-     * Temporarily uses a random free port allocated by the OS.
-     * @return a file descriptor that the payload should dup() its standard I/O
-     * file descriptors to.
-     */
-    ParcelFileDescriptor setupStdioProxy();
 }
diff --git a/microdroid_manager/src/dice.rs b/microdroid_manager/src/dice.rs
index 3881db3..499835f 100644
--- a/microdroid_manager/src/dice.rs
+++ b/microdroid_manager/src/dice.rs
@@ -38,6 +38,16 @@
     pub bcc: Vec<u8>,
 }
 
+impl DiceContext {
+    pub fn get_sealing_key(&self, salt: &[u8], identifier: &[u8], keysize: u32) -> Result<ZVec> {
+        // Deterministically derive a key to use for sealing data based on salt. Use different salt
+        // for different keys.
+        let mut key = ZVec::new(keysize as usize)?;
+        hkdf(&mut key, Md::sha256(), &self.cdi_seal, salt, identifier)?;
+        Ok(key)
+    }
+}
+
 /// Artifacts that are mapped into the process address space from the driver.
 pub enum DiceDriver<'a> {
     Real {
diff --git a/microdroid_manager/src/main.rs b/microdroid_manager/src/main.rs
index 0ac4167..a706dbe 100644
--- a/microdroid_manager/src/main.rs
+++ b/microdroid_manager/src/main.rs
@@ -25,9 +25,7 @@
 use crate::instance::{ApexData, ApkData, InstanceDisk, MicrodroidData, RootHash};
 use crate::vm_payload_service::register_vm_payload_service;
 use android_system_virtualizationcommon::aidl::android::system::virtualizationcommon::ErrorCode::ErrorCode;
-use android_system_virtualmachineservice::aidl::android::system::virtualmachineservice::IVirtualMachineService::{
-        IVirtualMachineService, VM_BINDER_SERVICE_PORT,
-};
+use android_system_virtualmachineservice::aidl::android::system::virtualmachineservice::IVirtualMachineService::IVirtualMachineService;
 use android_system_virtualization_payload::aidl::android::system::virtualization::payload::IVmPayloadService::{
     VM_APK_CONTENTS_PATH,
     VM_PAYLOAD_SERVICE_SOCKET_NAME,
@@ -83,6 +81,13 @@
 // SYNC WITH virtualizationservice/src/crosvm.rs
 const FAILURE_SERIAL_DEVICE: &str = "/dev/ttyS1";
 
+/// Identifier for the key used for encrypted store.
+const ENCRYPTEDSTORE_BACKING_DEVICE: &str = "/dev/block/by-name/encryptedstore";
+const ENCRYPTEDSTORE_BIN: &str = "/system/bin/encryptedstore";
+const ENCRYPTEDSTORE_KEY_IDENTIFIER: &str = "encryptedstore_key";
+const ENCRYPTEDSTORE_KEYSIZE: u32 = 64;
+const ENCRYPTEDSTORE_MOUNTPOINT: &str = "/mnt/encryptedstore";
+
 #[derive(thiserror::Error, Debug)]
 enum MicrodroidError {
     #[error("Cannot connect to virtualization service: {0}")]
@@ -153,8 +158,11 @@
 }
 
 fn get_vms_rpc_binder() -> Result<Strong<dyn IVirtualMachineService>> {
-    get_vsock_rpc_interface(VMADDR_CID_HOST, VM_BINDER_SERVICE_PORT as u32)
-        .context("Cannot connect to RPC service")
+    // The host is running a VirtualMachineService for this VM on a port equal
+    // to the CID of this VM.
+    let port = vsock::get_local_cid().context("Could not determine local CID")?;
+    get_vsock_rpc_interface(VMADDR_CID_HOST, port)
+        .context("Could not connect to IVirtualMachineService")
 }
 
 fn main() -> Result<()> {
@@ -365,7 +373,15 @@
 
     // To minimize the exposure to untrusted data, derive dice profile as soon as possible.
     info!("DICE derivation for payload");
-    let dice = dice_derivation(dice, &verified_data, &payload_metadata)?;
+    let dice_context = dice_derivation(dice, &verified_data, &payload_metadata)?;
+
+    // Run encryptedstore binary to prepare the storage
+    let encryptedstore_child = if Path::new(ENCRYPTEDSTORE_BACKING_DEVICE).exists() {
+        info!("Preparing encryptedstore ...");
+        Some(prepare_encryptedstore(&dice_context).context("encryptedstore run")?)
+    } else {
+        None
+    };
 
     // Before reading a file from the APK, start zipfuse
     run_zipfuse(
@@ -419,7 +435,12 @@
     // Wait until zipfuse has mounted the APK so we can access the payload
     wait_for_property_true(APK_MOUNT_DONE_PROP).context("Failed waiting for APK mount done")?;
 
-    register_vm_payload_service(allow_restricted_apis, service.clone(), dice)?;
+    register_vm_payload_service(allow_restricted_apis, service.clone(), dice_context)?;
+
+    if let Some(mut child) = encryptedstore_child {
+        let exitcode = child.wait().context("Wait for encryptedstore child")?;
+        ensure!(exitcode.success(), "Unable to prepare encrypted storage. Exitcode={}", exitcode);
+    }
 
     system_properties::write("dev.bootcomplete", "1").context("set dev.bootcomplete")?;
     exec_task(task, service).context("Failed to run payload")
@@ -440,8 +461,6 @@
 fn run_apkdmverity(args: &[ApkDmverityArgument]) -> Result<Child> {
     let mut cmd = Command::new(APKDMVERITY_BIN);
 
-    cmd.stdin(Stdio::null()).stdout(Stdio::null()).stderr(Stdio::null());
-
     for argument in args {
         cmd.arg("--apk").arg(argument.apk).arg(argument.idsig).arg(argument.name);
         if let Some(root_hash) = argument.saved_root_hash {
@@ -473,15 +492,7 @@
     if let Some(property_name) = ready_prop {
         cmd.args(["-p", property_name]);
     }
-    cmd.arg("-o")
-        .arg(option)
-        .arg(zip_path)
-        .arg(mount_dir)
-        .stdin(Stdio::null())
-        .stdout(Stdio::null())
-        .stderr(Stdio::null())
-        .spawn()
-        .context("Spawn zipfuse")
+    cmd.arg("-o").arg(option).arg(zip_path).arg(mount_dir).spawn().context("Spawn zipfuse")
 }
 
 fn write_apex_payload_data(
@@ -790,3 +801,28 @@
 fn to_hex_string(buf: &[u8]) -> String {
     buf.iter().map(|b| format!("{:02X}", b)).collect()
 }
+
+fn prepare_encryptedstore(dice: &DiceContext) -> Result<Child> {
+    // Use a fixed salt to scope the derivation to this API.
+    // Generated using hexdump -vn32 -e'14/1 "0x%02X, " 1 "\n"' /dev/urandom
+    // TODO(b/241541860) : Move this (& other salts) to a salt container, i.e. a global enum
+    let salt = [
+        0xFC, 0x1D, 0x35, 0x7B, 0x96, 0xF3, 0xEF, 0x17, 0x78, 0x7D, 0x70, 0xED, 0xEA, 0xFE, 0x1D,
+        0x6F, 0xB3, 0xF9, 0x40, 0xCE, 0xDD, 0x99, 0x40, 0xAA, 0xA7, 0x0E, 0x92, 0x73, 0x90, 0x86,
+        0x4A, 0x75,
+    ];
+    let key = dice.get_sealing_key(
+        &salt,
+        ENCRYPTEDSTORE_KEY_IDENTIFIER.as_bytes(),
+        ENCRYPTEDSTORE_KEYSIZE,
+    )?;
+
+    let mut cmd = Command::new(ENCRYPTEDSTORE_BIN);
+    cmd.arg("--blkdevice")
+        .arg(ENCRYPTEDSTORE_BACKING_DEVICE)
+        .arg("--key")
+        .arg(hex::encode(&*key))
+        .args(["--mountpoint", ENCRYPTEDSTORE_MOUNTPOINT])
+        .spawn()
+        .context("encryptedstore failed")
+}
diff --git a/microdroid_manager/src/vm_payload_service.rs b/microdroid_manager/src/vm_payload_service.rs
index 126a8a9..98b9f2b 100644
--- a/microdroid_manager/src/vm_payload_service.rs
+++ b/microdroid_manager/src/vm_payload_service.rs
@@ -18,18 +18,12 @@
 use android_system_virtualization_payload::aidl::android::system::virtualization::payload::IVmPayloadService::{
     BnVmPayloadService, IVmPayloadService, VM_PAYLOAD_SERVICE_SOCKET_NAME};
 use android_system_virtualmachineservice::aidl::android::system::virtualmachineservice::IVirtualMachineService::IVirtualMachineService;
-use anyhow::{bail, Context, Result};
-use binder::{Interface, BinderFeatures, ExceptionCode, ParcelFileDescriptor, Status, Strong};
+use anyhow::Result;
+use binder::{Interface, BinderFeatures, ExceptionCode, Status, Strong};
 use log::{error, info};
 use openssl::hkdf::hkdf;
 use openssl::md::Md;
-use rpcbinder::run_init_unix_domain_rpc_server;
-use std::fs::File;
-use std::sync::mpsc;
-use std::thread;
-use std::time::Duration;
-use std::os::unix::io::{FromRawFd, IntoRawFd};
-use vsock::VsockListener;
+use rpcbinder::RpcServer;
 
 /// Implementation of `IVmPayloadService`.
 struct VmPayloadService {
@@ -70,16 +64,6 @@
         self.check_restricted_apis_allowed()?;
         Ok(self.dice.cdi_attest.to_vec())
     }
-
-    fn setupStdioProxy(&self) -> binder::Result<ParcelFileDescriptor> {
-        let f = self.setup_payload_stdio_proxy().map_err(|e| {
-            Status::new_service_specific_error_str(
-                -1,
-                Some(format!("Failed to create stdio proxy: {:?}", e)),
-            )
-        })?;
-        Ok(ParcelFileDescriptor::new(f))
-    }
 }
 
 impl Interface for VmPayloadService {}
@@ -102,22 +86,6 @@
             Err(Status::new_exception_str(ExceptionCode::SECURITY, Some("Use of restricted APIs")))
         }
     }
-
-    fn setup_payload_stdio_proxy(&self) -> Result<File> {
-        // Instead of a predefined port in the host, we open up a port in the guest and have
-        // the host connect to it. This makes it possible to have per-app instances of VS.
-        const ANY_PORT: u32 = u32::MAX; // (u32)-1
-        let listener = VsockListener::bind_with_cid_port(libc::VMADDR_CID_ANY, ANY_PORT)
-            .context("Failed to create vsock listener")?;
-        let addr = listener.local_addr().context("Failed to resolve listener port")?;
-        self.virtual_machine_service
-            .connectPayloadStdioProxy(addr.port() as i32)
-            .context("Failed to connect to the host")?;
-        let (stream, _) =
-            listener.accept().context("Failed to accept vsock connection from the host")?;
-        // SAFETY: ownership is transferred from stream to the new File
-        Ok(unsafe { File::from_raw_fd(stream.into_raw_fd()) })
-    }
 }
 
 /// Registers the `IVmPayloadService` service.
@@ -130,29 +98,16 @@
         VmPayloadService::new(allow_restricted_apis, vm_service, dice),
         BinderFeatures::default(),
     );
-    let (sender, receiver) = mpsc::channel();
-    thread::spawn(move || {
-        let retval = run_init_unix_domain_rpc_server(
-            vm_payload_binder.as_binder(),
-            VM_PAYLOAD_SERVICE_SOCKET_NAME,
-            || {
-                sender.send(()).unwrap();
-            },
-        );
-        if retval {
-            info!(
-                "The RPC server at '{}' has shut down gracefully.",
-                VM_PAYLOAD_SERVICE_SOCKET_NAME
-            );
-        } else {
-            error!("Premature termination of the RPC server '{}'.", VM_PAYLOAD_SERVICE_SOCKET_NAME);
-        }
+
+    let server = RpcServer::new_init_unix_domain(
+        vm_payload_binder.as_binder(),
+        VM_PAYLOAD_SERVICE_SOCKET_NAME,
+    )?;
+    info!("The RPC server '{}' is running.", VM_PAYLOAD_SERVICE_SOCKET_NAME);
+
+    // Move server reference into a background thread and run it forever.
+    std::thread::spawn(move || {
+        server.join();
     });
-    match receiver.recv_timeout(Duration::from_millis(200)) {
-        Ok(()) => {
-            info!("The RPC server '{}' is running.", VM_PAYLOAD_SERVICE_SOCKET_NAME);
-            Ok(())
-        }
-        _ => bail!("Failed to register service '{}'", VM_PAYLOAD_SERVICE_SOCKET_NAME),
-    }
+    Ok(())
 }
diff --git a/tests/aidl/com/android/microdroid/testservice/ITestService.aidl b/tests/aidl/com/android/microdroid/testservice/ITestService.aidl
index e8c435f..eda4f75 100644
--- a/tests/aidl/com/android/microdroid/testservice/ITestService.aidl
+++ b/tests/aidl/com/android/microdroid/testservice/ITestService.aidl
@@ -36,4 +36,7 @@
 
     /* get the APK contents path. */
     String getApkContentsPath();
+
+    /* get the encrypted storage path. */
+    String getEncryptedStoragePath();
 }
diff --git a/tests/benchmark/Android.bp b/tests/benchmark/Android.bp
index bccea6b..10cdac5 100644
--- a/tests/benchmark/Android.bp
+++ b/tests/benchmark/Android.bp
@@ -16,13 +16,13 @@
         "com.android.microdroid.testservice-java",
         "truth-prebuilt",
     ],
-    libs: ["framework-virtualization"],
     jni_libs: [
         "MicrodroidBenchmarkNativeLib",
         "MicrodroidIdleNativeLib",
         "libiovsock_host_jni",
     ],
-    platform_apis: true,
+    jni_uses_platform_apis: true,
+    sdk_version: "test_current",
     use_embedded_native_libs: true,
     compile_multilib: "64",
 }
diff --git a/tests/benchmark/src/native/benchmarkbinary.cpp b/tests/benchmark/src/native/benchmarkbinary.cpp
index e43025c..66b41a1 100644
--- a/tests/benchmark/src/native/benchmarkbinary.cpp
+++ b/tests/benchmark/src/native/benchmarkbinary.cpp
@@ -160,12 +160,7 @@
 
 Result<void> run_io_benchmark_tests() {
     auto test_service = ndk::SharedRefBase::make<IOBenchmarkService>();
-    auto callback = []([[maybe_unused]] void* param) {
-        if (!AVmPayload_notifyPayloadReady()) {
-            LOG(ERROR) << "failed to notify payload ready to virtualizationservice";
-            abort();
-        }
-    };
+    auto callback = []([[maybe_unused]] void* param) { AVmPayload_notifyPayloadReady(); };
     if (!AVmPayload_runVsockRpcServer(test_service->asBinder().get(), test_service->SERVICE_PORT,
                                       callback, nullptr)) {
         return Error() << "RPC Server failed to run";
diff --git a/tests/benchmark_hostside/java/android/avf/test/AVFHostTestCase.java b/tests/benchmark_hostside/java/android/avf/test/AVFHostTestCase.java
index 1996f4b..c47e915 100644
--- a/tests/benchmark_hostside/java/android/avf/test/AVFHostTestCase.java
+++ b/tests/benchmark_hostside/java/android/avf/test/AVFHostTestCase.java
@@ -149,7 +149,9 @@
     }
 
     private void appStartupHelper(String launchIntentPackage) throws Exception {
-        assumeTrue("Skip on non-protected VMs", isProtectedVmSupported());
+        assumeTrue(
+                "Skip on non-protected VMs",
+                ((TestDevice) getDevice()).supportsMicrodroid(/*protectedVm=*/ true));
 
         StartupTimeMetricCollection mCollection =
                 new StartupTimeMetricCollection(getPackageName(launchIntentPackage), ROUND_COUNT);
diff --git a/tests/helper/Android.bp b/tests/helper/Android.bp
index bd92020..61c5dcd 100644
--- a/tests/helper/Android.bp
+++ b/tests/helper/Android.bp
@@ -3,15 +3,10 @@
 }
 
 java_library_static {
-    name: "VirtualizationTestHelper",
-    srcs: ["src/java/com/android/virt/**/*.java"],
-    host_supported: true,
-}
-
-java_library_static {
     name: "MicrodroidTestHelper",
     srcs: ["src/java/com/android/microdroid/test/common/*.java"],
     host_supported: true,
+    sdk_version: "system_current",
 }
 
 java_library_static {
@@ -21,8 +16,7 @@
         "androidx.test.runner",
         "androidx.test.ext.junit",
         "MicrodroidTestHelper",
-        "VirtualizationTestHelper",
         "truth-prebuilt",
     ],
-    libs: ["framework-virtualization"],
+    sdk_version: "system_current",
 }
diff --git a/tests/helper/src/java/com/android/microdroid/test/common/DeviceProperties.java b/tests/helper/src/java/com/android/microdroid/test/common/DeviceProperties.java
new file mode 100644
index 0000000..94f7e99
--- /dev/null
+++ b/tests/helper/src/java/com/android/microdroid/test/common/DeviceProperties.java
@@ -0,0 +1,63 @@
+/*
+ * 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 static java.util.Objects.requireNonNull;
+
+/** This class can be used in both host tests and device tests to get the device properties. */
+public final class DeviceProperties {
+    /** PropertyGetter is used to get the property associated to a given key. */
+    public interface PropertyGetter {
+        String getProperty(String key) throws Exception;
+    }
+
+    private static final String KEY_VENDOR_DEVICE = "ro.product.vendor.device";
+    private static final String KEY_METRICS_TAG = "debug.hypervisor.metrics_tag";
+
+    private static final String CUTTLEFISH_DEVICE_PREFIX = "vsoc_";
+
+    private final PropertyGetter mPropertyGetter;
+
+    private DeviceProperties(PropertyGetter propertyGetter) {
+        mPropertyGetter = requireNonNull(propertyGetter);
+    }
+
+    /** Creates a new instance of {@link DeviceProperties}. */
+    public static DeviceProperties create(PropertyGetter propertyGetter) {
+        return new DeviceProperties(propertyGetter);
+    }
+
+    /**
+     * @return whether the device is a cuttlefish device.
+     */
+    public boolean isCuttlefish() {
+        String vendorDeviceName = getProperty(KEY_VENDOR_DEVICE);
+        return vendorDeviceName != null && vendorDeviceName.startsWith(CUTTLEFISH_DEVICE_PREFIX);
+    }
+
+    public String getMetricsTag() {
+        return getProperty(KEY_METRICS_TAG);
+    }
+
+    private String getProperty(String key) {
+        try {
+            return mPropertyGetter.getProperty(key);
+        } catch (Exception e) {
+            throw new IllegalArgumentException("Cannot get property for the key: " + key, e);
+        }
+    }
+}
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 d1e1f6c..24e2049 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
@@ -35,8 +35,8 @@
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.platform.app.InstrumentationRegistry;
 
+import com.android.microdroid.test.common.DeviceProperties;
 import com.android.microdroid.test.common.MetricsProcessor;
-import com.android.virt.VirtualizationTestHelper;
 
 import java.io.BufferedReader;
 import java.io.ByteArrayOutputStream;
@@ -51,13 +51,12 @@
 
 public abstract class MicrodroidDeviceTestBase {
     public static boolean isCuttlefish() {
-        return VirtualizationTestHelper.isCuttlefish(
-                SystemProperties.get("ro.product.vendor.device"));
+        return DeviceProperties.create(SystemProperties::get).isCuttlefish();
     }
 
     public static String getMetricPrefix() {
         return MetricsProcessor.getMetricPrefix(
-                SystemProperties.get("debug.hypervisor.metrics_tag"));
+                DeviceProperties.create(SystemProperties::get).getMetricsTag());
     }
 
     protected final void grantPermission(String permission) {
@@ -230,9 +229,6 @@
         public void onPayloadStarted(VirtualMachine vm) {}
 
         @Override
-        public void onPayloadStdio(VirtualMachine vm, ParcelFileDescriptor stream) {}
-
-        @Override
         public void onPayloadReady(VirtualMachine vm) {}
 
         @Override
diff --git a/tests/hostside/helper/Android.bp b/tests/hostside/helper/Android.bp
index b2333ab..6196ec5 100644
--- a/tests/hostside/helper/Android.bp
+++ b/tests/hostside/helper/Android.bp
@@ -12,6 +12,5 @@
     ],
     static_libs: [
         "MicrodroidTestHelper",
-        "VirtualizationTestHelper",
     ],
 }
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 9bcd1d3..e5aa908 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
@@ -26,6 +26,7 @@
 import static org.junit.Assume.assumeTrue;
 
 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.microdroid.test.common.DeviceProperties;
 import com.android.microdroid.test.common.MetricsProcessor;
 import com.android.tradefed.build.IBuildInfo;
 import com.android.tradefed.device.DeviceNotAvailableException;
@@ -34,7 +35,6 @@
 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
 import com.android.tradefed.util.CommandResult;
 import com.android.tradefed.util.RunUtil;
-import com.android.virt.VirtualizationTestHelper;
 
 import java.io.File;
 import java.io.FileNotFoundException;
@@ -42,17 +42,12 @@
 
 public abstract class MicrodroidHostTestCaseBase extends BaseHostJUnit4Test {
     protected static final String TEST_ROOT = "/data/local/tmp/virt/";
-    protected static final String VIRT_APEX = "/apex/com.android.virt/";
     protected static final String LOG_PATH = TEST_ROOT + "log.txt";
     protected static final String CONSOLE_PATH = TEST_ROOT + "console.txt";
     private static final int TEST_VM_ADB_PORT = 8000;
     private static final String MICRODROID_SERIAL = "localhost:" + TEST_VM_ADB_PORT;
     private static final String INSTANCE_IMG = "instance.img";
 
-    // This is really slow on GCE (2m 40s) but fast on localhost or actual Android phones (< 10s).
-    // Then there is time to run the actual task. Set the maximum timeout value big enough.
-    private static final long MICRODROID_MAX_LIFETIME_MINUTES = 20;
-
     private static final long MICRODROID_ADB_CONNECT_TIMEOUT_MINUTES = 5;
     protected static final long MICRODROID_COMMAND_TIMEOUT_MILLIS = 30000;
     private static final long MICRODROID_COMMAND_RETRY_INTERVAL_MILLIS = 500;
@@ -87,14 +82,13 @@
         android.tryRun("rm", "-rf", "/data/misc/virtualizationservice/*");
     }
 
-    protected boolean isCuttlefish() throws Exception {
-        return VirtualizationTestHelper.isCuttlefish(
-            getDevice().getProperty("ro.product.vendor.device"));
+    protected boolean isCuttlefish() {
+        return DeviceProperties.create(getDevice()::getProperty).isCuttlefish();
     }
 
-    protected String getMetricPrefix() throws Exception {
+    protected String getMetricPrefix() {
         return MetricsProcessor.getMetricPrefix(
-                getDevice().getProperty("debug.hypervisor.metrics_tag"));
+                DeviceProperties.create(getDevice()::getProperty).getMetricsTag());
     }
 
     public static void testIfDeviceIsCapable(ITestDevice androidDevice) throws Exception {
@@ -196,25 +190,6 @@
         return pathLine.substring("package:".length());
     }
 
-    private static void forwardFileToLog(CommandRunner android, String path, String tag)
-            throws DeviceNotAvailableException {
-        android.runWithTimeout(
-                MICRODROID_MAX_LIFETIME_MINUTES * 60 * 1000,
-                "logwrapper",
-                "sh",
-                "-c",
-                "\"$'tail -f -n +0 " + path
-                        + " | sed \\'s/^/" + tag + ": /g\\''\""); // add tags in front of lines
-    }
-
-    public static void shutdownMicrodroid(ITestDevice androidDevice, String cid)
-            throws DeviceNotAvailableException {
-        CommandRunner android = new CommandRunner(androidDevice);
-
-        // Shutdown the VM
-        android.run(VIRT_APEX + "bin/vm", "stop", cid);
-    }
-
     // Establish an adb connection to microdroid by letting Android forward the connection to
     // microdroid. Wait until the connection is established and microdroid is booted.
     public static void adbConnectToMicrodroid(ITestDevice androidDevice, String cid) {
@@ -262,8 +237,4 @@
                 .stdoutTrimmed()
                 .isEqualTo("microdroid");
     }
-
-    public boolean isProtectedVmSupported() throws DeviceNotAvailableException {
-        return getDevice().getBooleanProperty("ro.boot.hypervisor.protected_vm.supported", false);
-    }
 }
diff --git a/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java b/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
index b872a73..f0c89c9 100644
--- a/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
+++ b/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
@@ -42,6 +42,7 @@
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.device.TestDevice;
+import com.android.tradefed.log.LogUtil.CLog;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner.TestMetrics;
 import com.android.tradefed.util.CommandResult;
@@ -50,7 +51,6 @@
 import com.android.tradefed.util.xml.AbstractXmlParser;
 
 import org.json.JSONArray;
-import org.json.JSONException;
 import org.json.JSONObject;
 import org.junit.After;
 import org.junit.Before;
@@ -62,13 +62,19 @@
 import org.xml.sax.Attributes;
 import org.xml.sax.helpers.DefaultHandler;
 
+import java.io.BufferedReader;
 import java.io.ByteArrayInputStream;
 import java.io.File;
 import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.PipedInputStream;
+import java.io.PipedOutputStream;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.Callable;
+import java.util.concurrent.TimeUnit;
 import java.util.function.Function;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
@@ -78,6 +84,7 @@
     private static final String APK_NAME = "MicrodroidTestApp.apk";
     private static final String PACKAGE_NAME = "com.android.microdroid.test";
     private static final String SHELL_PACKAGE_NAME = "com.android.shell";
+    private static final String VIRT_APEX = "/apex/com.android.virt/";
 
     private static final int MIN_MEM_ARM64 = 145;
     private static final int MIN_MEM_X86_64 = 196;
@@ -266,11 +273,11 @@
         return new ActiveApexInfoList(list);
     }
 
-    private String runMicrodroidWithResignedImages(
+    private Process runMicrodroidWithResignedImages(
             File key,
             Map<String, File> keyOverrides,
             boolean isProtected,
-            boolean daemonize,
+            boolean waitForOnline,
             String consolePath)
             throws Exception {
         CommandRunner android = new CommandRunner(getDevice());
@@ -375,19 +382,49 @@
         final String configPath = TEST_ROOT + "raw_config.json";
         getDevice().pushString(config.toString(), configPath);
 
-        final String logPath = LOG_PATH;
-        final String ret =
-                android.runWithTimeout(
-                        60 * 1000,
+        List<String> args =
+                Arrays.asList(
+                        "adb",
+                        "-s",
+                        getDevice().getSerialNumber(),
+                        "shell",
                         VIRT_APEX + "bin/vm run",
-                        daemonize ? "--daemonize" : "",
                         (consolePath != null) ? "--console " + consolePath : "",
-                        "--log " + logPath,
+                        "--log " + LOG_PATH,
                         configPath);
+
+        PipedInputStream pis = new PipedInputStream();
+        Process process = RunUtil.getDefault().runCmdInBackground(args, new PipedOutputStream(pis));
+        BufferedReader stdout = new BufferedReader(new InputStreamReader(pis));
+
+        // Retrieve the CID from the vm tool output
+        String cid = null;
         Pattern pattern = Pattern.compile("with CID (\\d+)");
-        Matcher matcher = pattern.matcher(ret);
-        assertWithMessage("Failed to find CID").that(matcher.find()).isTrue();
-        return matcher.group(1);
+        try {
+            String line;
+            while ((line = stdout.readLine()) != null) {
+                CLog.i("VM output: " + line);
+                Matcher matcher = pattern.matcher(line);
+                if (matcher.find()) {
+                    cid = matcher.group(1);
+                    break;
+                }
+            }
+        } catch (IOException ex) {
+            throw new IllegalStateException(
+                    "Could not find the CID of the VM. The process probably died.", ex);
+        }
+        if (cid == null) {
+            throw new IllegalStateException(
+                    "Could not find the CID of the VM. Output does not contain the expected"
+                            + " pattern.");
+        }
+
+        if (waitForOnline) {
+            adbConnectToMicrodroid(getDevice(), cid);
+        }
+
+        return process;
     }
 
     @Test
@@ -395,14 +432,19 @@
     @CddTest(requirements = {"9.17/C-2-1", "9.17/C-2-2", "9.17/C-2-6"})
     public void testBootFailsWhenProtectedVmStartsWithImagesSignedWithDifferentKey()
             throws Exception {
-        assumeTrue("Protected VMs are not supported", isProtectedVmSupported());
+        assumeTrue(
+                "Protected VMs are not supported",
+                getAndroidDevice().supportsMicrodroid(/*protectedVm=*/ true));
 
         File key = findTestFile("test.com.android.virt.pem");
         Map<String, File> keyOverrides = Map.of();
         boolean isProtected = true;
-        boolean daemonize = false; // VM should shut down due to boot failure.
+        boolean waitForOnline = false; // VM should shut down due to boot failure.
         String consolePath = TEST_ROOT + "console";
-        runMicrodroidWithResignedImages(key, keyOverrides, isProtected, daemonize, consolePath);
+        Process process =
+                runMicrodroidWithResignedImages(
+                        key, keyOverrides, isProtected, waitForOnline, consolePath);
+        process.waitFor(5L, TimeUnit.SECONDS);
         assertThat(getDevice().pullFileContents(consolePath), containsString("pvmfw boot failed"));
     }
 
@@ -416,14 +458,12 @@
         File key = findTestFile("test.com.android.virt.pem");
         Map<String, File> keyOverrides = Map.of();
         boolean isProtected = false;
-        boolean daemonize = true;
+        boolean waitForOnline = true; // Device online means that boot must have succeeded.
         String consolePath = TEST_ROOT + "console";
-        String cid =
+        Process process =
                 runMicrodroidWithResignedImages(
-                        key, keyOverrides, isProtected, daemonize, consolePath);
-        // Adb connection to the microdroid means that boot succeeded.
-        adbConnectToMicrodroid(getDevice(), cid);
-        shutdownMicrodroid(getDevice(), cid);
+                        key, keyOverrides, isProtected, waitForOnline, consolePath);
+        process.destroy();
     }
 
     @Test
@@ -434,29 +474,29 @@
         File key2 = findTestFile("test2.com.android.virt.pem");
         Map<String, File> keyOverrides = Map.of("microdroid_vbmeta.img", key2);
         boolean isProtected = false; // Not interested in pvwfw
-        boolean daemonize = true; // Bootloader fails and enters prompts.
+        boolean waitForOnline = false; // Bootloader fails and enters prompts.
         // To be able to stop it, it should be a daemon.
         String consolePath = TEST_ROOT + "console";
-        String cid =
+        Process process =
                 runMicrodroidWithResignedImages(
-                        key, keyOverrides, isProtected, daemonize, consolePath);
+                        key, keyOverrides, isProtected, waitForOnline, consolePath);
         // Wait so that init can print errors to console (time in cuttlefish >> in real device)
         assertThatEventually(
                 100000,
                 () -> getDevice().pullFileContents(consolePath),
                 containsString("init: [libfs_avb]Failed to verify vbmeta digest"));
-        shutdownMicrodroid(getDevice(), cid);
+        process.destroy();
     }
 
     private boolean isTombstoneGeneratedWithConfig(String configPath) throws Exception {
         // Note this test relies on logcat values being printed by tombstone_transmit on
         // and the reeceiver on host (virtualization_service)
-        mMicrodroidDevice = MicrodroidBuilder
-                .fromDevicePath(getPathForPackage(PACKAGE_NAME), configPath)
-                .debugLevel("full")
-                .memoryMib(minMemorySize())
-                .numCpus(NUM_VCPUS)
-                .build((TestDevice) getDevice());
+        mMicrodroidDevice =
+                MicrodroidBuilder.fromDevicePath(getPathForPackage(PACKAGE_NAME), configPath)
+                        .debugLevel("full")
+                        .memoryMib(minMemorySize())
+                        .numCpus(NUM_VCPUS)
+                        .build(getAndroidDevice());
         mMicrodroidDevice.waitForBootComplete(BOOT_COMPLETE_TIMEOUT);
         mMicrodroidDevice.enableAdbRoot();
 
@@ -507,7 +547,7 @@
         ConfigUtils.uploadConfigForPushedAtoms(getDevice(), PACKAGE_NAME, atomIds);
 
         // Create VM with microdroid
-        TestDevice device = (TestDevice) getDevice();
+        TestDevice device = getAndroidDevice();
         final String configPath = "assets/vm_config_apex.json"; // path inside the APK
         ITestDevice microdroid =
                 MicrodroidBuilder.fromDevicePath(getPathForPackage(PACKAGE_NAME), configPath)
@@ -575,7 +615,7 @@
                         .debugLevel("full")
                         .memoryMib(minMemorySize())
                         .numCpus(NUM_VCPUS)
-                        .build((TestDevice) getDevice());
+                        .build(getAndroidDevice());
         mMicrodroidDevice.waitForBootComplete(BOOT_COMPLETE_TIMEOUT);
         CommandRunner microdroid = new CommandRunner(mMicrodroidDevice);
 
@@ -637,12 +677,12 @@
     @Test
     public void testMicrodroidRamUsage() throws Exception {
         final String configPath = "assets/vm_config.json";
-        mMicrodroidDevice = MicrodroidBuilder
-                .fromDevicePath(getPathForPackage(PACKAGE_NAME), configPath)
-                .debugLevel("full")
-                .memoryMib(minMemorySize())
-                .numCpus(NUM_VCPUS)
-                .build((TestDevice) getDevice());
+        mMicrodroidDevice =
+                MicrodroidBuilder.fromDevicePath(getPathForPackage(PACKAGE_NAME), configPath)
+                        .debugLevel("full")
+                        .memoryMib(minMemorySize())
+                        .numCpus(NUM_VCPUS)
+                        .build(getAndroidDevice());
         mMicrodroidDevice.waitForBootComplete(BOOT_COMPLETE_TIMEOUT);
         mMicrodroidDevice.enableAdbRoot();
 
@@ -677,9 +717,10 @@
     }
 
     @Test
-    public void testCustomVirtualMachinePermission()
-            throws DeviceNotAvailableException, IOException, JSONException {
-        assumeTrue("Protected VMs are not supported", isProtectedVmSupported());
+    public void testCustomVirtualMachinePermission() throws Exception {
+        assumeTrue(
+                "Protected VMs are not supported",
+                getAndroidDevice().supportsMicrodroid(/*protectedVm=*/ true));
         CommandRunner android = new CommandRunner(getDevice());
 
         // Pull etc/microdroid.json
@@ -728,7 +769,7 @@
     @After
     public void shutdown() throws Exception {
         if (mMicrodroidDevice != null) {
-            ((TestDevice) getDevice()).shutdownMicrodroid(mMicrodroidDevice);
+            getAndroidDevice().shutdownMicrodroid(mMicrodroidDevice);
         }
 
         cleanUpVirtualizationTestSetup(getDevice());
@@ -746,4 +787,10 @@
                         SHELL_PACKAGE_NAME,
                         "android.permission.USE_CUSTOM_VIRTUAL_MACHINE");
     }
+
+    private TestDevice getAndroidDevice() {
+        TestDevice androidDevice = (TestDevice) getDevice();
+        assertThat(androidDevice).isNotNull();
+        return androidDevice;
+    }
 }
diff --git a/tests/testapk/Android.bp b/tests/testapk/Android.bp
index 8d49721..df7c6c0 100644
--- a/tests/testapk/Android.bp
+++ b/tests/testapk/Android.bp
@@ -19,12 +19,12 @@
         "truth-prebuilt",
         "compatibility-common-util-devicesidelib",
     ],
-    libs: ["framework-virtualization"],
+    sdk_version: "test_current",
     jni_libs: [
         "MicrodroidTestNativeLib",
         "MicrodroidIdleNativeLib",
     ],
-    platform_apis: true,
+    jni_uses_platform_apis: true,
     use_embedded_native_libs: true,
     // We only support 64-bit ABI, but CTS demands all APKs to be multi-ABI.
     compile_multilib: "both",
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 eb719b8..71a9e3b 100644
--- a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
+++ b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
@@ -15,6 +15,7 @@
  */
 package com.android.microdroid.test;
 
+import static android.system.virtualmachine.VirtualMachineConfig.DEBUG_LEVEL_APP_ONLY;
 import static android.system.virtualmachine.VirtualMachineConfig.DEBUG_LEVEL_FULL;
 import static android.system.virtualmachine.VirtualMachineConfig.DEBUG_LEVEL_NONE;
 
@@ -27,8 +28,6 @@
 
 import android.content.Context;
 import android.os.Build;
-import android.os.ParcelFileDescriptor;
-import android.os.ServiceSpecificException;
 import android.os.SystemProperties;
 import android.system.virtualmachine.VirtualMachine;
 import android.system.virtualmachine.VirtualMachineCallback;
@@ -122,6 +121,7 @@
         assertThat(testResults.mAppRunProp).isEqualTo("true");
         assertThat(testResults.mSublibRunProp).isEqualTo("true");
         assertThat(testResults.mApkContentsPath).isEqualTo("/mnt/apk");
+        assertThat(testResults.mEncryptedStoragePath).isEqualTo("");
     }
 
     @Test
@@ -280,16 +280,27 @@
     }
 
     @Test
-    @CddTest(requirements = {
-            "9.17/C-1-1",
-            "9.17/C-2-7"
-    })
-    public void changingDebugLevelInvalidatesVmIdentity() throws Exception {
+    @CddTest(requirements = {"9.17/C-1-1", "9.17/C-2-7"})
+    public void changingNonDebuggableVmDebuggableInvalidatesVmIdentity() throws Exception {
+        changeDebugLevel(DEBUG_LEVEL_NONE, DEBUG_LEVEL_FULL);
+        changeDebugLevel(DEBUG_LEVEL_NONE, DEBUG_LEVEL_APP_ONLY);
+    }
+
+    @Test
+    @CddTest(requirements = {"9.17/C-1-1", "9.17/C-2-7"})
+    @Ignore("b/260067026")
+    public void changingAppDebuggableVmFullyDebuggableInvalidatesVmIdentity() throws Exception {
+        assume().withMessage("Skip for non-protected VM. b/239158757").that(mProtectedVm).isTrue();
+        changeDebugLevel(DEBUG_LEVEL_APP_ONLY, DEBUG_LEVEL_FULL);
+    }
+
+    private void changeDebugLevel(int fromLevel, int toLevel) throws Exception {
         assumeSupportedKernel();
 
-        VirtualMachineConfig.Builder builder = mInner.newVmConfigBuilder()
-                .setPayloadBinaryPath("MicrodroidTestNativeLib.so")
-                .setDebugLevel(DEBUG_LEVEL_NONE);
+        VirtualMachineConfig.Builder builder =
+                mInner.newVmConfigBuilder()
+                        .setPayloadBinaryPath("MicrodroidTestNativeLib.so")
+                        .setDebugLevel(fromLevel);
         VirtualMachineConfig normalConfig = builder.build();
         mInner.forceCreateNewVirtualMachine("test_vm", normalConfig);
         assertThat(tryBootVm(TAG, "test_vm").payloadStarted).isTrue();
@@ -304,10 +315,11 @@
         Files.copy(vmInstanceBackup.toPath(), vmInstance.toPath(), REPLACE_EXISTING);
         assertThat(tryBootVm(TAG, "test_vm").payloadStarted).isTrue();
 
-        // Launch the same VM with different debug level. The Java API prohibits this (thankfully).
+        // Launch the same VM with a different debug level. The Java API prohibits this
+        // (thankfully).
         // For testing, we do that by creating a new VM with debug level, and copy the old instance
         // image to the new VM instance image.
-        VirtualMachineConfig debugConfig = builder.setDebugLevel(DEBUG_LEVEL_FULL).build();
+        VirtualMachineConfig debugConfig = builder.setDebugLevel(toLevel).build();
         mInner.forceCreateNewVirtualMachine("test_vm", debugConfig);
         Files.copy(vmInstanceBackup.toPath(), vmInstance.toPath(), REPLACE_EXISTING);
         assertThat(tryBootVm(TAG, "test_vm").payloadStarted).isFalse();
@@ -341,7 +353,7 @@
         listener.runToFinish(TAG, vm);
         Exception e = exception.getNow(null);
         if (e != null) {
-            throw e;
+            throw new RuntimeException(e);
         }
         return vmCdis;
     }
@@ -457,7 +469,7 @@
                 .build();
         mInner.forceCreateNewVirtualMachine("test_vm", config);
 
-        assertThrows(ServiceSpecificException.class, () -> launchVmAndGetCdis("test_vm"));
+        assertThrows(Exception.class, () -> launchVmAndGetCdis("test_vm"));
     }
 
 
@@ -708,6 +720,7 @@
         String mSublibRunProp;
         String mExtraApkTestProp;
         String mApkContentsPath;
+        String mEncryptedStoragePath;
     }
 
     private TestResults runVmTestService(VirtualMachine vm) throws Exception {
@@ -729,6 +742,8 @@
                             testResults.mExtraApkTestProp =
                                     testService.readProperty("debug.microdroid.test.extra_apk");
                             testResults.mApkContentsPath = testService.getApkContentsPath();
+                            testResults.mEncryptedStoragePath =
+                                    testService.getEncryptedStoragePath();
                         } catch (Exception e) {
                             testResults.mException = e;
                         }
@@ -747,13 +762,6 @@
                         Log.i(TAG, "onPayloadStarted");
                         payloadStarted.complete(true);
                     }
-
-                    @Override
-                    public void onPayloadStdio(VirtualMachine vm, ParcelFileDescriptor stream) {
-                        Log.i(TAG, "onPayloadStdio");
-                        logVmOutput(
-                                TAG, new FileInputStream(stream.getFileDescriptor()), "Payload");
-                    }
                 };
         listener.runToFinish(TAG, vm);
         assertThat(payloadStarted.getNow(false)).isTrue();
diff --git a/tests/testapk/src/native/testbinary.cpp b/tests/testapk/src/native/testbinary.cpp
index 1b18ce9..5c217ff 100644
--- a/tests/testapk/src/native/testbinary.cpp
+++ b/tests/testapk/src/native/testbinary.cpp
@@ -76,40 +76,22 @@
         ndk::ScopedAStatus insecurelyExposeVmInstanceSecret(std::vector<uint8_t>* out) override {
             const uint8_t identifier[] = {1, 2, 3, 4};
             out->resize(32);
-            if (!AVmPayload_getVmInstanceSecret(identifier, sizeof(identifier), out->data(),
-                                                out->size())) {
-                return ndk::ScopedAStatus::
-                        fromServiceSpecificErrorWithMessage(0, "Failed to get VM instance secret");
-            }
+            AVmPayload_getVmInstanceSecret(identifier, sizeof(identifier), out->data(),
+                                           out->size());
             return ndk::ScopedAStatus::ok();
         }
 
         ndk::ScopedAStatus insecurelyExposeAttestationCdi(std::vector<uint8_t>* out) override {
-            size_t cdi_size;
-            if (!AVmPayload_getDiceAttestationCdi(nullptr, 0, &cdi_size)) {
-                return ndk::ScopedAStatus::
-                        fromServiceSpecificErrorWithMessage(0, "Failed to measure attestation cdi");
-            }
+            size_t cdi_size = AVmPayload_getDiceAttestationCdi(nullptr, 0);
             out->resize(cdi_size);
-            if (!AVmPayload_getDiceAttestationCdi(out->data(), out->size(), &cdi_size)) {
-                return ndk::ScopedAStatus::
-                        fromServiceSpecificErrorWithMessage(0, "Failed to get attestation cdi");
-            }
+            AVmPayload_getDiceAttestationCdi(out->data(), out->size());
             return ndk::ScopedAStatus::ok();
         }
 
         ndk::ScopedAStatus getBcc(std::vector<uint8_t>* out) override {
-            size_t bcc_size;
-            if (!AVmPayload_getDiceAttestationChain(nullptr, 0, &bcc_size)) {
-                return ndk::ScopedAStatus::
-                        fromServiceSpecificErrorWithMessage(0,
-                                                            "Failed to measure attestation chain");
-            }
+            size_t bcc_size = AVmPayload_getDiceAttestationChain(nullptr, 0);
             out->resize(bcc_size);
-            if (!AVmPayload_getDiceAttestationChain(out->data(), out->size(), &bcc_size)) {
-                return ndk::ScopedAStatus::
-                        fromServiceSpecificErrorWithMessage(0, "Failed to get attestation chain");
-            }
+            AVmPayload_getDiceAttestationChain(out->data(), out->size());
             return ndk::ScopedAStatus::ok();
         }
 
@@ -123,15 +105,20 @@
             *out = path;
             return ndk::ScopedAStatus::ok();
         }
+
+        ndk::ScopedAStatus getEncryptedStoragePath(std::string* out) override {
+            const char* path_c = AVmPayload_getEncryptedStoragePath();
+            if (path_c == nullptr) {
+                out->clear();
+            } else {
+                *out = path_c;
+            }
+            return ndk::ScopedAStatus::ok();
+        }
     };
     auto testService = ndk::SharedRefBase::make<TestService>();
 
-    auto callback = []([[maybe_unused]] void* param) {
-        if (!AVmPayload_notifyPayloadReady()) {
-            std::cerr << "failed to notify payload ready to virtualizationservice" << std::endl;
-            abort();
-        }
-    };
+    auto callback = []([[maybe_unused]] void* param) { AVmPayload_notifyPayloadReady(); };
     if (!AVmPayload_runVsockRpcServer(testService->asBinder().get(), testService->SERVICE_PORT,
                                       callback, nullptr)) {
         return Error() << "RPC Server failed to run";
@@ -158,9 +145,6 @@
 } // Anonymous namespace
 
 extern "C" int AVmPayload_main() {
-    // Forward standard I/O to the host.
-    AVmPayload_setupStdioProxy();
-
     // disable buffering to communicate seamlessly
     setvbuf(stdin, nullptr, _IONBF, 0);
     setvbuf(stdout, nullptr, _IONBF, 0);
diff --git a/virtualizationservice/Android.bp b/virtualizationservice/Android.bp
index d6f4607..b767013 100644
--- a/virtualizationservice/Android.bp
+++ b/virtualizationservice/Android.bp
@@ -22,6 +22,7 @@
     rustlibs: [
         "android.system.virtualizationcommon-rust",
         "android.system.virtualizationservice-rust",
+        "android.system.virtualizationservice_internal-rust",
         "android.system.virtualmachineservice-rust",
         "android.os.permissions_aidl-rust",
         "libandroid_logger",
diff --git a/virtualizationservice/aidl/Android.bp b/virtualizationservice/aidl/Android.bp
index da237f8..a0bbc00 100644
--- a/virtualizationservice/aidl/Android.bp
+++ b/virtualizationservice/aidl/Android.bp
@@ -34,6 +34,23 @@
 }
 
 aidl_interface {
+    name: "android.system.virtualizationservice_internal",
+    srcs: ["android/system/virtualizationservice_internal/**/*.aidl"],
+    unstable: true,
+    backend: {
+        java: {
+            sdk_version: "module_current",
+        },
+        rust: {
+            enabled: true,
+            apex_available: [
+                "com.android.virt",
+            ],
+        },
+    },
+}
+
+aidl_interface {
     name: "android.system.virtualmachineservice",
     srcs: ["android/system/virtualmachineservice/**/*.aidl"],
     imports: ["android.system.virtualizationcommon"],
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualMachineCallback.aidl b/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualMachineCallback.aidl
index 521cf12..a329fa6 100644
--- a/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualMachineCallback.aidl
+++ b/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualMachineCallback.aidl
@@ -29,11 +29,6 @@
     void onPayloadStarted(int cid);
 
     /**
-     * Called when the payload provides access to its standard input/output via a socket.
-     */
-    void onPayloadStdio(int cid, in ParcelFileDescriptor fd);
-
-    /**
      * Called when the payload in the VM is ready to serve.
      */
     void onPayloadReady(int cid);
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice/PartitionType.aidl b/virtualizationservice/aidl/android/system/virtualizationservice/PartitionType.aidl
index f25e674..774681a 100644
--- a/virtualizationservice/aidl/android/system/virtualizationservice/PartitionType.aidl
+++ b/virtualizationservice/aidl/android/system/virtualizationservice/PartitionType.aidl
@@ -29,4 +29,8 @@
      * The partition is initialized as an instance image which is formatted to hold per-VM secrets
      */
     ANDROID_VM_INSTANCE = 1,
+    /**
+     * The partition is initialized to back encryptedstore disk image formatted to indicate intent
+     */
+    ENCRYPTEDSTORE = 2,
 }
diff --git a/tests/helper/src/java/com/android/virt/VirtualizationTestHelper.java b/virtualizationservice/aidl/android/system/virtualizationservice_internal/IGlobalVmContext.aidl
similarity index 66%
rename from tests/helper/src/java/com/android/virt/VirtualizationTestHelper.java
rename to virtualizationservice/aidl/android/system/virtualizationservice_internal/IGlobalVmContext.aidl
index 4c27915..1a7aa4a 100644
--- a/tests/helper/src/java/com/android/virt/VirtualizationTestHelper.java
+++ b/virtualizationservice/aidl/android/system/virtualizationservice_internal/IGlobalVmContext.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright 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.
@@ -13,10 +13,9 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.virt;
+package android.system.virtualizationservice_internal;
 
-public abstract class VirtualizationTestHelper {
-    public static boolean isCuttlefish(String vendorDeviceName) {
-        return vendorDeviceName != null && vendorDeviceName.startsWith("vsoc_");
-    }
+interface IGlobalVmContext {
+    /** Get the CID allocated to the VM. */
+    int getCid();
 }
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVirtualizationServiceInternal.aidl b/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVirtualizationServiceInternal.aidl
new file mode 100644
index 0000000..851ddf4
--- /dev/null
+++ b/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVirtualizationServiceInternal.aidl
@@ -0,0 +1,29 @@
+/*
+ * Copyright 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 android.system.virtualizationservice_internal;
+
+import android.system.virtualizationservice_internal.IGlobalVmContext;
+
+interface IVirtualizationServiceInternal {
+    /**
+     * Allocates global context for a new VM.
+     *
+     * This allocates VM's globally unique resources such as the CID.
+     * The resources will not be recycled as long as there is a strong reference
+     * to the returned object.
+     */
+    IGlobalVmContext allocateGlobalVmContext();
+}
diff --git a/virtualizationservice/aidl/android/system/virtualmachineservice/IVirtualMachineService.aidl b/virtualizationservice/aidl/android/system/virtualmachineservice/IVirtualMachineService.aidl
index deee662..3fdb48a 100644
--- a/virtualizationservice/aidl/android/system/virtualmachineservice/IVirtualMachineService.aidl
+++ b/virtualizationservice/aidl/android/system/virtualmachineservice/IVirtualMachineService.aidl
@@ -21,12 +21,6 @@
 interface IVirtualMachineService {
     /**
      * Port number that VirtualMachineService listens on connections from the guest VMs for the
-     * VirtualMachineService binder service.
-     */
-    const int VM_BINDER_SERVICE_PORT = 5000;
-
-    /**
-     * Port number that VirtualMachineService listens on connections from the guest VMs for the
      * tombtones
      */
     const int VM_TOMBSTONES_SERVICE_PORT = 2000;
@@ -50,9 +44,4 @@
      * Notifies that an error has occurred inside the VM.
      */
     void notifyError(ErrorCode errorCode, in String message);
-
-    /**
-     * Notifies that the guest has started a stdio proxy on the given port.
-     */
-    void connectPayloadStdioProxy(int port);
 }
diff --git a/virtualizationservice/src/aidl.rs b/virtualizationservice/src/aidl.rs
index 30b89da..040c0d8 100644
--- a/virtualizationservice/src/aidl.rs
+++ b/virtualizationservice/src/aidl.rs
@@ -16,7 +16,7 @@
 
 use crate::atom::{write_vm_booted_stats, write_vm_creation_stats};
 use crate::composite::make_composite_image;
-use crate::crosvm::{CrosvmConfig, DiskFile, PayloadState, VmInstance, VmState};
+use crate::crosvm::{CrosvmConfig, DiskFile, PayloadState, VmContext, VmInstance, VmState};
 use crate::payload::{add_microdroid_payload_images, add_microdroid_system_images};
 use crate::selinux::{getfilecon, SeContext};
 use android_os_permissions_aidl::aidl::android::os::IPermissionController;
@@ -36,23 +36,27 @@
     VirtualMachineRawConfig::VirtualMachineRawConfig,
     VirtualMachineState::VirtualMachineState,
 };
+use android_system_virtualizationservice_internal::aidl::android::system::virtualizationservice_internal::{
+    IGlobalVmContext::{BnGlobalVmContext, IGlobalVmContext},
+    IVirtualizationServiceInternal::{BnVirtualizationServiceInternal, IVirtualizationServiceInternal},
+};
 use android_system_virtualmachineservice::aidl::android::system::virtualmachineservice::IVirtualMachineService::{
-        BnVirtualMachineService, IVirtualMachineService, VM_BINDER_SERVICE_PORT,
-        VM_TOMBSTONES_SERVICE_PORT,
+        BnVirtualMachineService, IVirtualMachineService, VM_TOMBSTONES_SERVICE_PORT,
 };
 use anyhow::{anyhow, bail, Context, Result};
 use apkverify::{HashAlgorithm, V4Signature};
 use binder::{
     self, BinderFeatures, ExceptionCode, Interface, LazyServiceGuard, ParcelFileDescriptor,
-    SpIBinder, Status, StatusCode, Strong, ThreadState,
+    Status, StatusCode, Strong, ThreadState,
 };
 use disk::QcowFile;
 use libc::VMADDR_CID_HOST;
 use log::{debug, error, info, warn};
 use microdroid_payload_config::{OsConfig, Task, TaskType, VmPayloadConfig};
-use rpcbinder::run_vsock_rpc_server_with_factory;
+use rpcbinder::RpcServer;
 use rustutils::system_properties;
 use semver::VersionReq;
+use std::collections::HashMap;
 use std::convert::TryInto;
 use std::ffi::CStr;
 use std::fs::{create_dir, File, OpenOptions};
@@ -76,7 +80,8 @@
 
 /// The first CID to assign to a guest VM managed by the VirtualizationService. CIDs lower than this
 /// are reserved for the host or other usage.
-const FIRST_GUEST_CID: Cid = 10;
+const GUEST_CID_MIN: Cid = 2048;
+const GUEST_CID_MAX: Cid = 65535;
 
 const SYSPROP_LAST_CID: &str = "virtualizationservice.state.last_cid";
 
@@ -94,10 +99,149 @@
 
 const MICRODROID_OS_NAME: &str = "microdroid";
 
-/// Implementation of `IVirtualizationService`, the entry point of the AIDL service.
+const UNFORMATTED_STORAGE_MAGIC: &str = "UNFORMATTED-STORAGE";
+
+fn is_valid_guest_cid(cid: Cid) -> bool {
+    (GUEST_CID_MIN..=GUEST_CID_MAX).contains(&cid)
+}
+
+fn next_guest_cid(cid: Cid) -> Cid {
+    assert!(is_valid_guest_cid(cid));
+    if cid == GUEST_CID_MAX {
+        GUEST_CID_MIN
+    } else {
+        cid + 1
+    }
+}
+
+/// Singleton service for allocating globally-unique VM resources, such as the CID, and running
+/// singleton servers, like tombstone receiver.
 #[derive(Debug, Default)]
+pub struct VirtualizationServiceInternal {
+    state: Arc<Mutex<GlobalState>>,
+}
+
+impl VirtualizationServiceInternal {
+    pub fn init() -> VirtualizationServiceInternal {
+        let service = VirtualizationServiceInternal::default();
+
+        std::thread::spawn(|| {
+            if let Err(e) = handle_stream_connection_tombstoned() {
+                warn!("Error receiving tombstone from guest or writing them. Error: {:?}", e);
+            }
+        });
+
+        service
+    }
+}
+
+impl Interface for VirtualizationServiceInternal {}
+
+impl IVirtualizationServiceInternal for VirtualizationServiceInternal {
+    fn allocateGlobalVmContext(&self) -> binder::Result<Strong<dyn IGlobalVmContext>> {
+        let state = &mut *self.state.lock().unwrap();
+        let cid = state.allocate_cid().map_err(|e| {
+            Status::new_exception_str(ExceptionCode::ILLEGAL_STATE, Some(e.to_string()))
+        })?;
+        Ok(GlobalVmContext::create(cid))
+    }
+}
+
+/// The mutable state of the VirtualizationServiceInternal. There should only be one instance
+/// of this struct.
+#[derive(Debug, Default)]
+struct GlobalState {
+    /// CIDs currently allocated to running VMs. A CID is never recycled as long
+    /// as there is a strong reference held by a GlobalVmContext.
+    held_cids: HashMap<Cid, Weak<Cid>>,
+}
+
+impl GlobalState {
+    /// Get the next available CID, or an error if we have run out. The last CID used is stored in
+    /// a system property so that restart of virtualizationservice doesn't reuse CID while the host
+    /// Android is up.
+    fn allocate_cid(&mut self) -> Result<Arc<Cid>> {
+        // Garbage collect unused CIDs.
+        self.held_cids.retain(|_, cid| cid.strong_count() > 0);
+
+        // Start trying to find a CID from the last used CID + 1. This ensures
+        // that we do not eagerly recycle CIDs. It makes debugging easier but
+        // also means that retrying to allocate a CID, eg. because it is
+        // erroneously occupied by a process, will not recycle the same CID.
+        let last_cid_prop =
+            system_properties::read(SYSPROP_LAST_CID)?.and_then(|val| match val.parse::<Cid>() {
+                Ok(num) => {
+                    if is_valid_guest_cid(num) {
+                        Some(num)
+                    } else {
+                        error!("Invalid value '{}' of property '{}'", num, SYSPROP_LAST_CID);
+                        None
+                    }
+                }
+                Err(_) => {
+                    error!("Invalid value '{}' of property '{}'", val, SYSPROP_LAST_CID);
+                    None
+                }
+            });
+
+        let first_cid = if let Some(last_cid) = last_cid_prop {
+            next_guest_cid(last_cid)
+        } else {
+            GUEST_CID_MIN
+        };
+
+        let cid = self
+            .find_available_cid(first_cid..=GUEST_CID_MAX)
+            .or_else(|| self.find_available_cid(GUEST_CID_MIN..first_cid));
+
+        if let Some(cid) = cid {
+            let cid_arc = Arc::new(cid);
+            self.held_cids.insert(cid, Arc::downgrade(&cid_arc));
+            system_properties::write(SYSPROP_LAST_CID, &format!("{}", cid))?;
+            Ok(cid_arc)
+        } else {
+            Err(anyhow!("Could not find an available CID."))
+        }
+    }
+
+    fn find_available_cid<I>(&self, mut range: I) -> Option<Cid>
+    where
+        I: Iterator<Item = Cid>,
+    {
+        range.find(|cid| !self.held_cids.contains_key(cid))
+    }
+}
+
+/// Implementation of the AIDL `IGlobalVmContext` interface.
+#[derive(Debug, Default)]
+struct GlobalVmContext {
+    /// The unique CID assigned to the VM for vsock communication.
+    cid: Arc<Cid>,
+    /// Keeps our service process running as long as this VM context exists.
+    #[allow(dead_code)]
+    lazy_service_guard: LazyServiceGuard,
+}
+
+impl GlobalVmContext {
+    fn create(cid: Arc<Cid>) -> Strong<dyn IGlobalVmContext> {
+        let binder = GlobalVmContext { cid, ..Default::default() };
+        BnGlobalVmContext::new_binder(binder, BinderFeatures::default())
+    }
+}
+
+impl Interface for GlobalVmContext {}
+
+impl IGlobalVmContext for GlobalVmContext {
+    fn getCid(&self) -> binder::Result<i32> {
+        Ok(*self.cid as i32)
+    }
+}
+
+/// Implementation of `IVirtualizationService`, the entry point of the AIDL service.
+#[derive(Debug)]
 pub struct VirtualizationService {
     state: Arc<Mutex<State>>,
+    global_service: Strong<dyn IVirtualizationServiceInternal>,
 }
 
 impl Interface for VirtualizationService {
@@ -173,6 +317,7 @@
         match partition_type {
             PartitionType::RAW => Ok(()),
             PartitionType::ANDROID_VM_INSTANCE => format_as_android_vm_instance(&mut part),
+            PartitionType::ENCRYPTEDSTORE => format_as_encryptedstore(&mut part),
             _ => Err(Error::new(
                 ErrorKind::Unsupported,
                 format!("Unsupported partition type {:?}", partition_type),
@@ -250,8 +395,10 @@
 }
 
 fn handle_stream_connection_tombstoned() -> Result<()> {
+    // Should not listen for tombstones on a guest VM's port.
+    assert!(!is_valid_guest_cid(VM_TOMBSTONES_SERVICE_PORT as Cid));
     let listener =
-        VsockListener::bind_with_cid_port(VMADDR_CID_HOST, VM_TOMBSTONES_SERVICE_PORT as u32)?;
+        VsockListener::bind_with_cid_port(VMADDR_CID_HOST, VM_TOMBSTONES_SERVICE_PORT as Cid)?;
     for incoming_stream in listener.incoming() {
         let mut incoming_stream = match incoming_stream {
             Err(e) => {
@@ -299,28 +446,35 @@
 
 impl VirtualizationService {
     pub fn init() -> VirtualizationService {
-        let service = VirtualizationService::default();
+        let global_service = VirtualizationServiceInternal::init();
+        let global_service =
+            BnVirtualizationServiceInternal::new_binder(global_service, BinderFeatures::default());
 
-        std::thread::spawn(|| {
-            if let Err(e) = handle_stream_connection_tombstoned() {
-                warn!("Error receiving tombstone from guest or writing them. Error: {:?}", e);
-            }
-        });
+        VirtualizationService { global_service, state: Default::default() }
+    }
 
-        // binder server for vm
-        // reference to state (not the state itself) is copied
-        let state = service.state.clone();
-        std::thread::spawn(move || {
-            debug!("VirtualMachineService is starting as an RPC service.");
-            if run_vsock_rpc_server_with_factory(VM_BINDER_SERVICE_PORT as u32, |cid| {
-                VirtualMachineService::factory(cid, &state)
-            }) {
-                debug!("RPC server has shut down gracefully");
-            } else {
-                panic!("Premature termination of RPC server");
+    fn create_vm_context(&self) -> Result<(VmContext, Cid)> {
+        const NUM_ATTEMPTS: usize = 5;
+
+        for _ in 0..NUM_ATTEMPTS {
+            let global_context = self.global_service.allocateGlobalVmContext()?;
+            let cid = global_context.getCid()? as Cid;
+            let service = VirtualMachineService::new_binder(self.state.clone(), cid).as_binder();
+
+            // Start VM service listening for connections from the new CID on port=CID.
+            // TODO(b/245727626): Only accept connections from the new VM.
+            let port = cid;
+            match RpcServer::new_vsock(service, port) {
+                Ok(vm_server) => {
+                    vm_server.start();
+                    return Ok((VmContext::new(global_context, vm_server), cid));
+                }
+                Err(err) => {
+                    warn!("Could not start RpcServer on port {}: {}", port, err);
+                }
             }
-        });
-        service
+        }
+        bail!("Too many attempts to create VM context failed.");
     }
 
     fn create_vm_internal(
@@ -346,12 +500,19 @@
             check_use_custom_virtual_machine()?;
         }
 
+        let (vm_context, cid) = self.create_vm_context().map_err(|e| {
+            error!("Failed to create VmContext: {:?}", e);
+            Status::new_service_specific_error_str(
+                -1,
+                Some(format!("Failed to create VmContext: {:?}", e)),
+            )
+        })?;
+
         let state = &mut *self.state.lock().unwrap();
         let console_fd = console_fd.map(clone_file).transpose()?;
         let log_fd = log_fd.map(clone_file).transpose()?;
         let requester_uid = ThreadState::get_calling_uid();
         let requester_debug_pid = ThreadState::get_calling_pid();
-        let cid = state.next_cid().or(Err(ExceptionCode::ILLEGAL_STATE))?;
 
         // Counter to generate unique IDs for temporary image files.
         let mut next_temporary_image_id = 0;
@@ -468,14 +629,20 @@
             detect_hangup: is_app_config,
         };
         let instance = Arc::new(
-            VmInstance::new(crosvm_config, temporary_directory, requester_uid, requester_debug_pid)
-                .map_err(|e| {
-                    error!("Failed to create VM with config {:?}: {:?}", config, e);
-                    Status::new_service_specific_error_str(
-                        -1,
-                        Some(format!("Failed to create VM: {:?}", e)),
-                    )
-                })?,
+            VmInstance::new(
+                crosvm_config,
+                temporary_directory,
+                requester_uid,
+                requester_debug_pid,
+                vm_context,
+            )
+            .map_err(|e| {
+                error!("Failed to create VM with config {:?}: {:?}", config, e);
+                Status::new_service_specific_error_str(
+                    -1,
+                    Some(format!("Failed to create VM: {:?}", e)),
+                )
+            })?,
         );
         state.add_vm(Arc::downgrade(&instance));
         Ok(VirtualMachine::create(instance))
@@ -499,6 +666,11 @@
     part.flush()
 }
 
+fn format_as_encryptedstore(part: &mut dyn Write) -> std::io::Result<()> {
+    part.write_all(UNFORMATTED_STORAGE_MAGIC.as_bytes())?;
+    part.flush()
+}
+
 fn prepare_ramdump_file(ramdump_path: &Path) -> Result<File> {
     File::create(ramdump_path).context(format!("Failed to create ramdump file {:?}", &ramdump_path))
 }
@@ -859,16 +1031,6 @@
         }
     }
 
-    /// Call all registered callbacks to notify that the payload has provided a standard I/O proxy.
-    pub fn notify_payload_stdio(&self, cid: Cid, fd: ParcelFileDescriptor) {
-        let callbacks = &*self.0.lock().unwrap();
-        for callback in callbacks {
-            if let Err(e) = callback.onPayloadStdio(cid as i32, &fd) {
-                error!("Error notifying payload stdio event from VM CID {}: {:?}", cid, e);
-            }
-        }
-    }
-
     /// Call all registered callbacks to say that the VM has died.
     pub fn callback_on_died(&self, cid: Cid, reason: DeathReason) {
         let callbacks = &*self.0.lock().unwrap();
@@ -943,27 +1105,6 @@
         let vm = self.debug_held_vms.swap_remove(pos);
         Some(vm)
     }
-
-    /// Get the next available CID, or an error if we have run out. The last CID used is stored in
-    /// a system property so that restart of virtualizationservice doesn't reuse CID while the host
-    /// Android is up.
-    fn next_cid(&mut self) -> Result<Cid> {
-        let next = if let Some(val) = system_properties::read(SYSPROP_LAST_CID)? {
-            if let Ok(num) = val.parse::<u32>() {
-                num.checked_add(1).ok_or_else(|| anyhow!("run out of CID"))?
-            } else {
-                error!("Invalid last CID {}. Using {}", &val, FIRST_GUEST_CID);
-                FIRST_GUEST_CID
-            }
-        } else {
-            // First VM since the boot
-            FIRST_GUEST_CID
-        };
-        // Persist the last value for next use
-        let str_val = format!("{}", next);
-        system_properties::write(SYSPROP_LAST_CID, &str_val)?;
-        Ok(next)
-    }
 }
 
 /// Gets the `VirtualMachineState` of the given `VmInstance`.
@@ -1114,41 +1255,9 @@
             ))
         }
     }
-
-    fn connectPayloadStdioProxy(&self, port: i32) -> binder::Result<()> {
-        let cid = self.cid;
-        if let Some(vm) = self.state.lock().unwrap().get_vm(cid) {
-            info!("VM with CID {} started a stdio proxy", cid);
-            let stream = VsockStream::connect_with_cid_port(cid, port as u32).map_err(|e| {
-                Status::new_service_specific_error_str(
-                    -1,
-                    Some(format!("Failed to connect to guest stdio proxy: {:?}", e)),
-                )
-            })?;
-            vm.callbacks.notify_payload_stdio(cid, vsock_stream_to_pfd(stream));
-            Ok(())
-        } else {
-            error!("connectPayloadStdioProxy is called from an unknown CID {}", cid);
-            Err(Status::new_service_specific_error_str(
-                -1,
-                Some(format!("cannot find a VM with CID {}", cid)),
-            ))
-        }
-    }
 }
 
 impl VirtualMachineService {
-    fn factory(cid: Cid, state: &Arc<Mutex<State>>) -> Option<SpIBinder> {
-        if let Some(vm) = state.lock().unwrap().get_vm(cid) {
-            let mut vm_service = vm.vm_service.lock().unwrap();
-            let service = vm_service.get_or_insert_with(|| Self::new_binder(state.clone(), cid));
-            Some(service.as_binder())
-        } else {
-            error!("connection from cid={} is not from a guest VM", cid);
-            None
-        }
-    }
-
     fn new_binder(state: Arc<Mutex<State>>, cid: Cid) -> Strong<dyn IVirtualMachineService> {
         BnVirtualMachineService::new_binder(
             VirtualMachineService { state, cid },
diff --git a/virtualizationservice/src/crosvm.rs b/virtualizationservice/src/crosvm.rs
index db6da43..85a57c9 100644
--- a/virtualizationservice/src/crosvm.rs
+++ b/virtualizationservice/src/crosvm.rs
@@ -24,6 +24,7 @@
 use semver::{Version, VersionReq};
 use nix::{fcntl::OFlag, unistd::pipe2};
 use regex::{Captures, Regex};
+use rustutils::system_properties;
 use shared_child::SharedChild;
 use std::borrow::Cow;
 use std::cmp::max;
@@ -38,9 +39,11 @@
 use std::time::{Duration, SystemTime};
 use std::thread;
 use android_system_virtualizationservice::aidl::android::system::virtualizationservice::DeathReason::DeathReason;
+use android_system_virtualizationservice_internal::aidl::android::system::virtualizationservice_internal::IGlobalVmContext::IGlobalVmContext;
 use binder::Strong;
 use android_system_virtualmachineservice::aidl::android::system::virtualmachineservice::IVirtualMachineService::IVirtualMachineService;
 use tombstoned_client::{TombstonedConnection, DebuggerdDumpType};
+use rpcbinder::RpcServer;
 
 /// external/crosvm
 use base::UnixSeqpacketListener;
@@ -197,11 +200,30 @@
     }
 }
 
+/// Internal struct that holds the handles to globally unique resources of a VM.
+#[derive(Debug)]
+pub struct VmContext {
+    #[allow(dead_code)] // Keeps the global context alive
+    global_context: Strong<dyn IGlobalVmContext>,
+    #[allow(dead_code)] // Keeps the server alive
+    vm_server: RpcServer,
+}
+
+impl VmContext {
+    /// Construct new VmContext.
+    pub fn new(global_context: Strong<dyn IGlobalVmContext>, vm_server: RpcServer) -> VmContext {
+        VmContext { global_context, vm_server }
+    }
+}
+
 /// Information about a particular instance of a VM which may be running.
 #[derive(Debug)]
 pub struct VmInstance {
     /// The current state of the VM.
     pub vm_state: Mutex<VmState>,
+    /// Global resources allocated for this VM.
+    #[allow(dead_code)] // Keeps the context alive
+    vm_context: VmContext,
     /// The CID assigned to the VM for vsock communication.
     pub cid: Cid,
     /// The name of the VM.
@@ -234,6 +256,7 @@
         temporary_directory: PathBuf,
         requester_uid: u32,
         requester_debug_pid: i32,
+        vm_context: VmContext,
     ) -> Result<VmInstance, Error> {
         validate_config(&config)?;
         let cid = config.cid;
@@ -241,6 +264,7 @@
         let protected = config.protected;
         Ok(VmInstance {
             vm_state: Mutex::new(VmState::NotStarted { config }),
+            vm_context,
             cid,
             name,
             protected,
@@ -567,10 +591,15 @@
         .arg("info,disk=off")
         .arg("run")
         .arg("--disable-sandbox")
-        .arg("--no-balloon")
         .arg("--cid")
         .arg(config.cid.to_string());
 
+    if system_properties::read_bool("hypervisor.memory_reclaim.supported", false)? {
+        command.arg("--balloon-page-reporting");
+    } else {
+        command.arg("--no-balloon");
+    }
+
     if config.protected {
         command.arg("--protected-vm");
 
diff --git a/virtualizationservice/src/payload.rs b/virtualizationservice/src/payload.rs
index 233e74b..f6e8a7b 100644
--- a/virtualizationservice/src/payload.rs
+++ b/virtualizationservice/src/payload.rs
@@ -420,7 +420,7 @@
 
     if let Some(file) = storage_image {
         writable_partitions.push(Partition {
-            label: "encrypted-storage".to_owned(),
+            label: "encryptedstore".to_owned(),
             image: Some(ParcelFileDescriptor::new(file)),
             writable: true,
         });
diff --git a/vm/src/run.rs b/vm/src/run.rs
index 7cd5a19..01b916b 100644
--- a/vm/src/run.rs
+++ b/vm/src/run.rs
@@ -27,7 +27,7 @@
 use binder::ParcelFileDescriptor;
 use microdroid_payload_config::VmPayloadConfig;
 use std::fs::File;
-use std::io::{self, BufRead, BufReader};
+use std::io;
 use std::os::unix::io::{AsRawFd, FromRawFd};
 use std::path::{Path, PathBuf};
 use vmclient::{ErrorCode, VmInstance};
@@ -103,7 +103,7 @@
                 service,
                 path,
                 storage_size.unwrap_or(10 * 1024 * 1024),
-                PartitionType::RAW,
+                PartitionType::ENCRYPTEDSTORE,
             )?;
         }
         Some(open_parcel_file(path, true)?)
@@ -280,20 +280,6 @@
         eprintln!("payload started");
     }
 
-    fn on_payload_stdio(&self, _cid: i32, stream: &File) {
-        eprintln!("connecting payload stdio...");
-        // Show the output of the payload
-        let mut reader = BufReader::new(stream.try_clone().unwrap());
-        std::thread::spawn(move || loop {
-            let mut s = String::new();
-            match reader.read_line(&mut s) {
-                Ok(0) => break,
-                Ok(_) => print!("{}", s),
-                Err(e) => eprintln!("error reading from virtual machine: {}", e),
-            };
-        });
-    }
-
     fn on_payload_ready(&self, _cid: i32) {
         eprintln!("payload is ready");
     }
diff --git a/microdroid/vm_payload/Android.bp b/vm_payload/Android.bp
similarity index 98%
rename from microdroid/vm_payload/Android.bp
rename to vm_payload/Android.bp
index dd2a937..6be6f22 100644
--- a/microdroid/vm_payload/Android.bp
+++ b/vm_payload/Android.bp
@@ -17,6 +17,7 @@
         "liblibc",
         "liblog_rust",
         "librpcbinder_rs",
+        "libvsock",
     ],
     apex_available: [
         "com.android.compos",
diff --git a/microdroid/vm_payload/include-restricted/vm_payload_restricted.h b/vm_payload/include-restricted/vm_payload_restricted.h
similarity index 78%
rename from microdroid/vm_payload/include-restricted/vm_payload_restricted.h
rename to vm_payload/include-restricted/vm_payload_restricted.h
index 8170a64..0b78541 100644
--- a/microdroid/vm_payload/include-restricted/vm_payload_restricted.h
+++ b/vm_payload/include-restricted/vm_payload_restricted.h
@@ -36,21 +36,19 @@
  *
  * \param data pointer to size bytes where the chain is written.
  * \param size number of bytes that can be written to data.
- * \param total outputs the total size of the chain if the function succeeds
  *
- * \return true on success and false on failure.
+ * \return the total size of the chain
  */
-bool AVmPayload_getDiceAttestationChain(void *data, size_t size, size_t *total);
+size_t AVmPayload_getDiceAttestationChain(void *data, size_t size);
 
 /**
  * Get the VM's DICE attestation CDI.
  *
  * \param data pointer to size bytes where the CDI is written.
  * \param size number of bytes that can be written to data.
- * \param total outputs the total size of the CDI if the function succeeds
  *
- * \return true on success and false on failure.
+ * \return the total size of the CDI
  */
-bool AVmPayload_getDiceAttestationCdi(void *data, size_t size, size_t *total);
+size_t AVmPayload_getDiceAttestationCdi(void *data, size_t size);
 
 __END_DECLS
diff --git a/microdroid/vm_payload/include/vm_main.h b/vm_payload/include/vm_main.h
similarity index 100%
rename from microdroid/vm_payload/include/vm_main.h
rename to vm_payload/include/vm_main.h
diff --git a/microdroid/vm_payload/include/vm_payload.h b/vm_payload/include/vm_payload.h
similarity index 69%
rename from microdroid/vm_payload/include/vm_payload.h
rename to vm_payload/include/vm_payload.h
index d5853a1..0ad4c64 100644
--- a/microdroid/vm_payload/include/vm_payload.h
+++ b/vm_payload/include/vm_payload.h
@@ -30,9 +30,13 @@
 /**
  * Notifies the host that the payload is ready.
  *
- * \return true if the notification succeeds else false.
+ * If the host app has set a `VirtualMachineCallback` for the VM, its
+ * `onPayloadReady` method will be called.
+ *
+ * Note that subsequent calls to this function after the first have no effect;
+ * `onPayloadReady` is never called more than once.
  */
-bool AVmPayload_notifyPayloadReady(void);
+void AVmPayload_notifyPayloadReady(void);
 
 /**
  * Runs a binder RPC server, serving the supplied binder service implementation on the given vsock
@@ -56,17 +60,16 @@
                                   void (*on_ready)(void *param), void *param);
 
 /**
- * Get a secret that is uniquely bound to this VM instance. The secrets are 32-byte values and the
- * value associated with an identifier will not change over the lifetime of the VM instance.
+ * Get a secret that is uniquely bound to this VM instance. The secrets are
+ * 32-byte values and the value associated with an identifier will not change
+ * over the lifetime of the VM instance.
  *
  * \param identifier identifier of the secret to return.
  * \param identifier_size size of the secret identifier.
  * \param secret pointer to size bytes where the secret is written.
- * \param size number of bytes of the secret to get, up to the secret size.
- *
- * \return true on success and false on failure.
+ * \param size number of bytes of the secret to get, <= 32.
  */
-bool AVmPayload_getVmInstanceSecret(const void *identifier, size_t identifier_size, void *secret,
+void AVmPayload_getVmInstanceSecret(const void *identifier, size_t identifier_size, void *secret,
                                     size_t size);
 
 /**
@@ -81,12 +84,16 @@
 const char *AVmPayload_getApkContentsPath(void);
 
 /**
- * Initiates a socket connection with the host and duplicates stdin, stdout and
- * stderr file descriptors to the socket.
+ * Gets the path to the encrypted persistent storage for the VM, if any. This is
+ * a directory under which any files or directories created will be stored on
+ * behalf of the VM by the host app. All data is encrypted using a key known
+ * only to the VM, so the host cannot decrypt it, but may delete it.
  *
- * \return true on success and false on failure. If unsuccessful, the stdio FDs
- * may be in an inconsistent state.
+ * \return the path to the APK contents, or NULL if no encrypted storage was
+ * requested in the VM configuration. If non-null the returned string should not
+ * be deleted or freed by the application and remains valid for the lifetime of
+ * the VM.
  */
-bool AVmPayload_setupStdioProxy();
+const char *AVmPayload_getEncryptedStoragePath(void);
 
 __END_DECLS
diff --git a/vm_payload/src/api.rs b/vm_payload/src/api.rs
new file mode 100644
index 0000000..febc2be
--- /dev/null
+++ b/vm_payload/src/api.rs
@@ -0,0 +1,235 @@
+// Copyright 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.
+
+//! This module handles the interaction with virtual machine payload service.
+
+use android_system_virtualization_payload::aidl::android::system::virtualization::payload::IVmPayloadService::{
+    IVmPayloadService, VM_PAYLOAD_SERVICE_SOCKET_NAME, VM_APK_CONTENTS_PATH};
+use anyhow::{ensure, Context, Result};
+use binder::{Strong, unstable_api::{AIBinder, new_spibinder}};
+use lazy_static::lazy_static;
+use log::{error, info, Level};
+use rpcbinder::{get_unix_domain_rpc_interface, RpcServer};
+use std::ffi::CString;
+use std::fmt::Debug;
+use std::os::raw::{c_char, c_void};
+use std::ptr;
+use std::sync::{Mutex, atomic::{AtomicBool, Ordering}};
+
+lazy_static! {
+    static ref VM_APK_CONTENTS_PATH_C: CString =
+        CString::new(VM_APK_CONTENTS_PATH).expect("CString::new failed");
+    static ref PAYLOAD_CONNECTION: Mutex<Option<Strong<dyn IVmPayloadService>>> = Mutex::default();
+}
+
+static ALREADY_NOTIFIED: AtomicBool = AtomicBool::new(false);
+
+/// Return a connection to the payload service in Microdroid Manager. Uses the existing connection
+/// if there is one, otherwise attempts to create a new one.
+fn get_vm_payload_service() -> Result<Strong<dyn IVmPayloadService>> {
+    let mut connection = PAYLOAD_CONNECTION.lock().unwrap();
+    if let Some(strong) = &*connection {
+        Ok(strong.clone())
+    } else {
+        let new_connection: Strong<dyn IVmPayloadService> = get_unix_domain_rpc_interface(
+            VM_PAYLOAD_SERVICE_SOCKET_NAME,
+        )
+        .context(format!("Failed to connect to service: {}", VM_PAYLOAD_SERVICE_SOCKET_NAME))?;
+        *connection = Some(new_connection.clone());
+        Ok(new_connection)
+    }
+}
+
+/// Make sure our logging goes to logcat. It is harmless to call this more than once.
+fn initialize_logging() {
+    android_logger::init_once(
+        android_logger::Config::default().with_tag("vm_payload").with_min_level(Level::Info),
+    );
+}
+
+/// In many cases clients can't do anything useful if API calls fail, and the failure
+/// generally indicates that the VM is exiting or otherwise doomed. So rather than
+/// returning a non-actionable error indication we just log the problem and abort
+/// the process.
+fn unwrap_or_abort<T, E: Debug>(result: Result<T, E>) -> T {
+    result.unwrap_or_else(|e| {
+        let msg = format!("{:?}", e);
+        error!("{msg}");
+        panic!("{msg}")
+    })
+}
+
+/// Notifies the host that the payload is ready.
+/// Panics on failure.
+#[no_mangle]
+pub extern "C" fn AVmPayload_notifyPayloadReady() {
+    initialize_logging();
+
+    if !ALREADY_NOTIFIED.swap(true, Ordering::Relaxed) {
+        unwrap_or_abort(try_notify_payload_ready());
+
+        info!("Notified host payload ready successfully");
+    }
+}
+
+/// Notifies the host that the payload is ready.
+/// Returns a `Result` containing error information if failed.
+fn try_notify_payload_ready() -> Result<()> {
+    get_vm_payload_service()?.notifyPayloadReady().context("Cannot notify payload ready")
+}
+
+/// Runs a binder RPC server, serving the supplied binder service implementation on the given vsock
+/// port.
+///
+/// If and when the server is ready for connections (it is listening on the port), `on_ready` is
+/// called to allow appropriate action to be taken - e.g. to notify clients that they may now
+/// attempt to connect.
+///
+/// The current thread is joined to the binder thread pool to handle incoming messages.
+///
+/// Returns true if the server has shutdown normally, false if it failed in some way.
+///
+/// # Safety
+///
+/// The `on_ready` callback is only called inside `run_vsock_rpc_server`, within the lifetime of
+/// `ReadyNotifier` (the last parameter of `run_vsock_rpc_server`). If `on_ready` is called with
+/// wrong param, the callback execution could go wrong.
+#[no_mangle]
+pub unsafe extern "C" fn AVmPayload_runVsockRpcServer(
+    service: *mut AIBinder,
+    port: u32,
+    on_ready: Option<unsafe extern "C" fn(param: *mut c_void)>,
+    param: *mut c_void,
+) -> bool {
+    initialize_logging();
+
+    // SAFETY: AIBinder returned has correct reference count, and the ownership can
+    // safely be taken by new_spibinder.
+    let service = new_spibinder(service);
+    if let Some(service) = service {
+        match RpcServer::new_vsock(service, port) {
+            Ok(server) => {
+                if let Some(on_ready) = on_ready {
+                    on_ready(param);
+                }
+                server.join();
+                true
+            }
+            Err(err) => {
+                error!("Failed to start RpcServer: {:?}", err);
+                false
+            }
+        }
+    } else {
+        error!("Failed to convert the given service from AIBinder to SpIBinder.");
+        false
+    }
+}
+
+/// Get a secret that is uniquely bound to this VM instance.
+/// Panics on failure.
+///
+/// # Safety
+///
+/// Behavior is undefined if any of the following conditions are violated:
+///
+/// * `identifier` must be [valid] for reads of `identifier_size` bytes.
+/// * `secret` must be [valid] for writes of `size` bytes.
+///
+/// [valid]: ptr#safety
+#[no_mangle]
+pub unsafe extern "C" fn AVmPayload_getVmInstanceSecret(
+    identifier: *const u8,
+    identifier_size: usize,
+    secret: *mut u8,
+    size: usize,
+) {
+    initialize_logging();
+
+    let identifier = std::slice::from_raw_parts(identifier, identifier_size);
+    let vm_secret = unwrap_or_abort(try_get_vm_instance_secret(identifier, size));
+    ptr::copy_nonoverlapping(vm_secret.as_ptr(), secret, size);
+}
+
+fn try_get_vm_instance_secret(identifier: &[u8], size: usize) -> Result<Vec<u8>> {
+    let vm_secret = get_vm_payload_service()?
+        .getVmInstanceSecret(identifier, i32::try_from(size)?)
+        .context("Cannot get VM instance secret")?;
+    ensure!(
+        vm_secret.len() == size,
+        "Returned secret has {} bytes, expected {}",
+        vm_secret.len(),
+        size
+    );
+    Ok(vm_secret)
+}
+
+/// Get the VM's attestation chain.
+/// Panics on failure.
+///
+/// # Safety
+///
+/// Behavior is undefined if any of the following conditions are violated:
+///
+/// * `data` must be [valid] for writes of `size` bytes.
+///
+/// [valid]: ptr#safety
+#[no_mangle]
+pub unsafe extern "C" fn AVmPayload_getDiceAttestationChain(data: *mut u8, size: usize) -> usize {
+    initialize_logging();
+
+    let chain = unwrap_or_abort(try_get_dice_attestation_chain());
+    ptr::copy_nonoverlapping(chain.as_ptr(), data, std::cmp::min(chain.len(), size));
+    chain.len()
+}
+
+fn try_get_dice_attestation_chain() -> Result<Vec<u8>> {
+    get_vm_payload_service()?.getDiceAttestationChain().context("Cannot get attestation chain")
+}
+
+/// Get the VM's attestation CDI.
+/// Panics on failure.
+///
+/// # Safety
+///
+/// Behavior is undefined if any of the following conditions are violated:
+///
+/// * `data` must be [valid] for writes of `size` bytes.
+///
+/// [valid]: ptr#safety
+#[no_mangle]
+pub unsafe extern "C" fn AVmPayload_getDiceAttestationCdi(data: *mut u8, size: usize) -> usize {
+    initialize_logging();
+
+    let cdi = unwrap_or_abort(try_get_dice_attestation_cdi());
+    ptr::copy_nonoverlapping(cdi.as_ptr(), data, std::cmp::min(cdi.len(), size));
+    cdi.len()
+}
+
+fn try_get_dice_attestation_cdi() -> Result<Vec<u8>> {
+    get_vm_payload_service()?.getDiceAttestationCdi().context("Cannot get attestation CDI")
+}
+
+/// Gets the path to the APK contents.
+#[no_mangle]
+pub extern "C" fn AVmPayload_getApkContentsPath() -> *const c_char {
+    (*VM_APK_CONTENTS_PATH_C).as_ptr()
+}
+
+/// Gets the path to the VM's encrypted storage.
+#[no_mangle]
+pub extern "C" fn AVmPayload_getEncryptedStoragePath() -> *const c_char {
+    // TODO(b/254454578): Return a real path if storage is present
+    ptr::null()
+}
diff --git a/microdroid/vm_payload/src/lib.rs b/vm_payload/src/lib.rs
similarity index 90%
rename from microdroid/vm_payload/src/lib.rs
rename to vm_payload/src/lib.rs
index 65b59bf..5c3ee31 100644
--- a/microdroid/vm_payload/src/lib.rs
+++ b/vm_payload/src/lib.rs
@@ -14,9 +14,9 @@
 
 //! Library for payload to communicate with the Microdroid Manager.
 
-mod vm_payload_service;
+mod api;
 
-pub use vm_payload_service::{
+pub use api::{
     AVmPayload_getDiceAttestationCdi, AVmPayload_getDiceAttestationChain,
-    AVmPayload_getVmInstanceSecret, AVmPayload_notifyPayloadReady, AVmPayload_setupStdioProxy,
+    AVmPayload_getVmInstanceSecret, AVmPayload_notifyPayloadReady,
 };
diff --git a/vmclient/src/lib.rs b/vmclient/src/lib.rs
index 1dd553c..20b7f02 100644
--- a/vmclient/src/lib.rs
+++ b/vmclient/src/lib.rs
@@ -80,9 +80,6 @@
     /// clients.
     fn on_payload_ready(&self, cid: i32) {}
 
-    /// Called by the payload to forward its standard I/O streams to the host.
-    fn on_payload_stdio(&self, cid: i32, fd: &File);
-
     /// Called when the payload has exited in the VM. `exit_code` is the exit code of the payload
     /// process.
     fn on_payload_finished(&self, cid: i32, exit_code: i32) {}
@@ -280,13 +277,6 @@
         Ok(())
     }
 
-    fn onPayloadStdio(&self, cid: i32, stream: &ParcelFileDescriptor) -> BinderResult<()> {
-        if let Some(ref callback) = self.client_callback {
-            callback.on_payload_stdio(cid, stream.as_ref());
-        }
-        Ok(())
-    }
-
     fn onPayloadReady(&self, cid: i32) -> BinderResult<()> {
         self.state.notify_state(VirtualMachineState::READY);
         if let Some(ref callback) = self.client_callback {