Merge "Revert "vm: DRY *Config structs w.r.t. aflags using cfg_if"" into main
diff --git a/android/LinuxInstaller/Android.bp b/android/LinuxInstaller/Android.bp
index 5f34c63..f70452d 100644
--- a/android/LinuxInstaller/Android.bp
+++ b/android/LinuxInstaller/Android.bp
@@ -13,6 +13,7 @@
     required: [
         "privapp-permissions-linuxinstaller.xml",
     ],
+    certificate: ":com.android.virtualization.linuxinstaller_certificate",
 }
 
 android_app {
@@ -24,6 +25,7 @@
     required: [
         "privapp-permissions-linuxinstaller.xml",
     ],
+    certificate: ":com.android.virtualization.linuxinstaller_certificate",
 }
 
 prebuilt_etc {
@@ -32,3 +34,8 @@
     sub_dir: "permissions",
     system_ext_specific: true,
 }
+
+android_app_certificate {
+    name: "com.android.virtualization.linuxinstaller_certificate",
+    certificate: "com_android_virtualization_linuxinstaller",
+}
diff --git a/android/LinuxInstaller/AndroidManifest.xml b/android/LinuxInstaller/AndroidManifest.xml
index 5b10d9e..e5653f6 100644
--- a/android/LinuxInstaller/AndroidManifest.xml
+++ b/android/LinuxInstaller/AndroidManifest.xml
@@ -1,6 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.virtualization.linuxinstaller" >
+    package="com.android.virtualization.linuxinstaller"
+    android:versionCode="2100000000" >
     <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
     <uses-permission android:name="android.permission.CHANGE_COMPONENT_ENABLED_STATE" />
     <queries>
diff --git a/android/LinuxInstaller/com_android_virtualization_linuxinstaller.pk8 b/android/LinuxInstaller/com_android_virtualization_linuxinstaller.pk8
new file mode 100644
index 0000000..3f74303
--- /dev/null
+++ b/android/LinuxInstaller/com_android_virtualization_linuxinstaller.pk8
Binary files differ
diff --git a/android/LinuxInstaller/com_android_virtualization_linuxinstaller.x509.pem b/android/LinuxInstaller/com_android_virtualization_linuxinstaller.x509.pem
new file mode 100644
index 0000000..3ca64b7
--- /dev/null
+++ b/android/LinuxInstaller/com_android_virtualization_linuxinstaller.x509.pem
@@ -0,0 +1,24 @@
+-----BEGIN CERTIFICATE-----
+MIIEETCCAvmgAwIBAgIUfBxyELS+ri3QErq8DXHu+47xx4EwDQYJKoZIhvcNAQEL
+BQAwgZYxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQH
+DA1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKDAtHb29nbGUgSW5jLjEQMA4GA1UECwwH
+QW5kcm9pZDEyMDAGA1UEAwwpY29tX2FuZHJvaWRfdmlydHVhbGl6YXRpb25fbGlu
+dXhpbnN0YWxsZXIwIBcNMjQwODMwMTIyNjU2WhgPMjA1MjAxMTYxMjI2NTZaMIGW
+MQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91
+bnRhaW4gVmlldzEUMBIGA1UECgwLR29vZ2xlIEluYy4xEDAOBgNVBAsMB0FuZHJv
+aWQxMjAwBgNVBAMMKWNvbV9hbmRyb2lkX3ZpcnR1YWxpemF0aW9uX2xpbnV4aW5z
+dGFsbGVyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA8V/rH9ju6Wce
+1BdWuxfWaLmZJHGShXeDO6MB86Wrm10m26j9PFzd8/8FRKsZaujZphwNZsqBsdlt
+pWeNKts9T9luZn19Ci4E8A2EtgSxmfI8Fjwj/OJHHO0hG5+JcwIlUnmFQPcGtu/r
+EL3i7SfcF2ok+IC6aKYohnSbo+YkjyCSwb39i6POe6v6cPIZJtmOnecThS+fYCYR
+2yoMSSr3Bf8ayySrG0pJp7xZ1I5NixK6hUFZhQRLusyiv/KYTpAElMd+n1YJEYbf
+pW30DYAu+31S0hx8JXncFmI0uG3Zxx+LgNQwY8OPV6NPFfVwMPluZR6ep0tZ6q7e
+KIV2w5uC7QIDAQABo1MwUTAdBgNVHQ4EFgQU6FBYv7mW+9DR9q0c9uS4NNdX4Acw
+HwYDVR0jBBgwFoAU6FBYv7mW+9DR9q0c9uS4NNdX4AcwDwYDVR0TAQH/BAUwAwEB
+/zANBgkqhkiG9w0BAQsFAAOCAQEAj3bvUpwKjvpCggXzjMNkn7fAaQ0s1BubnkFe
+ge4zwz4tObP3OGRcxt5V9R5EZ7UY6bPcybA/rfg9FCzjcUQOBjmuepcQpbNHFW2I
+lasFa42UHkHSUFzeg2n9UC5iO3B+sclOr4EPaEE4HbG4B2vj++BYMW3C7PDyHc7R
+fq5ZsEEWcYUa8qZCO46I8AbMZ8iv1HpR4mZeQMkSxhD3uVHDQW+VqDTpzne/YBkJ
+yNfjpgFVZ/Y1E6BvvjzWZpBfj668fo7P3DekWHbvPPr/DiZ7OA6PCmAH1FBsi2c+
+xPgb9clDc2Zjb2Cd9lAoZdeB14zDOh6ZCF1c/i+qYt5tA9t+GA==
+-----END CERTIFICATE-----
diff --git a/android/TerminalApp/AndroidManifest.xml b/android/TerminalApp/AndroidManifest.xml
index d92aa8b..c92da67 100644
--- a/android/TerminalApp/AndroidManifest.xml
+++ b/android/TerminalApp/AndroidManifest.xml
@@ -6,7 +6,8 @@
     <uses-permission android:name="com.android.virtualization.vmlauncher.permission.USE_VM_LAUNCHER"/>
 
     <application
