Merge "Argument list for 0 arguments should be (void)" into main
diff --git a/android/TerminalApp/AndroidManifest.xml b/android/TerminalApp/AndroidManifest.xml
index c11b1a0..8ed2f79 100644
--- a/android/TerminalApp/AndroidManifest.xml
+++ b/android/TerminalApp/AndroidManifest.xml
@@ -48,6 +48,7 @@
             </intent-filter>
         </activity>
         <activity android:name=".DisplayActivity"
+            android:taskAffinity="com.android.virtualization.terminal.display"
             android:screenOrientation="landscape"
             android:resizeableActivity="false"
             android:theme="@style/FullscreenTheme"
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.kt b/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.kt
index 1daeadb..0f18261 100644
--- a/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.kt
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.kt
@@ -368,6 +368,8 @@
             return true
         } else if (id == R.id.menu_item_display) {
             val intent = Intent(this, DisplayActivity::class.java)
+            intent.flags =
+                intent.flags or Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
             this.startActivity(intent)
             return true
         }
diff --git a/android/android.system.virtualmachine.res/Android.bp b/android/android.system.virtualmachine.res/Android.bp
index 1c55f78..c5d2dbb 100644
--- a/android/android.system.virtualmachine.res/Android.bp
+++ b/android/android.system.virtualmachine.res/Android.bp
@@ -2,23 +2,11 @@
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
-soong_config_module_type {
-    name: "avf_flag_aware_android_app",
-    module_type: "android_app",
-    config_namespace: "ANDROID",
-    bool_variables: ["release_avf_allow_preinstalled_apps"],
-    properties: ["manifest"],
-}
-
 // Defines our permissions