-        android:label="VmTerminalApp"
+	android:label="@string/app_name"
+        android:icon="@mipmap/ic_launcher"
         android:usesCleartextTraffic="true">
         <activity android:name=".MainActivity"
                   android:configChanges="orientation|screenSize|keyboard|keyboardHidden|navigation|uiMode|screenLayout|smallestScreenSize"
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.java b/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.java
index 9afca7f..a6723fb 100644
--- a/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.java
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.java
@@ -102,17 +102,16 @@
 
     @Override
     public boolean onMenuItemSelected(int featureId, MenuItem item) {
-        switch (item.getItemId()) {
-            case R.id.copy_ip_addr:
-                // TODO(b/340126051): remove this menu item when port forwarding is supported.
-                getSystemService(ClipboardManager.class)
-                        .setPrimaryClip(ClipData.newPlainText("A VM's IP address", mVmIpAddr));
-                return true;
-            case R.id.stop_vm:
-                VmLauncherServices.stopVmLauncherService(this);
-                return true;
-            default:
-                return super.onMenuItemSelected(featureId, item);
+        int id = item.getItemId();
+        if (id == R.id.copy_ip_addr) {
+            // TODO(b/340126051): remove this menu item when port forwarding is supported.
+            getSystemService(ClipboardManager.class)
+                    .setPrimaryClip(ClipData.newPlainText("A VM's IP address", mVmIpAddr));
+            return true;
+        } else if (id == R.id.stop_vm) {
+            VmLauncherServices.stopVmLauncherService(this);
+            return true;
         }
+        return super.onMenuItemSelected(featureId, item);
     }
 }
diff --git a/android/TerminalApp/res/drawable/ic_launcher_background.xml b/android/TerminalApp/res/drawable/ic_launcher_background.xml
new file mode 100644
index 0000000..07d5da9
--- /dev/null
+++ b/android/TerminalApp/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,170 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="108dp"
+    android:height="108dp"
+    android:viewportWidth="108"
+    android:viewportHeight="108">
+    <path
+        android:fillColor="#3DDC84"
+        android:pathData="M0,0h108v108h-108z" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M9,0L9,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,0L19,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M29,0L29,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M39,0L39,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M49,0L49,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M59,0L59,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M69,0L69,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M79,0L79,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M89,0L89,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M99,0L99,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,9L108,9"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,19L108,19"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,29L108,29"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,39L108,39"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,49L108,49"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,59L108,59"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,69L108,69"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,79L108,79"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,89L108,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,99L108,99"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,29L89,29"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,39L89,39"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,49L89,49"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,59L89,59"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,69L89,69"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,79L89,79"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M29,19L29,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M39,19L39,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M49,19L49,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M59,19L59,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M69,19L69,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M79,19L79,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+</vector>
diff --git a/android/TerminalApp/res/drawable/ic_launcher_foreground.xml b/android/TerminalApp/res/drawable/ic_launcher_foreground.xml
new file mode 100644
index 0000000..8b28c8e
--- /dev/null
+++ b/android/TerminalApp/res/drawable/ic_launcher_foreground.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="108dp"
+    android:height="108dp"
+    android:viewportWidth="142"
+    android:viewportHeight="168.75">
+  <group android:scaleX="0.37325713"
+      android:scaleY="0.44357142"
+      android:translateX="43.332314"
+      android:translateY="39.324776">
+    <group android:translateY="133.59375">
+      <path android:pathData="M9.078125,-77.484375L69.75,-51.40625L69.75,-37.765625L9.078125,-11.609375L9.078125,-28.40625L52.53125,-44.71875L9.078125,-60.75L9.078125,-77.484375Z"
+          android:fillColor="#3BBA46"/>
+      <path android:pathData="M139.76562,0L139.76562,13.5L75.21875,13.5L75.21875,0L139.76562,0Z"
+          android:fillColor="#3BBA46"/>
+    </group>
+  </group>
+</vector>
\ No newline at end of file
diff --git a/android/TerminalApp/res/layout/activity_headless.xml b/android/TerminalApp/res/layout/activity_headless.xml
index 2a640f3..3fe5271 100644
--- a/android/TerminalApp/res/layout/activity_headless.xml
+++ b/android/TerminalApp/res/layout/activity_headless.xml
@@ -5,6 +5,7 @@
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:orientation="vertical"
+    android:fitsSystemWindows="true"
     tools:context=".MainActivity">
     <TextView
         android:id="@+id/ip_addr_textview"