-avf_flag_aware_android_app {
+android_app {
     name: "android.system.virtualmachine.res",
     installable: true,
     apex_available: ["com.android.virt"],
     platform_apis: true,
-    soong_config_variables: {
-        release_avf_allow_preinstalled_apps: {
-            manifest: "AndroidManifestNext.xml",
-        },
-    },
+    manifest: "AndroidManifest.xml",
 }
diff --git a/android/android.system.virtualmachine.res/AndroidManifest.xml b/android/android.system.virtualmachine.res/AndroidManifest.xml
index 95b9cfa..c38d2b1 100644
--- a/android/android.system.virtualmachine.res/AndroidManifest.xml
+++ b/android/android.system.virtualmachine.res/AndroidManifest.xml
@@ -20,11 +20,11 @@
   <!-- @SystemApi Allows an application to create and run a Virtual Machine
        using the Virtualization Framework APIs
        (android.system.virtualmachine.*).
-       <p>Protection level: signature|privileged|development
+       <p>Protection level: signature|preinstalled|development
        @hide
   -->
   <permission android:name="android.permission.MANAGE_VIRTUAL_MACHINE"
-      android:protectionLevel="signature|privileged|development" />
+      android:protectionLevel="signature|preinstalled|development" />
 
   <!-- @hide Allows an application to run a Virtual Machine with a custom
        kernel or a Microdroid configuration file.
@@ -40,5 +40,14 @@
   <permission android:name="android.permission.DEBUG_VIRTUAL_MACHINE"
       android:protectionLevel="signature" />
 
+  <!-- @hide Makes Microdroid pVM use a more relaxed rollback protection scheme.
+    Should only be used by payloads delivered inside Mainline modules.
+    See packages/modules/Virtualization/docs/mainline_module_payload.md.
+    <p>Protection level: signature|development|privileged
+  -->
+  <permission android:name="android.permission.USE_RELAXED_MICRODROID_ROLLBACK_PROTECTION"
+      android:protectionLevel="signature|development|privileged" />
+
+
   <application android:hasCode="false" />
 </manifest>
diff --git a/android/android.system.virtualmachine.res/AndroidManifestNext.xml b/android/android.system.virtualmachine.res/AndroidManifestNext.xml
deleted file mode 100644
index ebcb8ba..0000000
--- a/android/android.system.virtualmachine.res/AndroidManifestNext.xml
+++ /dev/null
@@ -1,44 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- * Copyright (C) 2021 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.
- -->
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-  package="com.android.virtualmachine.res">
-
-  <!-- @SystemApi Allows an application to create and run a Virtual Machine
-       using the Virtualization Framework APIs
-       (android.system.virtualmachine.*).
-       <p>Protection level: signature|preinstalled|development
-       @hide
-  -->
-  <permission android:name="android.permission.MANAGE_VIRTUAL_MACHINE"
-      android:protectionLevel="signature|preinstalled|development" />
-
-  <!-- @hide Allows an application to run a Virtual Machine with a custom
-       kernel or a Microdroid configuration file.
-       <p>Not for use by third-party applications.
-  -->
-  <permission android:name="android.permission.USE_CUSTOM_VIRTUAL_MACHINE"
-      android:protectionLevel="signature|development" />
-
-  <!-- @hide Allows an application to access various Virtual Machine debug
-       facilities, e.g. list all running VMs.
-       <p>Not for use by third-party applications.
-  -->
-  <permission android:name="android.permission.DEBUG_VIRTUAL_MACHINE"
-      android:protectionLevel="signature" />
-
-  <application android:hasCode="false" />
-</manifest>
diff --git a/android/virtmgr/src/aidl.rs b/android/virtmgr/src/aidl.rs
index 33f3be1..2d31b87 100644
--- a/android/virtmgr/src/aidl.rs
+++ b/android/virtmgr/src/aidl.rs
@@ -71,7 +71,7 @@
 use glob::glob;
 use libc::{AF_VSOCK, sa_family_t, sockaddr_vm};
 use log::{debug, error, info, warn};
-use microdroid_payload_config::{ApkConfig, Task, TaskType, VmPayloadConfig};
+use microdroid_payload_config::{ApexConfig, ApkConfig, Task, TaskType, VmPayloadConfig};
 use nix::unistd::pipe;
 use rpcbinder::RpcServer;
 use rustutils::system_properties;
@@ -1409,7 +1409,19 @@
     let extra_apks =
         (0..extra_apk_count).map(|i| ApkConfig { path: format!("extra-apk-{i}") }).collect();
 
-    Ok(VmPayloadConfig { task: Some(task), extra_apks, ..Default::default() })
+    if check_use_relaxed_microdroid_rollback_protection().is_ok() {
+        // The only payload delivered via Mainline module in this release requires
+        // com.android.i18n apex. However, we are past all the reasonable deadlines to add new
+        // APIs, so we use the fact that the payload is granted
+        // USE_RELAXED_MICRODROID_ROLLBACK_PROTECTION permission as a signal that we should include
+        // com.android.i18n APEX.
+        // TODO: remove this after we provide a stable @SystemApi to load additional APEXes to
+        // Microdroid pVMs.
+        let apexes = vec![ApexConfig { name: String::from("com.android.i18n") }];
+        Ok(VmPayloadConfig { task: Some(task), apexes, extra_apks, ..Default::default() })
+    } else {
+        Ok(VmPayloadConfig { task: Some(task), extra_apks, ..Default::default() })
+    }
 }
 
 /// Generates a unique filename to use for a composite disk image.
@@ -1469,6 +1481,12 @@
     check_permission("android.permission.USE_CUSTOM_VIRTUAL_MACHINE")
 }
 