diff --git a/android/TerminalApp/res/mipmap-anydpi-v26/ic_launcher.xml b/android/TerminalApp/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000..7353dbd
--- /dev/null
+++ b/android/TerminalApp/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+    <background android:drawable="@color/ic_launcher_background"/>
+    <foreground android:drawable="@drawable/ic_launcher_foreground"/>
+</adaptive-icon>
\ No newline at end of file
diff --git a/android/TerminalApp/res/mipmap-anydpi-v26/ic_launcher_round.xml b/android/TerminalApp/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 0000000..7353dbd
--- /dev/null
+++ b/android/TerminalApp/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+    <background android:drawable="@color/ic_launcher_background"/>
+    <foreground android:drawable="@drawable/ic_launcher_foreground"/>
+</adaptive-icon>
\ No newline at end of file
diff --git a/android/TerminalApp/res/mipmap-hdpi/ic_launcher_round.webp b/android/TerminalApp/res/mipmap-hdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..9be8219
--- /dev/null
+++ b/android/TerminalApp/res/mipmap-hdpi/ic_launcher_round.webp
Binary files differ
diff --git a/android/TerminalApp/res/mipmap-mdpi/ic_launcher_round.webp b/android/TerminalApp/res/mipmap-mdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..662c81e
--- /dev/null
+++ b/android/TerminalApp/res/mipmap-mdpi/ic_launcher_round.webp
Binary files differ
diff --git a/android/TerminalApp/res/mipmap-xhdpi/ic_launcher_round.webp b/android/TerminalApp/res/mipmap-xhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..2d7990d
--- /dev/null
+++ b/android/TerminalApp/res/mipmap-xhdpi/ic_launcher_round.webp
Binary files differ
diff --git a/android/TerminalApp/res/mipmap-xxhdpi/ic_launcher_round.webp b/android/TerminalApp/res/mipmap-xxhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..7941000
--- /dev/null
+++ b/android/TerminalApp/res/mipmap-xxhdpi/ic_launcher_round.webp
Binary files differ
diff --git a/android/TerminalApp/res/mipmap-xxxhdpi/ic_launcher_round.webp b/android/TerminalApp/res/mipmap-xxxhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..55f8020
--- /dev/null
+++ b/android/TerminalApp/res/mipmap-xxxhdpi/ic_launcher_round.webp
Binary files differ
diff --git a/android/TerminalApp/res/values/ic_launcher_background.xml b/android/TerminalApp/res/values/ic_launcher_background.xml
new file mode 100644
index 0000000..337764a
--- /dev/null
+++ b/android/TerminalApp/res/values/ic_launcher_background.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <color name="ic_launcher_background">#070E1E</color>
+</resources>
\ No newline at end of file
diff --git a/android/TerminalApp/res/values/strings.xml b/android/TerminalApp/res/values/strings.xml
index eb6476d..79da7cd 100644
--- a/android/TerminalApp/res/values/strings.xml
+++ b/android/TerminalApp/res/values/strings.xml
@@ -16,7 +16,8 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="vm_creation_message">A VM instance is being created.</string>
-    <string name="vm_stop_message">A VM instance is stopped, finish the app.</string>
-    <string name="vm_error_message">A VM instance is crashed, finish the app.</string>
-</resources>
\ No newline at end of file
+    <string name="app_name">Terminal</string>
+    <string name="vm_creation_message">Virtual machine is booting. Please wait.</string>
+    <string name="vm_stop_message">Virtual machine is stopped. Exiting.</string>
+    <string name="vm_error_message">Virtual machine crashed. Exiting.</string>
+</resources>
diff --git a/android/virtmgr/Android.bp b/android/virtmgr/Android.bp
index 4828057..62ff8d8 100644
--- a/android/virtmgr/Android.bp
+++ b/android/virtmgr/Android.bp
@@ -44,7 +44,6 @@
         "libglob",
         "libhex",
         "libhypervisor_props",
-        "liblazy_static",
         "liblibc",
         "liblog_rust",
         "libmicrodroid_metadata",
diff --git a/android/virtmgr/src/aidl.rs b/android/virtmgr/src/aidl.rs
index 5355c19..fb3d353 100644
--- a/android/virtmgr/src/aidl.rs
+++ b/android/virtmgr/src/aidl.rs
@@ -67,7 +67,6 @@
 };
 use cstr::cstr;
 use glob::glob;
-use lazy_static::lazy_static;
 use log::{debug, error, info, warn};
 use microdroid_payload_config::{ApkConfig, Task, TaskType, VmPayloadConfig};
 use nix::unistd::pipe;
@@ -86,7 +85,7 @@
 use std::os::unix::io::{AsRawFd, IntoRawFd};
 use std::os::unix::raw::pid_t;
 use std::path::{Path, PathBuf};
-use std::sync::{Arc, Mutex, Weak};
+use std::sync::{Arc, Mutex, Weak, LazyLock};
 use vbmeta::VbMetaImage;
 use vmconfig::{VmConfig, get_debug_level};
 use vsock::VsockStream;
@@ -119,13 +118,13 @@
 
 const VM_REFERENCE_DT_ON_HOST_PATH: &str = "/proc/device-tree/avf/reference";
 
-lazy_static! {
-    pub static ref GLOBAL_SERVICE: Strong<dyn IVirtualizationServiceInternal> =
+pub static GLOBAL_SERVICE: LazyLock<Strong<dyn IVirtualizationServiceInternal>> =
+    LazyLock::new(|| {
         wait_for_interface(BINDER_SERVICE_IDENTIFIER)
-            .expect("Could not connect to VirtualizationServiceInternal");
-    static ref SUPPORTED_OS_NAMES: HashSet<String> =
-        get_supported_os_names().expect("Failed to get list of supported os names");
-}
+            .expect("Could not connect to VirtualizationServiceInternal")
+    });
+static SUPPORTED_OS_NAMES: LazyLock<HashSet<String>> =
+    LazyLock::new(|| get_supported_os_names().expect("Failed to get list of supported os names"));
 
 fn create_or_update_idsig_file(
     input_fd: &ParcelFileDescriptor,
@@ -623,6 +622,7 @@
             boost_uclamp: config.boostUclamp,
             gpu_config,
             audio_config,
+            no_balloon: config.noBalloon,
         };
         let instance = Arc::new(
             VmInstance::new(
diff --git a/android/virtmgr/src/crosvm.rs b/android/virtmgr/src/crosvm.rs
index 08a9e47..9d688a2 100644
--- a/android/virtmgr/src/crosvm.rs
+++ b/android/virtmgr/src/crosvm.rs
@@ -20,7 +20,6 @@
 use anyhow::{anyhow, bail, Context, Error, Result};
 use binder::ParcelFileDescriptor;
 use command_fds::CommandFdExt;
-use lazy_static::lazy_static;
 use libc::{sysconf, _SC_CLK_TCK};
 use log::{debug, error, info};
 use semver::{Version, VersionReq};
@@ -39,7 +38,7 @@
 use std::os::unix::process::ExitStatusExt;
 use std::path::{Path, PathBuf};
 use std::process::{Command, ExitStatus};
-use std::sync::{Arc, Condvar, Mutex};
+use std::sync::{Arc, Condvar, Mutex, LazyLock};
 use std::time::{Duration, SystemTime};
 use std::thread::{self, JoinHandle};
 use android_system_virtualizationcommon::aidl::android::system::virtualizationcommon::DeathReason::DeathReason;
@@ -89,16 +88,16 @@
 /// Serial (emulated uart)
 const CONSOLE_TTYS0: &str = "ttyS0";
 
-lazy_static! {
-    /// If the VM doesn't move to the Started state within this amount time, a hang-up error is
-    /// triggered.
-    static ref BOOT_HANGUP_TIMEOUT: Duration = if nested_virt::is_nested_virtualization().unwrap() {
+/// If the VM doesn't move to the Started state within this amount time, a hang-up error is
+/// triggered.
+static BOOT_HANGUP_TIMEOUT: LazyLock<Duration> = LazyLock::new(|| {
+    if nested_virt::is_nested_virtualization().unwrap() {
         // Nested virtualization is slow, so we need a longer timeout.
         Duration::from_secs(300)
     } else {
         Duration::from_secs(30)
-    };
-}
+    }
+});
 
 /// Configuration for a VM to run with crosvm.
 #[derive(Debug)]
@@ -134,6 +133,7 @@
     pub boost_uclamp: bool,
     pub gpu_config: Option<GpuConfig>,
     pub audio_config: Option<AudioConfig>,
+    pub no_balloon: bool,
 }
 
 #[derive(Debug)]
@@ -892,7 +892,9 @@
         .arg("--cid")
         .arg(config.cid.to_string());
 
-    if system_properties::read_bool("hypervisor.memory_reclaim.supported", false)? {
+    if system_properties::read_bool("hypervisor.memory_reclaim.supported", false)?
+        && !config.no_balloon
+    {
         command.arg("--balloon-page-reporting");
     } else {
         command.arg("--no-balloon");
diff --git a/android/virtmgr/src/debug_config.rs b/android/virtmgr/src/debug_config.rs
index 52ac964..74559de 100644
--- a/android/virtmgr/src/debug_config.rs
+++ b/android/virtmgr/src/debug_config.rs
@@ -18,7 +18,6 @@
     VirtualMachineAppConfig::DebugLevel::DebugLevel, VirtualMachineConfig::VirtualMachineConfig,
 };
 use anyhow::{anyhow, Context, Error, Result};
-use lazy_static::lazy_static;
 use libfdt::{Fdt, FdtError};
 use log::{info, warn};
 use rustutils::system_properties;
@@ -26,6 +25,7 @@
 use std::fs;
 use std::io::ErrorKind;
 use std::path::{Path, PathBuf};
+use std::sync::LazyLock;
 use vmconfig::get_debug_level;
 
 const CUSTOM_DEBUG_POLICY_OVERLAY_SYSPROP: &str =
@@ -56,11 +56,12 @@
     }
 }
 
-lazy_static! {
-    static ref DP_LOG_PATH: DPPath = DPPath::new("/avf/guest/common", "log").unwrap();
-    static ref DP_RAMDUMP_PATH: DPPath = DPPath::new("/avf/guest/common", "ramdump").unwrap();
-    static ref DP_ADB_PATH: DPPath = DPPath::new("/avf/guest/microdroid", "adb").unwrap();
-}
+static DP_LOG_PATH: LazyLock<DPPath> =
+    LazyLock::new(|| DPPath::new("/avf/guest/common", "log").unwrap());
+static DP_RAMDUMP_PATH: LazyLock<DPPath> =
+    LazyLock::new(|| DPPath::new("/avf/guest/common", "ramdump").unwrap());
+static DP_ADB_PATH: LazyLock<DPPath> =
+    LazyLock::new(|| DPPath::new("/avf/guest/microdroid", "adb").unwrap());
 
 /// Get debug policy value in bool. It's true iff the value is explicitly set to <1>.
 fn get_debug_policy_bool(path: &Path) -> Result<bool> {
diff --git a/android/virtmgr/src/main.rs b/android/virtmgr/src/main.rs
index 7d29685..a4e75a7 100644
--- a/android/virtmgr/src/main.rs
+++ b/android/virtmgr/src/main.rs
@@ -27,10 +27,10 @@
 use android_system_virtualizationservice::aidl::android::system::virtualizationservice::IVirtualizationService::BnVirtualizationService;
 use anyhow::{bail, Result};
 use binder::{BinderFeatures, ProcessState};
-use lazy_static::lazy_static;
 use log::{info, LevelFilter};
 use rpcbinder::{FileDescriptorTransportMode, RpcServer};
 use std::os::unix::io::{AsFd, RawFd};
+use std::sync::LazyLock;
 use clap::Parser;
 use nix::unistd::{write, Pid, Uid};
 use std::os::unix::raw::{pid_t, uid_t};
@@ -38,11 +38,9 @@
 
 const LOG_TAG: &str = "virtmgr";
 
-lazy_static! {
-    static ref PID_CURRENT: Pid = Pid::this();
-    static ref PID_PARENT: Pid = Pid::parent();
-    static ref UID_CURRENT: Uid = Uid::current();
-}
+static PID_CURRENT: LazyLock<Pid> = LazyLock::new(Pid::this);
+static PID_PARENT: LazyLock<Pid> = LazyLock::new(Pid::parent);
+static UID_CURRENT: LazyLock<Uid> = LazyLock::new(Uid::current);
 
 fn get_this_pid() -> pid_t {
     // Return the process ID of this process.
diff --git a/android/virtualizationservice/Android.bp b/android/virtualizationservice/Android.bp
index f9034af..fb6e39a 100644
--- a/android/virtualizationservice/Android.bp
+++ b/android/virtualizationservice/Android.bp
@@ -38,7 +38,6 @@
         "libbinder_rs",
         "libhex",
         "libhypervisor_props",
-        "liblazy_static",
         "liblibc",
         "liblibsqlite3_sys",
         "liblog_rust",
diff --git a/android/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineRawConfig.aidl b/android/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineRawConfig.aidl
index 07d52db..4ac401d 100644
--- a/android/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineRawConfig.aidl
+++ b/android/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineRawConfig.aidl
@@ -100,4 +100,6 @@
     @nullable GpuConfig gpuConfig;
 
     @nullable AudioConfig audioConfig;
+
+    boolean noBalloon;
 }
diff --git a/android/virtualizationservice/src/aidl.rs b/android/virtualizationservice/src/aidl.rs
index acdb53a..0f16291 100644
--- a/android/virtualizationservice/src/aidl.rs
+++ b/android/virtualizationservice/src/aidl.rs
@@ -33,7 +33,6 @@
     self, wait_for_interface, BinderFeatures, ExceptionCode, Interface, IntoBinderResult,
     LazyServiceGuard, ParcelFileDescriptor, Status, Strong,
 };