+/// Check whether the caller of the current binder method is allowed to use relaxed microdroid
+/// rollback protection schema.
+fn check_use_relaxed_microdroid_rollback_protection() -> binder::Result<()> {
+    check_permission("android.permission.USE_RELAXED_MICRODROID_ROLLBACK_PROTECTION")
+}
+
 /// Return whether a partition is exempt from selinux label checks, because we know that it does
 /// not contain code and is likely to be generated in an app-writable directory.
 fn is_safe_app_partition(label: &str) -> bool {
diff --git a/build/debian/fai_config/files/etc/systemd/system/avahi_ttyd.service/AVF b/build/debian/fai_config/files/etc/systemd/system/avahi_ttyd.service/AVF
new file mode 100644
index 0000000..09d4ee6
--- /dev/null
+++ b/build/debian/fai_config/files/etc/systemd/system/avahi_ttyd.service/AVF
@@ -0,0 +1,13 @@
+[Unit]
+Description=avahi_TTYD
+After=ttyd.service
+
+[Service]
+ExecStart=/usr/bin/avahi-publish-service ttyd _http._tcp 7681
+Type=simple
+Restart=always
+User=root
+Group=root
+
+[Install]
+WantedBy=multi-user.target
diff --git a/build/debian/fai_config/files/etc/systemd/system/ttyd.service/AVF b/build/debian/fai_config/files/etc/systemd/system/ttyd.service/AVF
index d86bab0..cf06fb3 100644
--- a/build/debian/fai_config/files/etc/systemd/system/ttyd.service/AVF
+++ b/build/debian/fai_config/files/etc/systemd/system/ttyd.service/AVF
@@ -6,7 +6,6 @@
 
 [Service]
 ExecStart=/usr/local/bin/ttyd --ssl --ssl-cert /etc/ttyd/server.crt --ssl-key /etc/ttyd/server.key --ssl-ca /mnt/internal/ca.crt -t disableLeaveAlert=true -W login -f droid
-ExecStartPost=/usr/bin/avahi-publish-service ttyd _http._tcp 7681
 Type=simple
 Restart=always
 User=root
diff --git a/build/debian/fai_config/files/etc/systemd/user/weston.service/AVF b/build/debian/fai_config/files/etc/systemd/user/weston.service/AVF
new file mode 100644
index 0000000..088c561
--- /dev/null
+++ b/build/debian/fai_config/files/etc/systemd/user/weston.service/AVF
@@ -0,0 +1,23 @@
+[Unit]
+Description=Weston, a Wayland compositor, as a user service
+Documentation=man:weston(1) man:weston.ini(5)
+Documentation=https://wayland.freedesktop.org/
+
+# Activate using a systemd socket
+Requires=weston.socket
+After=weston.socket
+
+# Since we are part of the graphical session, make sure we are started before
+Before=graphical-session.target
+
+[Service]
+Type=notify
+# Defaults to journal
+StandardOutput=journal
+StandardError=journal
+
+# add a ~/.config/weston.ini and weston will pick-it up
+ExecStart=/usr/bin/weston --modules=systemd-notify.so --xwayland --shell=kiosk-shell.so --continue-without-input
+
+[Install]
+WantedBy=graphical-session.target
\ No newline at end of file
diff --git a/build/debian/fai_config/files/etc/systemd/user/weston.socket/AVF b/build/debian/fai_config/files/etc/systemd/user/weston.socket/AVF
new file mode 100644
index 0000000..c57ff88
--- /dev/null
+++ b/build/debian/fai_config/files/etc/systemd/user/weston.socket/AVF
@@ -0,0 +1,7 @@
+[Unit]
+Description=Weston, a Wayland compositor
+Documentation=man:weston(1) man:weston.ini(5)
+Documentation=https://wayland.freedesktop.org/
+
+[Socket]
+ListenStream=%t/wayland-0
\ No newline at end of file
diff --git a/build/debian/fai_config/files/usr/local/bin/enable_display/AVF b/build/debian/fai_config/files/usr/local/bin/enable_display/AVF
new file mode 100644
index 0000000..69dce6a
--- /dev/null
+++ b/build/debian/fai_config/files/usr/local/bin/enable_display/AVF
@@ -0,0 +1,4 @@
+#!/bin/bash
+sudo systemd-run --collect -E XDG_SESSION_TYPE=wayland --uid=1000 -p PAMName=login -p TTYPath=/dev/tty7 sleep 1d
+systemctl --user start weston
+export DISPLAY=:0
\ No newline at end of file
diff --git a/build/debian/fai_config/scripts/AVF/10-systemd b/build/debian/fai_config/scripts/AVF/10-systemd
index 121acc5..a087a48 100755
--- a/build/debian/fai_config/scripts/AVF/10-systemd
+++ b/build/debian/fai_config/scripts/AVF/10-systemd
@@ -1,7 +1,9 @@
 #!/bin/bash
 
 chmod +x $target/usr/local/bin/ttyd
+chmod +x $target/usr/local/bin/enable_display
 ln -s /etc/systemd/system/ttyd.service $target/etc/systemd/system/multi-user.target.wants/ttyd.service
+ln -s /etc/systemd/system/avahi_ttyd.service $target/etc/systemd/system/multi-user.target.wants/avahi_ttyd.service
 ln -s /etc/systemd/system/virtiofs.service $target/etc/systemd/system/multi-user.target.wants/virtiofs.service
 ln -s /etc/systemd/system/virtiofs_internal.service $target/etc/systemd/system/multi-user.target.wants/virtiofs_internal.service
 ln -s /etc/systemd/system/backup_mount.service $target/etc/systemd/system/multi-user.target.wants/backup_mount.service
diff --git a/build/microdroid/init.rc b/build/microdroid/init.rc
index 672f47d..ce26a35 100644
--- a/build/microdroid/init.rc
+++ b/build/microdroid/init.rc
@@ -35,6 +35,16 @@
     # (In Android this happens inside apexd-bootstrap.)
     wait_for_prop ro.cold_boot_done true
 
+    # We need to define ANDROID_TZDATA_ROOT otherwise libicu complains.
+    # For now set it to a non-existent value, because libicu APIs that don't depend on tzdata work
+    # correctly without it.
+    # Once loading tzdata APEX is supported, we can set this value to /apex/com.android.tzdata.
+    # This is no-op for most microdroid VMs, and is only used if com.android.i18n APEX was mounted.
+    export ANDROID_TZDATA_ROOT /does/not/exist
+    # Set ANDROID_I18N_ROOT otherwise libicu will complain.
+    # This is no-op for most microdroid VMs, and is only used if com.android.i18n APEX was mounted.
+    export ANDROID_I18N_ROOT /apex/com.android.i18n
+
 on init
     mkdir /mnt/apk 0755 root root
     mkdir /mnt/extra-apk 0755 root root
diff --git a/docs/custom_vm.md b/docs/custom_vm.md
index 2815bbf..148d368 100644
--- a/docs/custom_vm.md
+++ b/docs/custom_vm.md
@@ -26,23 +26,9 @@
 `/apex/com.android.virt/bin/vm help` for details.
 
 # Terminal app
-## Graphical environment (Wayland, VNC)
-By installing Wayland compositor and VNC backend, you can enable graphical environment.
-One of the options is `sway`, `wayvnc` and `xwayland`(if necessary).
-
-```
-sudo apt install sway wayvnc xwayland
-WLR_BACKENDS=headless WLR_LIBINPUT_NO_DEVICES=1 sway
-WAYLAND_DISPLAY=wayland-1 wayvnc 0.0.0.0 # or use port forwarding
-```
-
-And then, connect to 192.168.0.2:5900(or localhost:5900) with arbitrary VNC client.
-Or, `novnc`(https://github.com/novnc/noVNC/releases). For `novnc` you need to install
-`novnc`, and run `<novnc_path>/utils/novnc_proxy`, and then connect to `http://192.168.0.2:6080/vnc.html`
-(or `localhost:6080` if port forwarding is enabled.)
-
-`weston` with VNC backend might be another option, but it isn't available in
-Debian package repository for bookworm.
+## Run GUI apps
+Execute `source enable_display` and then click Display button above to enable display feature.
+And then, go back to the terminal, and run GUI apps.
 
 ## Hardware acceleration
 If the file `/sdcard/linux/virglrenderer` exists on the device, it enables VirGL for VM.
diff --git a/docs/mainline_module_payload.md b/docs/mainline_module_payload.md
new file mode 100644
index 0000000..84617f0
--- /dev/null
+++ b/docs/mainline_module_payload.md
@@ -0,0 +1,19 @@
+# Delivery Microdroid pVM payload via Mainline modules
+
+There are several additional challenges when a Microdroid pVM payload is
+delivered inside a Mainline module.
+
+## Mainline rollbacks
+
+Mainline modules are expected to be rolled back on a device in case a problem
+with a Mainline release has been detected. This doesn't work well with the
+rollback protection of Microdroid pVMs - if a payload is updated, then a
+previous version of the payload is not allowed to access it's secrets.
+
+To work around this challenge, payloads delivered via Mainline modules are
+expected to request
+`android.permission.USE_RELAXED_MICRODROID_ROLLBACK_PROTECTION` privileged
+permission.
+
+TODO(ioffe): add more context on how permission is used once the implementation
+is done.
diff --git a/libs/bssl/src/err.rs b/libs/bssl/src/err.rs
index a53ac8c..f64baee 100644
--- a/libs/bssl/src/err.rs
+++ b/libs/bssl/src/err.rs
@@ -17,8 +17,8 @@
 use alloc::string::{String, ToString};
 use bssl_avf_error::{CipherError, EcError, EcdsaError, GlobalError, ReasonCode};
 use bssl_sys::{
-    self, ERR_get_error_line, ERR_lib_error_string, ERR_reason_error_string, ERR_GET_LIB_RUST,
-    ERR_GET_REASON_RUST,
+    self, ERR_get_error_line, ERR_lib_error_string, ERR_reason_error_string, ERR_GET_LIB,
+    ERR_GET_REASON,
 };
 use core::ffi::{c_char, CStr};
 use core::ptr;
@@ -102,13 +102,13 @@
 
 fn get_reason(packed_error: u32) -> i32 {
     // SAFETY: This function only reads the given error code.
-    unsafe { ERR_GET_REASON_RUST(packed_error) }
+    unsafe { ERR_GET_REASON(packed_error) }
 }
 
 /// Returns the library code for the error.
 fn get_lib(packed_error: u32) -> i32 {
     // SAFETY: This function only reads the given error code.
-    unsafe { ERR_GET_LIB_RUST(packed_error) }
+    unsafe { ERR_GET_LIB(packed_error) }
 }
 
 fn map_to_reason_code(reason: i32, lib: i32) -> ReasonCode {
diff --git a/libs/libvm_payload/Android.bp b/libs/libvm_payload/Android.bp
index 1ebbe39..0d99113 100644
--- a/libs/libvm_payload/Android.bp
+++ b/libs/libvm_payload/Android.bp
@@ -3,11 +3,10 @@
 }
 
 // The Rust implementation of the C API.
-rust_ffi_static {
-    name: "libvm_payload_impl",
+rust_ffi {
+    name: "libvm_payload",
     crate_name: "vm_payload",
     defaults: ["avf_build_flags_rust"],
-    visibility: ["//visibility:private"],
     srcs: ["src/lib.rs"],
     include_dirs: ["include"],
     prefer_rlib: true,
@@ -23,6 +22,20 @@
         "libvm_payload_status_bindgen",
         "libvsock",
     ],
+    shared_libs: [
+        "libbinder_ndk",
+        "libbinder_rpc_unstable",
+        "liblog",
+        "libcrypto",
+    ],
+    no_full_install: true,
+    version_script: "libvm_payload.map.txt",
+    stubs: {
+        symbol_file: "libvm_payload.map.txt",
+        // Implementation is available inside a Microdroid VM.
+        implementation_installable: false,
+    },
+    visibility: ["//visibility:public"],
 }
 
 rust_bindgen {
@@ -76,28 +89,6 @@
     visibility: ["//visibility:public"],
 }
 
-// Shared library for clients to link against.
-cc_library_shared {
-    name: "libvm_payload",
-    defaults: ["avf_build_flags_cc"],
-    shared_libs: [
-        "libbinder_ndk",
-        "libbinder_rpc_unstable",
-        "liblog",
-        "libcrypto",
-    ],
-    whole_static_libs: ["libvm_payload_impl"],
-    export_static_lib_headers: ["libvm_payload_impl"],
-    no_full_install: true,
-    version_script: "libvm_payload.map.txt",
-    stubs: {
-        symbol_file: "libvm_payload.map.txt",
-        // Implementation is available inside a Microdroid VM.
-        implementation_installable: false,
-    },
-    visibility: ["//visibility:public"],
-}
-
 // Just the headers. Mostly useful for clients that only want the
 // declaration of AVmPayload_main().
 cc_library_headers {
diff --git a/tests/aidl/com/android/microdroid/testservice/ITestService.aidl b/tests/aidl/com/android/microdroid/testservice/ITestService.aidl
index 6a413d6..4c824f0 100644
--- a/tests/aidl/com/android/microdroid/testservice/ITestService.aidl
+++ b/tests/aidl/com/android/microdroid/testservice/ITestService.aidl
@@ -104,4 +104,9 @@
      * @return true on the first boot of the instance & false on subsequent boot.
      */
     boolean isNewInstance();
+
+    /**
+     * Checks that libicu is accessible to the payload that has com.android.i18n APEX mounted.
+     */
+    void checkLibIcuIsAccessible();
 }
diff --git a/tests/testapk/Android.bp b/tests/testapk/Android.bp
index 99300e2..806592d 100644
--- a/tests/testapk/Android.bp
+++ b/tests/testapk/Android.bp
@@ -63,11 +63,22 @@
 
 DATA = [
     ":MicrodroidTestAppUpdated",
+    ":MicrodroidTestHelperAppRelaxedRollbackProtection_correct_V5",
     ":MicrodroidVmShareApp",
     ":test_microdroid_vendor_image",
     ":test_microdroid_vendor_image_unsigned",
 ]
 
+android_test_helper_app {
+    name: "MicrodroidTestHelperAppRelaxedRollbackProtection_correct_V5",
+    defaults: ["MicrodroidTestAppsDefaults"],
+    manifest: "AndroidManifestV5_relaxed_rollback_protection.xml",
+    jni_libs: [
+        "MicrodroidTestNativeLibWithLibIcu",
+    ],
+    min_sdk_version: "33",
+}
+
 android_test {
     name: "MicrodroidTestApp",
     defaults: ["MicrodroidVersionsTestAppDefaults"],
@@ -188,6 +199,17 @@
 }
 
 cc_library_shared {
+    name: "MicrodroidTestNativeLibWithLibIcu",
+    defaults: ["MicrodroidTestNativeLibDefaults"],
+    shared_libs: [
+        "libicu",
+    ],
+    cflags: [
+        "-D__MICRODROID_TEST_PAYLOAD_USES_LIBICU__",
+    ],
+}
+
+cc_library_shared {
     name: "MicrodroidTestNativeLibSub",
     defaults: ["avf_build_flags_cc"],
     srcs: ["src/native/testlib.cpp"],
diff --git a/tests/testapk/AndroidManifestV5.xml b/tests/testapk/AndroidManifestV5.xml
index 7d97680..2ef1b6b 100644
--- a/tests/testapk/AndroidManifestV5.xml
+++ b/tests/testapk/AndroidManifestV5.xml
@@ -18,10 +18,12 @@
       android:versionCode="5">
     <uses-permission android:name="android.permission.MANAGE_VIRTUAL_MACHINE" />
     <uses-permission android:name="android.permission.USE_CUSTOM_VIRTUAL_MACHINE" />
+    <uses-permission android:name="android.permission.USE_RELAXED_MICRODROID_ROLLBACK_PROTECTION" />
     <uses-sdk android:minSdkVersion="33" android:targetSdkVersion="33" />
     <uses-feature android:name="android.software.virtualization_framework" android:required="false" />
     <queries>
         <package android:name="com.android.microdroid.vmshare_app" />
+        <package android:name="com.android.microdroid.test_relaxed_rollback_protection_scheme" />
     </queries>
     <application />
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
diff --git a/tests/testapk/AndroidManifestV5_relaxed_rollback_protection.xml b/tests/testapk/AndroidManifestV5_relaxed_rollback_protection.xml
new file mode 100644
index 0000000..619d158
--- /dev/null
+++ b/tests/testapk/AndroidManifestV5_relaxed_rollback_protection.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+      package="com.android.microdroid.test_relaxed_rollback_protection_scheme"
+      android:versionCode="5">
+    <uses-permission android:name="android.permission.MANAGE_VIRTUAL_MACHINE" />
+    <uses-permission android:name="android.permission.USE_CUSTOM_VIRTUAL_MACHINE" />
+    <uses-permission android:name="android.permission.USE_RELAXED_MICRODROID_ROLLBACK_PROTECTION" />
+    <uses-sdk android:minSdkVersion="33" android:targetSdkVersion="33" />
+    <uses-feature android:name="android.software.virtualization_framework" android:required="false" />
+    <application />
+</manifest>
diff --git a/tests/testapk/AndroidManifestV6.xml b/tests/testapk/AndroidManifestV6.xml
index 19d5674..7dd0663 100644
--- a/tests/testapk/AndroidManifestV6.xml
+++ b/tests/testapk/AndroidManifestV6.xml
@@ -18,6 +18,7 @@
       android:versionCode="6">
     <uses-permission android:name="android.permission.MANAGE_VIRTUAL_MACHINE" />
     <uses-permission android:name="android.permission.USE_CUSTOM_VIRTUAL_MACHINE" />
+    <uses-permission android:name="android.permission.USE_RELAXED_MICRODROID_ROLLBACK_PROTECTION" />
     <uses-sdk android:minSdkVersion="33" android:targetSdkVersion="33" />
     <uses-feature android:name="android.software.virtualization_framework" android:required="false" />
     <queries>
diff --git a/tests/testapk/AndroidTestTemplate.xml b/tests/testapk/AndroidTestTemplate.xml
index 613ce28..6cdf984 100644
--- a/tests/testapk/AndroidTestTemplate.xml
+++ b/tests/testapk/AndroidTestTemplate.xml
@@ -21,6 +21,7 @@
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="test-file-name" value="{MODULE}.apk" />
         <option name="test-file-name" value="MicrodroidVmShareApp.apk" />
+        <option name="test-file-name" value="MicrodroidTestHelperAppRelaxedRollbackProtection_correct_V5.apk" />
     </target_preparer>
     <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
         <option name="run-command" value="mkdir -p /data/local/tmp/cts/microdroid" />
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 3ece140..b492684 100644
--- a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
+++ b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
@@ -130,6 +130,9 @@
     private static final String VM_ATTESTATION_MESSAGE = "Hello RKP from AVF!";
     private static final int ENCRYPTED_STORAGE_BYTES = 4_000_000;
 
+    private static final String USE_RELAXED_MICRODROID_ROLLBACK_PROTECTION_PERMISSION =
+            "android.permission.USE_RELAXED_MICRODROID_ROLLBACK_PROTECTION";
+
     @Rule public Timeout globalTimeout = Timeout.seconds(300);
 
     @Parameterized.Parameters(name = "protectedVm={0},os={1}")
@@ -163,15 +166,19 @@
             // Tests that rely on the state of the permission should explicitly grant or revoke it.
             revokePermission(VirtualMachine.USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION);
         }
+        revokePermission(USE_RELAXED_MICRODROID_ROLLBACK_PROTECTION_PERMISSION);
     }
 
     @After
     public void tearDown() {
         revokePermission(VirtualMachine.USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION);
+        revokePermission(USE_RELAXED_MICRODROID_ROLLBACK_PROTECTION_PERMISSION);
     }
     private static final String EXAMPLE_STRING = "Literally any string!! :)";
 
     private static final String VM_SHARE_APP_PACKAGE_NAME = "com.android.microdroid.vmshare_app";
+    private static final String RELAXED_ROLLBACK_PROTECTION_SCHEME_PACKAGE_NAME =
+            "com.android.microdroid.test_relaxed_rollback_protection_scheme";
 
     private void createAndConnectToVmHelper(int cpuTopology, boolean shouldUseHugepages)
             throws Exception {
@@ -2747,6 +2754,39 @@
         assertThat(testResults.mPageSize).isEqualTo(expectedPageSize);
     }
 
+    @Test
+    public void libIcuIsLoadable() throws Exception {
+        assumeSupportedDevice();
+
+        // This test relies on the test apk having USE_RELAXED_MICRODROID_ROLLBACK_PROTECTION
+        // permission.
+        grantPermission(USE_RELAXED_MICRODROID_ROLLBACK_PROTECTION_PERMISSION);
+
+        Context otherAppCtx =
+                getContext()
+                        .createPackageContext(RELAXED_ROLLBACK_PROTECTION_SCHEME_PACKAGE_NAME, 0);
+        VirtualMachineConfig config =
+                new VirtualMachineConfig.Builder(otherAppCtx)
+                        .setDebugLevel(DEBUG_LEVEL_FULL)
+                        .setPayloadBinaryName("MicrodroidTestNativeLibWithLibIcu.so")
+                        .setProtectedVm(isProtectedVm())
+                        .setOs(os())
+                        .build();
+
+        VirtualMachine vm = forceCreateNewVirtualMachine("test_libicu_is_loadable", config);
+
+        TestResults testResults =
+                runVmTestService(
+                        TAG,
+                        vm,
+                        (ts, tr) -> {
+                            ts.checkLibIcuIsAccessible();
+                        });
+
+        // checkLibIcuIsAccessible will throw an exception if something goes wrong.
+        assertThat(testResults.mException).isNull();
+    }
+
     private static class VmShareServiceConnection implements ServiceConnection {
 
         private final CountDownLatch mLatch = new CountDownLatch(1);
diff --git a/tests/testapk/src/native/testbinary.cpp b/tests/testapk/src/native/testbinary.cpp
index 2ab73a4..355cfb1 100644
--- a/tests/testapk/src/native/testbinary.cpp
+++ b/tests/testapk/src/native/testbinary.cpp
@@ -30,6 +30,9 @@
 #include <stdio.h>
 #include <sys/capability.h>
 #include <sys/system_properties.h>
+#ifdef __MICRODROID_TEST_PAYLOAD_USES_LIBICU__
+#include <unicode/uchar.h>
+#endif
 #include <unistd.h>
 #include <vm_main.h>
 #include <vm_payload_restricted.h>
@@ -384,6 +387,29 @@
             }
         }
 