-use lazy_static::lazy_static;
 use libc::{VMADDR_CID_HOST, VMADDR_CID_HYPERVISOR, VMADDR_CID_LOCAL};
 use log::{error, info, warn};
 use nix::unistd::{chown, Uid};
@@ -52,7 +51,7 @@
 use std::os::unix::fs::PermissionsExt;
 use std::os::unix::raw::{pid_t, uid_t};
 use std::path::{Path, PathBuf};
-use std::sync::{Arc, Condvar, Mutex, Weak};
+use std::sync::{Arc, Condvar, LazyLock, Mutex, Weak};
 use tombstoned_client::{DebuggerdDumpType, TombstonedConnection};
 use virtualizationcommon::Certificate::Certificate;
 use virtualizationmaintenance::{
@@ -157,18 +156,18 @@
     0xb9, 0x0f,
 ];
 
-lazy_static! {
-    static ref FAKE_PROVISIONED_KEY_BLOB_FOR_TESTING: Mutex<Option<Vec<u8>>> = Mutex::new(None);
-    static ref VFIO_SERVICE: Strong<dyn IVfioHandler> =
-        wait_for_interface(<BpVfioHandler as IVfioHandler>::get_descriptor())
-            .expect("Could not connect to VfioHandler");
-    static ref NETWORK_SERVICE: Strong<dyn IVmnic> =
-        wait_for_interface(<BpVmnic as IVmnic>::get_descriptor())
-            .expect("Could not connect to Vmnic");
-    static ref TETHERING_SERVICE: Strong<dyn IVmTethering> =
-        wait_for_interface(<BpVmTethering as IVmTethering>::get_descriptor())
-            .expect("Could not connect to VmTethering");
-}
+static FAKE_PROVISIONED_KEY_BLOB_FOR_TESTING: Mutex<Option<Vec<u8>>> = Mutex::new(None);
+static VFIO_SERVICE: LazyLock<Strong<dyn IVfioHandler>> = LazyLock::new(|| {
+    wait_for_interface(<BpVfioHandler as IVfioHandler>::get_descriptor())
+        .expect("Could not connect to VfioHandler")
+});
+static NETWORK_SERVICE: LazyLock<Strong<dyn IVmnic>> = LazyLock::new(|| {
+    wait_for_interface(<BpVmnic as IVmnic>::get_descriptor()).expect("Could not connect to Vmnic")
+});
+static TETHERING_SERVICE: LazyLock<Strong<dyn IVmTethering>> = LazyLock::new(|| {
+    wait_for_interface(<BpVmTethering as IVmTethering>::get_descriptor())
+        .expect("Could not connect to VmTethering")
+});
 
 fn is_valid_guest_cid(cid: Cid) -> bool {
     (GUEST_CID_MIN..=GUEST_CID_MAX).contains(&cid)
diff --git a/android/virtualizationservice/vfio_handler/Android.bp b/android/virtualizationservice/vfio_handler/Android.bp
index 66fc2ee..3635cf1 100644
--- a/android/virtualizationservice/vfio_handler/Android.bp
+++ b/android/virtualizationservice/vfio_handler/Android.bp
@@ -25,7 +25,6 @@
         "libandroid_logger",
         "libanyhow",
         "libbinder_rs",
-        "liblazy_static",
         "liblog_rust",
         "libnix",
         "librustutils",
diff --git a/android/virtualizationservice/vfio_handler/src/aidl.rs b/android/virtualizationservice/vfio_handler/src/aidl.rs
index b527260..3b4d0c5 100644
--- a/android/virtualizationservice/vfio_handler/src/aidl.rs
+++ b/android/virtualizationservice/vfio_handler/src/aidl.rs
@@ -20,11 +20,11 @@
 use android_system_virtualizationservice_internal::aidl::android::system::virtualizationservice_internal::IVfioHandler::VfioDev::VfioDev;
 use android_system_virtualizationservice_internal::binder::ParcelFileDescriptor;
 use binder::{self, BinderFeatures, ExceptionCode, Interface, IntoBinderResult, Strong};
-use lazy_static::lazy_static;
 use log::error;
 use std::fs::{read_link, write, File};
 use std::io::{Read, Seek, SeekFrom, Write};
 use std::mem::size_of;
+use std::sync::LazyLock;
 use std::path::{Path, PathBuf};
 use rustutils::system_properties;
 use zerocopy::{
@@ -169,10 +169,9 @@
     _custom: [U32<BigEndian>; 4],
 }
 
-lazy_static! {
-    static ref IS_VFIO_SUPPORTED: bool =
-        Path::new(DEV_VFIO_PATH).exists() && Path::new(VFIO_PLATFORM_DRIVER_PATH).exists();
-}
+static IS_VFIO_SUPPORTED: LazyLock<bool> = LazyLock::new(|| {
+    Path::new(DEV_VFIO_PATH).exists() && Path::new(VFIO_PLATFORM_DRIVER_PATH).exists()
+});
 
 fn check_platform_device(path: &Path) -> binder::Result<()> {
     if !path.exists() {
diff --git a/guest/authfs/Android.bp b/guest/authfs/Android.bp
index b11da3d..d7a8322 100644
--- a/guest/authfs/Android.bp
+++ b/guest/authfs/Android.bp
@@ -13,7 +13,6 @@
         "libanyhow",
         "libauthfs_fsverity_metadata",
         "libbinder_rs",
-        "libcfg_if",
         "libclap",
         "libfsverity_digests_proto_rust",
         "libfuse_rust",
diff --git a/guest/authfs/src/fusefs.rs b/guest/authfs/src/fusefs.rs
index 618b8ac..fa4076d 100644
--- a/guest/authfs/src/fusefs.rs
+++ b/guest/authfs/src/fusefs.rs
@@ -385,15 +385,6 @@
     }
 }
 
-cfg_if::cfg_if! {
-    if #[cfg(all(any(target_arch = "aarch64", target_arch = "riscv64"),
-                 target_pointer_width = "64"))] {
-        fn blk_size() -> libc::c_int { CHUNK_SIZE as libc::c_int }
-    } else {
-        fn blk_size() -> libc::c_long { CHUNK_SIZE as libc::c_long }
-    }
-}
-
 #[allow(clippy::enum_variant_names)]
 enum AccessMode {
     ReadOnly,
@@ -421,7 +412,7 @@
     st.st_gid = 0;
     st.st_size = libc::off64_t::try_from(file_size)
         .map_err(|_| io::Error::from_raw_os_error(libc::EFBIG))?;
-    st.st_blksize = blk_size();
+    st.st_blksize = CHUNK_SIZE.try_into().unwrap();
     // Per man stat(2), st_blocks is "Number of 512B blocks allocated".
     st.st_blocks = libc::c_longlong::try_from(divide_roundup(file_size, 512))
         .map_err(|_| io::Error::from_raw_os_error(libc::EFBIG))?;
diff --git a/libs/framework-virtualization/src/android/system/virtualmachine/VirtualMachineConfig.java b/libs/framework-virtualization/src/android/system/virtualmachine/VirtualMachineConfig.java
index a1230df..644a85a 100644
--- a/libs/framework-virtualization/src/android/system/virtualmachine/VirtualMachineConfig.java
+++ b/libs/framework-virtualization/src/android/system/virtualmachine/VirtualMachineConfig.java
@@ -724,6 +724,7 @@
                 Optional.ofNullable(customImageConfig.getAudioConfig())
                         .map(ac -> ac.toParcelable())
                         .orElse(null);
+        config.noBalloon = !customImageConfig.useAutoMemoryBalloon();
         return config;
     }
 