+        ScopedAStatus checkLibIcuIsAccessible() override {
+#ifdef __MICRODROID_TEST_PAYLOAD_USES_LIBICU__
+            static constexpr const char* kLibIcuPath = "/apex/com.android.i18n/lib64/libicu.so";
+            if (access(kLibIcuPath, R_OK) == 0) {
+                if (!u_hasBinaryProperty(U'❤' /* Emoji heart U+2764 */, UCHAR_EMOJI)) {
+                    return ScopedAStatus::fromExceptionCodeWithMessage(EX_SERVICE_SPECIFIC,
+                                                                       "libicu broken!");
+                }
+                return ScopedAStatus::ok();
+            } else {
+                std::string msg = "failed to access " + std::string(kLibIcuPath) + "(" +
+                        std::to_string(errno) + ")";
+                return ScopedAStatus::fromExceptionCodeWithMessage(EX_SERVICE_SPECIFIC,
+                                                                   msg.c_str());
+            }
+#else
+            return ScopedAStatus::
+                    fromExceptionCodeWithMessage(EX_SERVICE_SPECIFIC,
+                                                 "should be only used together with "
+                                                 "MicrodroidTestNativeLibWithLibIcu.so payload");
+#endif
+        }
+
         ScopedAStatus quit() override { exit(0); }
     };
     auto testService = ndk::SharedRefBase::make<TestService>();