diff --git a/libs/framework-virtualization/src/android/system/virtualmachine/VirtualMachineCustomImageConfig.java b/libs/framework-virtualization/src/android/system/virtualmachine/VirtualMachineCustomImageConfig.java
index 37dc8fa..a38ee7f 100644
--- a/libs/framework-virtualization/src/android/system/virtualmachine/VirtualMachineCustomImageConfig.java
+++ b/libs/framework-virtualization/src/android/system/virtualmachine/VirtualMachineCustomImageConfig.java
@@ -360,7 +360,8 @@
         private boolean network;
         private GpuConfig gpuConfig;
         private boolean trackpad;
-        private boolean autoMemoryBalloon = true;
+        // TODO(b/363985291): balloon breaks Linux VM behavior
+        private boolean autoMemoryBalloon = false;
 
         /** @hide */
         public Builder() {}
diff --git a/libs/libcompos_common/Android.bp b/libs/libcompos_common/Android.bp
index 72cb5e1..01836ae 100644
--- a/libs/libcompos_common/Android.bp
+++ b/libs/libcompos_common/Android.bp
@@ -14,7 +14,6 @@
         "libanyhow",
         "libbinder_rs",
         "libglob",
-        "liblazy_static",
         "liblog_rust",
         "libnested_virt",
         "libnum_traits",
diff --git a/libs/libcompos_common/timeouts.rs b/libs/libcompos_common/timeouts.rs
index 7bd7679..d22f7f7 100644
--- a/libs/libcompos_common/timeouts.rs
+++ b/libs/libcompos_common/timeouts.rs
@@ -17,7 +17,7 @@
 //! Timeouts for common situations, with support for longer timeouts when using nested
 //! virtualization.
 
-use lazy_static::lazy_static;
+use std::sync::LazyLock;
 use std::time::Duration;
 
 /// Holder for the various timeouts we use.
@@ -31,15 +31,15 @@
     pub vm_max_time_to_exit: Duration,
 }
 
-lazy_static! {
 /// The timeouts that are appropriate on the current platform.
-pub static ref TIMEOUTS: Timeouts = if nested_virt::is_nested_virtualization().unwrap() {
-    // Nested virtualization is slow.
-    EXTENDED_TIMEOUTS
-} else {
-    NORMAL_TIMEOUTS
-};
-}
+pub static TIMEOUTS: LazyLock<Timeouts> = LazyLock::new(|| {
+    if nested_virt::is_nested_virtualization().unwrap() {
+        // Nested virtualization is slow.
+        EXTENDED_TIMEOUTS
+    } else {
+        NORMAL_TIMEOUTS
+    }
+});
 
 /// The timeouts that we use normally.
 const NORMAL_TIMEOUTS: Timeouts = Timeouts {
diff --git a/libs/libservice_vm_manager/Android.bp b/libs/libservice_vm_manager/Android.bp
index 6469212..b3618a6 100644
--- a/libs/libservice_vm_manager/Android.bp
+++ b/libs/libservice_vm_manager/Android.bp
@@ -12,7 +12,6 @@
         "android.system.virtualizationservice-rust",
         "libanyhow",
         "libciborium",
-        "liblazy_static",
         "liblog_rust",
         "libnix",
         "libservice_vm_comm",
diff --git a/libs/libservice_vm_manager/src/lib.rs b/libs/libservice_vm_manager/src/lib.rs
index d3d86e9..d7b4dd6 100644
--- a/libs/libservice_vm_manager/src/lib.rs
+++ b/libs/libservice_vm_manager/src/lib.rs
@@ -25,7 +25,6 @@
     binder::ParcelFileDescriptor,
 };
 use anyhow::{anyhow, ensure, Context, Result};
-use lazy_static::lazy_static;
 use log::{info, warn};
 use service_vm_comm::{Request, Response, ServiceVmRequest, VmType};
 use std::fs::{self, File, OpenOptions};