diff --git a/tests/testapk/src/native/testbinary.rs b/tests/testapk/src/native/testbinary.rs
index c9d46b8..3900cad 100644
--- a/tests/testapk/src/native/testbinary.rs
+++ b/tests/testapk/src/native/testbinary.rs
@@ -135,6 +135,9 @@
     fn isNewInstance(&self) -> BinderResult<bool> {
         unimplemented()
     }
+    fn checkLibIcuIsAccessible(&self) -> BinderResult<()> {
+        unimplemented()
+    }
 }
 
 fn unimplemented<T>() -> BinderResult<T> {
diff --git a/tests/vmshareapp/src/java/com/android/microdroid/test/sharevm/VmShareServiceImpl.java b/tests/vmshareapp/src/java/com/android/microdroid/test/sharevm/VmShareServiceImpl.java
index 1f71888..7f44fa5 100644
--- a/tests/vmshareapp/src/java/com/android/microdroid/test/sharevm/VmShareServiceImpl.java
+++ b/tests/vmshareapp/src/java/com/android/microdroid/test/sharevm/VmShareServiceImpl.java
@@ -291,5 +291,10 @@
         public boolean isNewInstance() {
             throw new UnsupportedOperationException("Not supported");
         }
+
+        @Override
+        public void checkLibIcuIsAccessible() {
+            throw new UnsupportedOperationException("Not supported");
+        }
     }
 }