@@ -48,11 +47,10 @@
 const WRITE_BUFFER_CAPACITY: usize = 512;
 const READ_TIMEOUT: Duration = Duration::from_secs(10);
 const WRITE_TIMEOUT: Duration = Duration::from_secs(10);
-lazy_static! {
-    static ref PENDING_REQUESTS: AtomicCounter = AtomicCounter::default();
-    static ref SERVICE_VM: Mutex<Option<ServiceVm>> = Mutex::new(None);
-    static ref SERVICE_VM_SHUTDOWN: Condvar = Condvar::new();
-}
+
+static PENDING_REQUESTS: AtomicCounter = AtomicCounter::new();
+static SERVICE_VM: Mutex<Option<ServiceVm>> = Mutex::new(None);
+static SERVICE_VM_SHUTDOWN: Condvar = Condvar::new();
 
 /// Atomic counter with a condition variable that is used to wait for the counter
 /// to become positive within a timeout.
@@ -63,6 +61,10 @@
 }
 
 impl AtomicCounter {
+    const fn new() -> Self {
+        Self { num: Mutex::new(0), num_increased: Condvar::new() }
+    }
+
     /// Checks if the counter becomes positive within the given timeout.
     fn is_positive_within_timeout(&self, timeout: Duration) -> bool {
         let (guard, _wait_result) = self
diff --git a/libs/libvirtualization_jni/Android.bp b/libs/libvirtualization_jni/Android.bp
index 4a569d4..9dc86b0 100644
--- a/libs/libvirtualization_jni/Android.bp
+++ b/libs/libvirtualization_jni/Android.bp
@@ -16,7 +16,10 @@
         "liblog",
         "libnativehelper",
     ],
-    static_libs: ["libavf_cc_flags"],
+    static_libs: [
+        "libavf_cc_flags",
+        "libvmclient.ffi",
+    ],
 }
 
 cc_library_shared {
diff --git a/libs/libvirtualization_jni/android_system_virtualmachine_VirtualizationService.cpp b/libs/libvirtualization_jni/android_system_virtualmachine_VirtualizationService.cpp
index 0538c9e..f0c9b4f 100644
--- a/libs/libvirtualization_jni/android_system_virtualmachine_VirtualizationService.cpp
+++ b/libs/libvirtualization_jni/android_system_virtualmachine_VirtualizationService.cpp
@@ -19,6 +19,7 @@
 #include <android-base/unique_fd.h>
 #include <android/avf_cc_flags.h>
 #include <android/binder_ibinder_jni.h>
+#include <errno.h>
 #include <jni.h>
 #include <log/log.h>
 #include <poll.h>
@@ -29,57 +30,25 @@
 
 using namespace android::base;
 
-static constexpr const char VIRTMGR_PATH[] = "/apex/com.android.virt/bin/virtmgr";
 static constexpr size_t VIRTMGR_THREADS = 2;
 
+void error_callback(int code, const char* msg, void* ctx) {
+    JNIEnv* env = reinterpret_cast<JNIEnv*>(ctx);
+    if (code == EPERM || code == EACCES) {
+        env->ThrowNew(env->FindClass("java/lang/SecurityException"),
+                      "Virtmgr didn't send any data through pipe. Please consider checking if "
+                      "android.permission.MANAGE_VIRTUAL_MACHINE permission is granted");
+        return;
+    }
+    env->ThrowNew(env->FindClass("android/system/virtualmachine/VirtualMachineException"), msg);
+}
+
+extern "C" int get_virtualization_service(decltype(error_callback)*, void*);
+
 extern "C" JNIEXPORT jint JNICALL
 Java_android_system_virtualmachine_VirtualizationService_nativeSpawn(
         JNIEnv* env, [[maybe_unused]] jclass clazz) {
-    unique_fd serverFd, clientFd;
-    if (!Socketpair(SOCK_STREAM, &serverFd, &clientFd)) {
-        env->ThrowNew(env->FindClass("android/system/virtualmachine/VirtualMachineException"),
-                      ("Failed to create socketpair: " + std::string(strerror(errno))).c_str());
-        return -1;
-    }
-
-    unique_fd waitFd, readyFd;
-    if (!Pipe(&waitFd, &readyFd, 0)) {
-        env->ThrowNew(env->FindClass("android/system/virtualmachine/VirtualMachineException"),
-                      ("Failed to create pipe: " + std::string(strerror(errno))).c_str());
-        return -1;
-    }
-
-    if (fork() == 0) {
-        // Close client's FDs.
-        clientFd.reset();
-        waitFd.reset();
-
-        auto strServerFd = std::to_string(serverFd.get());
-        auto strReadyFd = std::to_string(readyFd.get());
-
-        execl(VIRTMGR_PATH, VIRTMGR_PATH, "--rpc-server-fd", strServerFd.c_str(), "--ready-fd",
-              strReadyFd.c_str(), NULL);
-    }
-
-    // Close virtmgr's FDs.
-    serverFd.reset();
-    readyFd.reset();
-
-    // Wait for the server to signal its readiness by closing its end of the pipe.
-    char buf;
-    int ret = read(waitFd.get(), &buf, sizeof(buf));
-    if (ret < 0) {
-        env->ThrowNew(env->FindClass("android/system/virtualmachine/VirtualMachineException"),
-                      "Failed to wait for VirtualizationService to be ready");
-        return -1;
-    } else if (ret < 1) {
-        env->ThrowNew(env->FindClass("java/lang/SecurityException"),
-                      "Virtmgr didn't send any data through pipe. Please consider checking if "
-                      "android.permission.MANAGE_VIRTUAL_MACHINE permission is granted");
-        return -1;
-    }
-
-    return clientFd.release();
+    return get_virtualization_service(error_callback, env);
 }
 
 extern "C" JNIEXPORT jobject JNICALL
diff --git a/libs/libvm_payload/Android.bp b/libs/libvm_payload/Android.bp
index cf2a002..bb91737 100644
--- a/libs/libvm_payload/Android.bp
+++ b/libs/libvm_payload/Android.bp
@@ -16,7 +16,6 @@
         "libandroid_logger",
         "libanyhow",
         "libbinder_rs",
-        "liblazy_static",
         "liblibc",
         "liblog_rust",
         "libopenssl",
diff --git a/libs/libvm_payload/src/lib.rs b/libs/libvm_payload/src/lib.rs
index 13c6e76..40f7b79 100644
--- a/libs/libvm_payload/src/lib.rs
+++ b/libs/libvm_payload/src/lib.rs
@@ -23,7 +23,6 @@
     unstable_api::{new_spibinder, AIBinder},
     Strong, ExceptionCode,
 };
-use lazy_static::lazy_static;
 use log::{error, info, LevelFilter};
 use rpcbinder::{RpcServer, RpcSession};
 use openssl::{ec::EcKey, sha::sha256, ecdsa::EcdsaSig};
@@ -35,6 +34,7 @@
 use std::ptr::{self, NonNull};
 use std::sync::{
     atomic::{AtomicBool, Ordering},
+    LazyLock,
     Mutex,
 };
 use vm_payload_status_bindgen::AVmAttestationStatus;
@@ -42,13 +42,11 @@
 /// Maximum size of an ECDSA signature for EC P-256 key is 72 bytes.
 const MAX_ECDSA_P256_SIGNATURE_SIZE: usize = 72;
 
-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 ref VM_ENCRYPTED_STORAGE_PATH_C: CString =
-        CString::new(ENCRYPTEDSTORE_MOUNTPOINT).expect("CString::new failed");
-}
+static VM_APK_CONTENTS_PATH_C: LazyLock<CString> =
+    LazyLock::new(|| CString::new(VM_APK_CONTENTS_PATH).expect("CString::new failed"));
+static PAYLOAD_CONNECTION: Mutex<Option<Strong<dyn IVmPayloadService>>> = Mutex::new(None);
+static VM_ENCRYPTED_STORAGE_PATH_C: LazyLock<CString> =
+    LazyLock::new(|| CString::new(ENCRYPTEDSTORE_MOUNTPOINT).expect("CString::new failed"));
 
 static ALREADY_NOTIFIED: AtomicBool = AtomicBool::new(false);
 
diff --git a/libs/libvmclient/Android.bp b/libs/libvmclient/Android.bp
index 9fdeaf8..5bd59da 100644
--- a/libs/libvmclient/Android.bp
+++ b/libs/libvmclient/Android.bp
@@ -2,8 +2,8 @@
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
-rust_library {
-    name: "libvmclient",
+rust_defaults {
+    name: "libvmclient.default",
     crate_name: "vmclient",
     defaults: ["avf_build_flags_rust"],
     srcs: ["src/lib.rs"],
@@ -25,3 +25,13 @@
         "com.android.virt",
     ],
 }
+
+rust_library {
+    name: "libvmclient",
+    defaults: ["libvmclient.default"],
+}
+
+rust_ffi_static {
+    name: "libvmclient.ffi",
+    defaults: ["libvmclient.default"],
+}
diff --git a/libs/libvmclient/src/lib.rs b/libs/libvmclient/src/lib.rs
index 7b576e6..bc9d683 100644
--- a/libs/libvmclient/src/lib.rs
+++ b/libs/libvmclient/src/lib.rs
@@ -43,7 +43,9 @@
 use log::warn;
 use rpcbinder::{FileDescriptorTransportMode, RpcSession};
 use shared_child::SharedChild;
+use std::ffi::{c_char, c_int, c_void, CString};
 use std::io::{self, Read};
+use std::os::fd::RawFd;
 use std::process::Command;
 use std::{
     fmt::{self, Debug, Formatter},
@@ -74,6 +76,40 @@
     Ok(socketpair(AddressFamily::Unix, SockType::Stream, None, SockFlag::SOCK_CLOEXEC)?)
 }
 
+/// Error handling function for `get_virtualization_service`.
+///
+/// # Safety
+/// `message` shouldn't be used outside of the lifetime of the function. Management of `ctx` is
+/// entirely up to the function.
+pub type ErrorCallback =
+    unsafe extern "C" fn(code: c_int, message: *const c_char, ctx: *mut c_void);
+
+/// Spawns a new instance of virtmgr and rerturns a file descriptor for the socket connection to
+/// the service. When error occurs, it is reported via the ErrorCallback function along with the
+/// error message and any context that is set by the client.
+///
+/// # Safety
+/// `cb` should be null or a valid function pointer of type `ErrorCallback`
+#[no_mangle]
+pub unsafe extern "C" fn get_virtualization_service(
+    cb: Option<ErrorCallback>,
+    ctx: *mut c_void,
+) -> RawFd {
+    match VirtualizationService::new() {
+        Ok(vs) => vs.client_fd.into_raw_fd(),
+        Err(e) => {
+            if let Some(cb) = cb {
+                let code = e.raw_os_error().unwrap_or(-1);
+                let msg = CString::new(e.to_string()).unwrap();
+                // SAFETY: `cb` doesn't use `msg` outside of the lifetime of the function.
+                // msg's lifetime is longer than `cb` as it is bound to a local variable.
+                unsafe { cb(code, msg.as_ptr(), ctx) };
+            }
+            -1
+        }
+    }
+}
+
 /// A running instance of virtmgr which is hosting a VirtualizationService
 /// RpcBinder server.
 pub struct VirtualizationService {
@@ -97,10 +133,11 @@
 
         SharedChild::spawn(&mut command)?;
 
-        // Wait for the child to signal that the RpcBinder server is ready
-        // by closing its end of the pipe.
-        let _ignored = File::from(wait_fd).read(&mut [0]);
-
+        // Wait for the child to signal that the RpcBinder server is read by closing its end of the
+        // pipe. Failing to read (especially EACCESS or EPERM) can happen if the client lacks the
+        // MANAGE_VIRTUAL_MACHINE permission. Therefore, such errors are propagated instead of
+        // being ignored.
+        let _ = File::from(wait_fd).read(&mut [0])?;
         Ok(VirtualizationService { client_fd })
     }