Merge "Revert^2 "libvm_payload: Use Rust stubs instead of cc"" 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/TerminalApp/res/values-az/strings.xml b/android/TerminalApp/res/values-az/strings.xml
index 94edf81..b3f45fc 100644
--- a/android/TerminalApp/res/values-az/strings.xml
+++ b/android/TerminalApp/res/values-az/strings.xml
@@ -33,7 +33,7 @@
     <string name="installer_error_no_wifi" msgid="1180164894845030969">"Wi-Fi əlçatan olmadığı üçün quraşdırmaq alınmadı"</string>
     <string name="installer_error_unknown" msgid="5657920711470180224">"Quraşdırmaq alınmadı. Yenidən cəhd edin"</string>
     <string name="action_settings" msgid="5729342767795123227">"Ayarlar"</string>
-    <string name="action_display" msgid="8487008779926038139">"Displey"</string>
+    <string name="action_display" msgid="8487008779926038139">"Göstərin"</string>
     <string name="vm_creation_message" msgid="6594953532721367502">"Terminal hazırlanır"</string>
     <string name="vm_stop_message" msgid="3978349856095529255">"Terminal dayandırılır"</string>
     <string name="vm_error_message" msgid="5231867246177661525">"Terminal çökdü"</string>
diff --git a/android/TerminalApp/res/values-in/strings.xml b/android/TerminalApp/res/values-in/strings.xml
index a1fe894..b114246 100644
--- a/android/TerminalApp/res/values-in/strings.xml
+++ b/android/TerminalApp/res/values-in/strings.xml
@@ -33,7 +33,7 @@
     <string name="installer_error_no_wifi" msgid="1180164894845030969">"Gagal menginstal karena Wi-Fi tidak tersedia"</string>
     <string name="installer_error_unknown" msgid="5657920711470180224">"Gagal menginstal. Coba lagi"</string>
     <string name="action_settings" msgid="5729342767795123227">"Setelan"</string>
-    <string name="action_display" msgid="8487008779926038139">"Tampilan"</string>
+    <string name="action_display" msgid="8487008779926038139">"Layar"</string>
     <string name="vm_creation_message" msgid="6594953532721367502">"Menyiapkan terminal"</string>
     <string name="vm_stop_message" msgid="3978349856095529255">"Menghentikan terminal"</string>
     <string name="vm_error_message" msgid="5231867246177661525">"Terminal error"</string>
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/android/virtmgr/src/crosvm.rs b/android/virtmgr/src/crosvm.rs
index df53bc6..00858cb 100644
--- a/android/virtmgr/src/crosvm.rs
+++ b/android/virtmgr/src/crosvm.rs
@@ -1105,7 +1105,7 @@
     #[cfg(target_arch = "aarch64")]
     command
         .arg("--pci")
-        .arg("mem=[start=0x70000000,size=0x2000000],cam=[start=0x72000000,size=0x1000000]");
+        .arg("mem=[start=0x2c000000,size=0x2000000],cam=[start=0x2e000000,size=0x1000000]");
 
     command.arg("--mem").arg(memory_mib.to_string());
 
diff --git a/build/apex/Android.bp b/build/apex/Android.bp
index 946bc8c..20f44fe 100644
--- a/build/apex/Android.bp
+++ b/build/apex/Android.bp
@@ -149,6 +149,12 @@
             "microdroid_gki-android15-6.6_kernel",
             "microdroid_gki-android15-6.6.json",
         ],
+        "android16_612": [
+            "microdroid_gki-android16-6.12_initrd_debuggable",
+            "microdroid_gki-android16-6.12_initrd_normal",
+            "microdroid_gki-android16-6.12_kernel",
+            "microdroid_gki-android16-6.12.json",
+        ],
         default: [],
     }) + select(release_flag("RELEASE_AVF_ENABLE_DEVICE_ASSIGNMENT"), {
         true: ["com.android.virt.vfio_handler.rc"],
diff --git a/build/apex/sign_virt_apex.py b/build/apex/sign_virt_apex.py
index e042f8d..57961a4 100644
--- a/build/apex/sign_virt_apex.py
+++ b/build/apex/sign_virt_apex.py
@@ -498,7 +498,7 @@
     RunCommand(args, cmd)
 
 
-gki_versions = ['android15-6.6']
+gki_versions = ['android15-6.6', 'android16-6.12']
 
 # dict of (key, file) for re-sign/verification. keys are un-versioned for readability.
 virt_apex_non_gki_files = {
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/debian/kokoro/gcp_ubuntu_docker/aarch64/build.sh b/build/debian/kokoro/gcp_ubuntu_docker/aarch64/build.sh
index 43f0338..3868d63 100644
--- a/build/debian/kokoro/gcp_ubuntu_docker/aarch64/build.sh
+++ b/build/debian/kokoro/gcp_ubuntu_docker/aarch64/build.sh
@@ -2,6 +2,17 @@
 
 set -e
 
+# pyenv set up to use python 3.11
+sudo apt update; sudo apt install --no-install-recommends --assume-yes \
+  build-essential libssl-dev zlib1g-dev \
+  libbz2-dev libreadline-dev libsqlite3-dev curl git \
+  libncursesw5-dev xz-utils tk-dev libxml2-dev libxmlsec1-dev libffi-dev liblzma-dev
+git clone https://github.com/pyenv/pyenv.git ~/.pyenv
+export PYENV_ROOT="$HOME/.pyenv"
+export PATH="$PYENV_ROOT/bin:$PATH"
+pyenv install 3.11
+pyenv global 3.11
+
 cd "${KOKORO_ARTIFACTS_DIR}/git/avf/build/debian/"
 sudo losetup -D
 grep vmx /proc/cpuinfo || true
diff --git a/build/debian/kokoro/gcp_ubuntu_docker/x86_64/build.sh b/build/debian/kokoro/gcp_ubuntu_docker/x86_64/build.sh
index a935591..1712c24 100644
--- a/build/debian/kokoro/gcp_ubuntu_docker/x86_64/build.sh
+++ b/build/debian/kokoro/gcp_ubuntu_docker/x86_64/build.sh
@@ -2,6 +2,17 @@
 
 set -e
 
+# pyenv set up to use python 3.11
+sudo apt update; sudo apt install --no-install-recommends --assume-yes \
+  build-essential libssl-dev zlib1g-dev \
+  libbz2-dev libreadline-dev libsqlite3-dev curl git \
+  libncursesw5-dev xz-utils tk-dev libxml2-dev libxmlsec1-dev libffi-dev liblzma-dev
+git clone https://github.com/pyenv/pyenv.git ~/.pyenv
+export PYENV_ROOT="$HOME/.pyenv"
+export PATH="$PYENV_ROOT/bin:$PATH"
+pyenv install 3.11
+pyenv global 3.11
+
 cd "${KOKORO_ARTIFACTS_DIR}/git/avf/build/debian/"
 sudo losetup -D
 grep vmx /proc/cpuinfo || true
diff --git a/build/debian/vm_config.json.x86_64 b/build/debian/vm_config.json.x86_64
index 496e684..c34a0f2 100644
--- a/build/debian/vm_config.json.x86_64
+++ b/build/debian/vm_config.json.x86_64
@@ -43,5 +43,6 @@
     "debuggable": true,
     "console_out": true,
     "console_input_device": "ttyS0",
-    "network": true
+    "network": true,
+    "auto_memory_balloon": true
 }
diff --git a/build/microdroid/Android.bp b/build/microdroid/Android.bp
index dea0bf3..059077a 100644
--- a/build/microdroid/Android.bp
+++ b/build/microdroid/Android.bp
@@ -633,7 +633,7 @@
 }
 
 avb_add_hash_footer_defaults {
-    name: "microdroid_gki_kernel_signed_defaults",
+    name: "microdroid_gki-android15-6.6_kernel_signed_defaults",
     defaults: ["microdroid_kernel_signed_defaults"],
     arch: {
         arm64: {
@@ -652,7 +652,7 @@
 avb_add_hash_footer {
     name: "microdroid_gki-android15-6.6_kernel_signed",
     defaults: [
-        "microdroid_gki_kernel_signed_defaults",
+        "microdroid_gki-android15-6.6_kernel_signed_defaults",
         "microdroid_kernel_cap_defaults",
     ],
     filename: "microdroid_gki-android15-6.6_kernel_signed",
@@ -661,7 +661,7 @@
 avb_add_hash_footer {
     name: "microdroid_gki-android15-6.6_kernel_signed_supports_uefi_boot",
     defaults: [
-        "microdroid_gki_kernel_signed_defaults",
+        "microdroid_gki-android15-6.6_kernel_signed_defaults",
         "microdroid_kernel_cap_with_uefi_defaults",
     ],
     filename: "microdroid_gki-android15-6.6_kernel_signed_supports_uefi_boot",
@@ -709,6 +709,91 @@
     src: ":microdroid_gki-android15-6.6_initrd_debuggable",
 }
 
+///////////////////////////////////////
+// GKI-android16-6.12
+///////////////////////////////////////
+prebuilt_etc {
+    name: "microdroid_gki-android16-6.12.json",
+    src: "microdroid_gki-android16-6.12.json",
+}
+
+avb_add_hash_footer_defaults {
+    name: "microdroid_gki-android16-6.12_kernel_signed_defaults",
+    defaults: ["microdroid_kernel_signed_defaults"],
+    arch: {
+        arm64: {
+            src: ":microdroid_gki_kernel_prebuilts-android16-6.12-arm64",
+        },
+        x86_64: {
+            src: ":microdroid_gki_kernel_prebuilts-android16-6.12-x86_64",
+        },
+    },
+    include_descriptors_from_images: [
+        ":microdroid_gki-android16-6.12_initrd_normal_hashdesc",
+        ":microdroid_gki-android16-6.12_initrd_debug_hashdesc",
+    ],
+}
+
+avb_add_hash_footer {
+    name: "microdroid_gki-android16-6.12_kernel_signed",
+    defaults: [
+        "microdroid_gki-android16-6.12_kernel_signed_defaults",
+        "microdroid_kernel_cap_defaults",
+    ],
+    filename: "microdroid_gki-android16-6.12_kernel_signed",
+}
+
+avb_add_hash_footer {
+    name: "microdroid_gki-android16-6.12_kernel_signed_supports_uefi_boot",
+    defaults: [
+        "microdroid_gki-android16-6.12_kernel_signed_defaults",
+        "microdroid_kernel_cap_with_uefi_defaults",
+    ],
+    filename: "microdroid_gki-android16-6.12_kernel_signed_supports_uefi_boot",
+}
+
+// HACK: use cc_genrule for arch-specific properties
+cc_genrule {
+    name: "microdroid_gki-android16-6.12_kernel_signed-lz4",
+    out: ["microdroid_gki-android16-6.12_kernel_signed-lz4"],
+    srcs: [":empty_file"],
+    arch: {
+        arm64: {
+            srcs: [":microdroid_gki-android16-6.12_kernel_signed"],
+            exclude_srcs: [":empty_file"],
+        },
+    },
+    tools: ["lz4"],
+    cmd: "$(location lz4) -9 $(in) $(out)",
+}
+
+prebuilt_etc {
+    name: "microdroid_gki-android16-6.12_kernel",
+    filename: "microdroid_gki-android16-6.12_kernel",
+    src: ":empty_file",
+    relative_install_path: "fs",
+    arch: {
+        arm64: {
+            src: ":microdroid_gki-android16-6.12_kernel_signed",
+        },
+        x86_64: {
+            src: ":microdroid_gki-android16-6.12_kernel_signed",
+        },
+    },
+}
+
+avb_gen_vbmeta_image {
+    name: "microdroid_gki-android16-6.12_initrd_normal_hashdesc",
+    defaults: ["microdroid_initrd_normal_defaults"],
+    src: ":microdroid_gki-android16-6.12_initrd_normal",
+}
+
+avb_gen_vbmeta_image {
+    name: "microdroid_gki-android16-6.12_initrd_debug_hashdesc",
+    defaults: ["microdroid_initrd_debug_defaults"],
+    src: ":microdroid_gki-android16-6.12_initrd_debuggable",
+}
+
 python_binary_host {
     name: "extract_microdroid_kernel_hashes",
     srcs: ["extract_microdroid_kernel_hashes.py"],
@@ -723,11 +808,13 @@
         arm64: {
             srcs: [
                 ":microdroid_gki-android15-6.6_kernel_signed",
+                ":microdroid_gki-android16-6.12_kernel_signed",
             ],
         },
         x86_64: {
             srcs: [
                 ":microdroid_gki-android15-6.6_kernel_signed",
+                ":microdroid_gki-android16-6.12_kernel_signed",
             ],
         },
     },
diff --git a/build/microdroid/bootconfig.arm64 b/build/microdroid/bootconfig.arm64
index 7509a2c..f257c81 100644
--- a/build/microdroid/bootconfig.arm64
+++ b/build/microdroid/bootconfig.arm64
@@ -1 +1 @@
-androidboot.boot_devices = 72000000.pci
+androidboot.boot_devices = 2e000000.pci
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/build/microdroid/initrd/Android.bp b/build/microdroid/initrd/Android.bp
index 7331e0b..d9aa108 100644
--- a/build/microdroid/initrd/Android.bp
+++ b/build/microdroid/initrd/Android.bp
@@ -52,6 +52,17 @@
 }
 
 java_genrule {
+    name: "microdroid_gki-android16-6.12_initrd_gen_arm64",
+    srcs: [
+        ":microdroid_ramdisk",
+        ":microdroid_first_stage_ramdisk",
+        ":microdroid_gki_modules-android16-6.12-arm64",
+    ],
+    out: ["microdroid_initrd.img"],
+    cmd: "cat $(in) > $(out)",
+}
+
+java_genrule {
     name: "microdroid_gki-android15-6.6_initrd_gen_x86_64",
     srcs: [
         ":microdroid_ramdisk",
@@ -62,6 +73,17 @@
     cmd: "cat $(in) > $(out)",
 }
 
+java_genrule {
+    name: "microdroid_gki-android16-6.12_initrd_gen_x86_64",
+    srcs: [
+        ":microdroid_ramdisk",
+        ":microdroid_first_stage_ramdisk",
+        ":microdroid_gki_modules-android16-6.12-x86_64",
+    ],
+    out: ["microdroid_initrd.img"],
+    cmd: "cat $(in) > $(out)",
+}
+
 // This contains vbmeta hashes & related (boot)configs which are passed to kernel/init
 java_genrule {
     name: "microdroid_vbmeta_bootconfig_gen",
@@ -111,6 +133,17 @@
 }
 
 java_genrule {
+    name: "microdroid_gki-android16-6.12_initrd_debuggable_arm64",
+    tools: ["initrd_bootconfig"],
+    srcs: [
+        ":microdroid_gki-android16-6.12_initrd_gen_arm64",
+        ":microdroid_bootconfig_debuggable_src",
+    ] + bootconfigs_arm64,
+    out: ["microdroid_gki-android16-6.12_initrd_debuggable_arm64"],
+    cmd: "$(location initrd_bootconfig) attach --output $(out) $(in)",
+}
+
+java_genrule {
     name: "microdroid_initrd_debuggable_x86_64",
     tools: ["initrd_bootconfig"],
     srcs: [
@@ -144,6 +177,17 @@
 }
 
 java_genrule {
+    name: "microdroid_gki-android16-6.12_initrd_debuggable_x86_64",
+    tools: ["initrd_bootconfig"],
+    srcs: [
+        ":microdroid_gki-android16-6.12_initrd_gen_x86_64",
+        ":microdroid_bootconfig_debuggable_src",
+    ] + bootconfigs_x86_64,
+    out: ["microdroid_gki-android16-6.12_initrd_debuggable_x86_64"],
+    cmd: "$(location initrd_bootconfig) attach --output $(out) $(in)",
+}
+
+java_genrule {
     name: "microdroid_initrd_normal_arm64",
     tools: ["initrd_bootconfig"],
     srcs: [
@@ -166,6 +210,17 @@
 }
 
 java_genrule {
+    name: "microdroid_gki-android16-6.12_initrd_normal_arm64",
+    tools: ["initrd_bootconfig"],
+    srcs: [
+        ":microdroid_gki-android16-6.12_initrd_gen_arm64",
+        ":microdroid_bootconfig_normal_src",
+    ] + bootconfigs_arm64,
+    out: ["microdroid_gki-android16-6.12_initrd_normal_arm64"],
+    cmd: "$(location initrd_bootconfig) attach --output $(out) $(in)",
+}
+
+java_genrule {
     name: "microdroid_initrd_normal_x86_64",
     tools: ["initrd_bootconfig"],
     srcs: [
@@ -198,6 +253,17 @@
     cmd: "$(location initrd_bootconfig) attach --output $(out) $(in)",
 }
 
+java_genrule {
+    name: "microdroid_gki-android16-6.12_initrd_normal_x86_64",
+    tools: ["initrd_bootconfig"],
+    srcs: [
+        ":microdroid_gki-android16-6.12_initrd_gen_x86_64",
+        ":microdroid_bootconfig_normal_src",
+    ] + bootconfigs_x86_64,
+    out: ["microdroid_gki-android16-6.12_initrd_normal_x86_64"],
+    cmd: "$(location initrd_bootconfig) attach --output $(out) $(in)",
+}
+
 prebuilt_etc {
     name: "microdroid_initrd_debuggable",
     // We don't have ramdisk for architectures other than x86_64 & arm64
@@ -247,6 +313,21 @@
 }
 
 prebuilt_etc {
+    name: "microdroid_gki-android16-6.12_initrd_debuggable",
+    // We don't have ramdisk for architectures other than x86_64 & arm64
+    src: ":empty_file",
+    arch: {
+        arm64: {
+            src: ":microdroid_gki-android16-6.12_initrd_debuggable_arm64",
+        },
+        x86_64: {
+            src: ":microdroid_gki-android16-6.12_initrd_debuggable_x86_64",
+        },
+    },
+    filename: "microdroid_gki-android16-6.12_initrd_debuggable.img",
+}
+
+prebuilt_etc {
     name: "microdroid_initrd_normal",
     // We don't have ramdisk for architectures other than x86_64 & arm64
     src: ":empty_file",
@@ -293,3 +374,18 @@
     },
     filename: "microdroid_gki-android15-6.6_initrd_normal.img",
 }
+
+prebuilt_etc {
+    name: "microdroid_gki-android16-6.12_initrd_normal",
+    // We don't have ramdisk for architectures other than x86_64 & arm64
+    src: ":empty_file",
+    arch: {
+        arm64: {
+            src: ":microdroid_gki-android16-6.12_initrd_normal_arm64",
+        },
+        x86_64: {
+            src: ":microdroid_gki-android16-6.12_initrd_normal_x86_64",
+        },
+    },
+    filename: "microdroid_gki-android16-6.12_initrd_normal.img",
+}
diff --git a/build/microdroid/microdroid_gki-android16-6.12.json b/build/microdroid/microdroid_gki-android16-6.12.json
new file mode 100644
index 0000000..57adb24
--- /dev/null
+++ b/build/microdroid/microdroid_gki-android16-6.12.json
@@ -0,0 +1,20 @@
+{
+  "kernel": "/apex/com.android.virt/etc/fs/microdroid_gki-android16-6.12_kernel",
+  "disks": [
+    {
+      "partitions": [
+        {
+          "label": "vbmeta_a",
+          "path": "/apex/com.android.virt/etc/fs/microdroid_vbmeta.img"
+        },
+        {
+          "label": "super",
+          "path": "/apex/com.android.virt/etc/fs/microdroid_super.img"
+        }
+      ],
+      "writable": false
+    }
+  ],
+  "memory_mib": 256,
+  "platform_version": "~1.0"
+}
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/guest/forwarder_guest_launcher/src/main.rs b/guest/forwarder_guest_launcher/src/main.rs
index 963a531..f4c8ca9 100644
--- a/guest/forwarder_guest_launcher/src/main.rs
+++ b/guest/forwarder_guest_launcher/src/main.rs
@@ -36,14 +36,12 @@
 
 const NON_PREVILEGED_PORT_RANGE_START: i32 = 1024;
 const TTYD_PORT: i32 = 7681;
-const TCPSTATES_IP_4: i8 = 4;
 const TCPSTATES_STATE_CLOSE: &str = "CLOSE";
 const TCPSTATES_STATE_LISTEN: &str = "LISTEN";
 
 #[derive(Debug, Deserialize)]
 #[serde(rename_all = "UPPERCASE")]
 struct TcpStateRow {
-    ip: i8,
     lport: i32,
     rport: i32,
     #[serde(alias = "C-COMM")]
@@ -127,10 +125,8 @@
 
     // TODO(b/340126051): Consider using NETLINK_SOCK_DIAG for the optimization.
     let listeners = listeners::get_all()?;
-    // TODO(b/340126051): Support distinguished port forwarding for ipv6 as well.
     let mut listening_ports: HashMap<_, _> = listeners
         .iter()
-        .filter(|x| x.socket.is_ipv4())
         .map(|x| {
             (
                 x.socket.port().into(),
@@ -144,9 +140,6 @@
     let mut records = csv_reader.records();
     while let Some(record) = records.next().await {
         let row: TcpStateRow = record?.deserialize(Some(&header))?;
-        if row.ip != TCPSTATES_IP_4 {
-            continue;
-        }
         if !is_forwardable_port(row.lport) {
             continue;
         }
diff --git a/guest/microdroid_manager/src/dice.rs b/guest/microdroid_manager/src/dice.rs
index bf89358..dd5375f 100644
--- a/guest/microdroid_manager/src/dice.rs
+++ b/guest/microdroid_manager/src/dice.rs
@@ -153,7 +153,11 @@
             subcomponents.into_iter().map(Subcomponent::into_value).collect::<Result<Vec<_>>>()?;
         map.push((cbor!(-71002)?, cbor!(values)?));
     }
-    // Add a placeholder security version as it is required by the open-dice profile "Android.16"
+    // Add a placeholder security version as it is required by the open-dice profile "Android.16".
+    // Note: The DICE certificate derived in microdroid_manager primarily describes the APKs/APEXs
+    // loaded by microdroid_manager. Each APK/APEX is described separately with its own security
+    // version as a subcomponent within the certificate's config descriptor.
+    // Therefore, the global security version below (for the entire certificate) is unused.
     map.push((cbor!(-70005)?, cbor!(0)?));
     Ok(Value::Map(map).to_vec()?)
 }
diff --git a/guest/pvmfw/platform.dts b/guest/pvmfw/platform.dts
index 643a5e4..653a70f 100644
--- a/guest/pvmfw/platform.dts
+++ b/guest/pvmfw/platform.dts
@@ -355,11 +355,11 @@
 		dma-coherent;
 		memory-region = <&swiotlb>;
 		ranges = <
-			0x3000000 0x0 0x70000000 0x0 0x70000000 0x00 0x02000000
+			0x3000000 0x0 0x2c000000 0x0 0x2c000000 0x00 0x02000000
 			0x3000000 PLACEHOLDER2   PLACEHOLDER2   PLACEHOLDER2
 		>;
 		bus-range = <0x00 0x00>;
-		reg = <0x00 0x72000000 0x00 0x1000000>;
+		reg = <0x00 0x2e000000 0x00 0x1000000>;
 		interrupt-map = <
 			0x0800 0x0 0x0 1 &intc 0 0 GIC_SPI (IRQ_BASE + 0) IRQ_TYPE_LEVEL_HIGH
 			0x1000 0x0 0x0 1 &intc 0 0 GIC_SPI (IRQ_BASE + 1) IRQ_TYPE_LEVEL_HIGH
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/libavf/include/android/virtualization.h b/libs/libavf/include/android/virtualization.h
index ef57325..8d96fac 100644
--- a/libs/libavf/include/android/virtualization.h
+++ b/libs/libavf/include/android/virtualization.h
@@ -357,7 +357,7 @@
  * For a graceful shutdown, you could request the virtual machine to exit itself, and wait for the
  * virtual machine to stop by `AVirtualMachine_waitForStop`.
  *
- * A stopped virtual machine can be re-started by calling `AVirtualMachine_start`.
+ * A stopped virtual machine cannot be re-started.
  *
  * `AVirtualMachine_stop` stops a virtual machine by sending a signal to the process. This operation
  * is synchronous and `AVirtualMachine_stop` may block.
diff --git a/libs/libvm_payload/include/vm_payload.h b/libs/libvm_payload/include/vm_payload.h
index e4609fa..a3bb577 100644
--- a/libs/libvm_payload/include/vm_payload.h
+++ b/libs/libvm_payload/include/vm_payload.h
@@ -58,7 +58,7 @@
 typedef enum AVmAccessRollbackProtectedSecretStatus : int32_t {
     /**
      * Relevant Entry not found. This can happen either due to no value was ever written or because
-     * Android maliciously deleted the value (deletions may not be authenticated).
+     * it was deleted by host.
      */
     AVMACCESSROLLBACKPROTECTEDSECRETSTATUS_ENTRY_NOT_FOUND = -1,
     /** Requested access size is not supported by the implementation */
@@ -282,9 +282,9 @@
         __INTRODUCED_IN(__ANDROID_API_V__);
 /**
  * Writes up to n bytes from buffer starting at `buf`, on behalf of the payload, to rollback
- * detectable storage. The number of bytes written may be less than n if, for example, the
- * underlying storage has size constraints. This stored data is confidential to the pVM and
- * protected via appropriate DICE policy on the payload's DICE chain.
+ * detectable storage. The data is written from the start. The number of bytes written may be less
+ * than n if, for example, the underlying storage has size constraints. This stored data is
+ * confidential to the VM instance.
  *
  * \param buf A pointer to data to be written. This should have the size of at least n bytes.
  * \param n The maximum number of bytes to be filled in `buf`.
@@ -296,7 +296,7 @@
 int32_t AVmPayload_writeRollbackProtectedSecret(const void* _Nonnull buf, size_t n)
         __INTRODUCED_IN(36);
 /**
- * Read up to n bytes of payload's data in rollback detectable storage into `buf`.
+ * Read the first n bytes of payload's data in rollback detectable storage into `buf`.
  *
  * \param buf A pointer to buffer where the requested data is written. This should have the size of
  * at least n bytes.
@@ -307,7 +307,6 @@
  * number) is returned.
  */
 int32_t AVmPayload_readRollbackProtectedSecret(void* _Nullable buf, size_t n) __INTRODUCED_IN(36);
-;
 
 /**
  * Checks whether the VM instance is new - i.e., if this is the first run of an instance.
@@ -316,6 +315,6 @@
  *
  *  \return true if this is the first run of an instance, false otherwise.
  */
-bool AVmPayload_isNewInstance() __INTRODUCED_IN(36);
+bool AVmPayload_isNewInstance(void) __INTRODUCED_IN(36);
 
 __END_DECLS
diff --git a/tests/aidl/com/android/microdroid/testservice/IBenchmarkService.aidl b/tests/aidl/com/android/microdroid/testservice/IBenchmarkService.aidl
index 4d043f6..ca752e1 100644
--- a/tests/aidl/com/android/microdroid/testservice/IBenchmarkService.aidl
+++ b/tests/aidl/com/android/microdroid/testservice/IBenchmarkService.aidl
@@ -27,6 +27,13 @@
      */
     double measureReadRate(String filename, boolean isRand);
 
+    /**
+     * Measures the write rate for writing the given file, creating the file if necessary
+     *
+     * @return The write rate in MB/s.
+     */
+    double measureWriteRate(String filename, long size_bytes);
+
     /** Returns an entry from /proc/meminfo. */
     long getMemInfoEntry(String name);
 
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/backcompat_test/goldens/dt_dump_golden.dts b/tests/backcompat_test/goldens/dt_dump_golden.dts
index a583514..99b2cff 100644
--- a/tests/backcompat_test/goldens/dt_dump_golden.dts
+++ b/tests/backcompat_test/goldens/dt_dump_golden.dts
@@ -102,8 +102,8 @@
 		dma-coherent;
 		interrupt-map = <0x800 0x00 0x00 0x01 0x01 0x00 0x00 0x00 0x04 0x04 0x1000 0x00 0x00 0x01 0x01 0x00 0x00 0x00 0x05 0x04 0x1800 0x00 0x00 0x01 0x01 0x00 0x00 0x00 0x06 0x04 0x2000 0x00 0x00 0x01 0x01 0x00 0x00 0x00 0x07 0x04 0x2800 0x00 0x00 0x01 0x01 0x00 0x00 0x00 0x08 0x04 0x3000 0x00 0x00 0x01 0x01 0x00 0x00 0x00 0x09 0x04 0x3800 0x00 0x00 0x01 0x01 0x00 0x00 0x00 0x0a 0x04 0x4000 0x00 0x00 0x01 0x01 0x00 0x00 0x00 0x0b 0x04>;
 		interrupt-map-mask = <0xf800 0x00 0x00 0x07 0xf800 0x00 0x00 0x07 0xf800 0x00 0x00 0x07 0xf800 0x00 0x00 0x07 0xf800 0x00 0x00 0x07 0xf800 0x00 0x00 0x07 0xf800 0x00 0x00 0x07 0xf800 0x00 0x00 0x07>;
-		ranges = <0x3000000 0x00 0x70000000 0x00 0x70000000 0x00 0x2000000 0x43000000 0x00 0x93400000 0x00 0x93400000 0xff 0x6cc00000>;
-		reg = <0x00 0x72000000 0x00 0x1000000>;
+		ranges = <0x3000000 0x00 0x2c000000 0x00 0x2c000000 0x00 0x2000000 0x43000000 0x00 0x93400000 0x00 0x93400000 0xff 0x6cc00000>;
+		reg = <0x00 0x2e000000 0x00 0x1000000>;
 	};
 
 	pclk@3M {
diff --git a/tests/backcompat_test/goldens/dt_dump_protected_golden.dts b/tests/backcompat_test/goldens/dt_dump_protected_golden.dts
index 656958d..7cea489 100644
--- a/tests/backcompat_test/goldens/dt_dump_protected_golden.dts
+++ b/tests/backcompat_test/goldens/dt_dump_protected_golden.dts
@@ -103,8 +103,8 @@
 		interrupt-map = <0x800 0x00 0x00 0x01 0x01 0x00 0x00 0x00 0x04 0x04 0x1000 0x00 0x00 0x01 0x01 0x00 0x00 0x00 0x05 0x04 0x1800 0x00 0x00 0x01 0x01 0x00 0x00 0x00 0x06 0x04 0x2000 0x00 0x00 0x01 0x01 0x00 0x00 0x00 0x07 0x04 0x2800 0x00 0x00 0x01 0x01 0x00 0x00 0x00 0x08 0x04 0x3000 0x00 0x00 0x01 0x01 0x00 0x00 0x00 0x09 0x04 0x3800 0x00 0x00 0x01 0x01 0x00 0x00 0x00 0x0a 0x04>;
 		interrupt-map-mask = <0xf800 0x00 0x00 0x07 0xf800 0x00 0x00 0x07 0xf800 0x00 0x00 0x07 0xf800 0x00 0x00 0x07 0xf800 0x00 0x00 0x07 0xf800 0x00 0x00 0x07 0xf800 0x00 0x00 0x07>;
 		memory-region = <0x02>;
-		ranges = <0x3000000 0x00 0x70000000 0x00 0x70000000 0x00 0x2000000 0x43000000 0x00 0x94000000 0x00 0x94000000 0xff 0x6c000000>;
-		reg = <0x00 0x72000000 0x00 0x1000000>;
+		ranges = <0x3000000 0x00 0x2c000000 0x00 0x2c000000 0x00 0x2000000 0x43000000 0x00 0x94000000 0x00 0x94000000 0xff 0x6c000000>;
+		reg = <0x00 0x2e000000 0x00 0x1000000>;
 	};
 
 	pclk@3M {
diff --git a/tests/benchmark/src/java/com/android/microdroid/benchmark/MicrodroidBenchmarks.java b/tests/benchmark/src/java/com/android/microdroid/benchmark/MicrodroidBenchmarks.java
index 1d827b9..915fc82 100644
--- a/tests/benchmark/src/java/com/android/microdroid/benchmark/MicrodroidBenchmarks.java
+++ b/tests/benchmark/src/java/com/android/microdroid/benchmark/MicrodroidBenchmarks.java
@@ -101,6 +101,7 @@
     private static final double NANO_TO_MICRO = 1_000.0;
     private static final String MICRODROID_IMG_PREFIX = "microdroid_";
     private static final String MICRODROID_IMG_SUFFIX = ".img";
+    static final long ENCRYPTED_STORE_SIZE = 1_073_741_824; // 1G
 
     @Parameterized.Parameters(name = "protectedVm={0},os={1}")
     public static Collection<Object[]> params() {
@@ -1097,4 +1098,58 @@
                 "latency/writeRollbackProtectedSecretWithRefreshSession",
                 "us");
     }
+
+    @Test
+    public void encryptedstoreIoRate() throws Exception {
+        VirtualMachineConfig config =
+                newVmConfigBuilderWithPayloadConfig("assets/vm_config_io.json")
+                        .setDebugLevel(DEBUG_LEVEL_NONE)
+                        .setShouldUseHugepages(true)
+                        .setEncryptedStorageBytes(ENCRYPTED_STORE_SIZE)
+                        .build();
+        List<Double> writeThroughput = new ArrayList<>(IO_TEST_TRIAL_COUNT);
+        List<Double> readThroughput = new ArrayList<>(IO_TEST_TRIAL_COUNT);
+
+        for (int i = 0; i < IO_TEST_TRIAL_COUNT; ++i) {
+            String vmName = "vm_encryptedstore_io" + i;
+            VirtualMachine vm = forceCreateNewVirtualMachine(vmName, config);
+            BenchmarkVmListener.create(new EncryptedstoreBenchmarkListener(writeThroughput, true))
+                    .runToFinish(TAG, vm);
+            // Rerun the VM & read the storage!
+            BenchmarkVmListener.create(new EncryptedstoreBenchmarkListener(readThroughput, false))
+                    .runToFinish(TAG, vm);
+        }
+        reportMetrics(writeThroughput, "encryptedstore/sequential_write", "mb_per_sec");
+        reportMetrics(readThroughput, "encryptedstore/sequential_read", "mb_per_sec");
+    }
+
+    private static class EncryptedstoreBenchmarkListener
+            implements BenchmarkVmListener.InnerListener {
+        private static final String FILENAME = "/mnt/encryptedstore/test_file";
+
+        private final List<Double> mIoThroughput;
+        // Set to true iff write is to be measured, read throughput is measured otherwise
+        private final boolean mMeasureWrite;
+
+        EncryptedstoreBenchmarkListener(List<Double> ioThroughput, boolean measureWrite) {
+            mIoThroughput = ioThroughput;
+            mMeasureWrite = measureWrite;
+        }
+
+        @Override
+        public void onPayloadReady(VirtualMachine vm, IBenchmarkService benchmarkService)
+                throws RemoteException {
+            double rate;
+            if (mMeasureWrite) {
+                // Fill 3/4 of the storage by writing (random) data into a file!
+                rate =
+                        benchmarkService.measureWriteRate(
+                                FILENAME, /*sizeBytes */ (ENCRYPTED_STORE_SIZE * 3) / 4);
+            } else {
+                // Sequentially read the file, just written.
+                rate = benchmarkService.measureReadRate(FILENAME, /*isRand */ false);
+            }
+            mIoThroughput.add(rate);
+        }
+    }
 }
diff --git a/tests/benchmark/src/native/benchmarkbinary.cpp b/tests/benchmark/src/native/benchmarkbinary.cpp
index 5d93b93..5e46712 100644
--- a/tests/benchmark/src/native/benchmarkbinary.cpp
+++ b/tests/benchmark/src/native/benchmarkbinary.cpp
@@ -67,6 +67,15 @@
         return resultStatus(res);
     }
 
+    ndk::ScopedAStatus measureWriteRate(const std::string& filename, int64_t size_bytes,
+                                        double* out) override {
+        auto res = measure_write_rate(filename, size_bytes);
+        if (res.ok()) {
+            *out = res.value();
+        }
+        return resultStatus(res);
+    }
+
     ndk::ScopedAStatus getMemInfoEntry(const std::string& name, int64_t* out) override {
         auto value = read_meminfo_entry(name);
         if (!value.ok()) {
@@ -144,6 +153,49 @@
         return {file_size_mb / elapsed_seconds};
     }
 
+    /**
+     * Measures the throughput of writing random data to the given file.
+     * @return The write rate in MB/s.
+     */
+    Result<double> measure_write_rate(const std::string& filename, int64_t size_bytes) {
+        struct stat file_stats;
+        const int64_t block_count = size_bytes / kBlockSizeBytes;
+        char buf[kBlockSizeBytes];
+        int fd_rand = open("/dev/urandom", O_RDONLY);
+        read(fd_rand, buf, kBlockSizeBytes);
+
+        struct timespec start;
+        if (clock_gettime(CLOCK_MONOTONIC, &start) == -1) {
+            return ErrnoError() << "failed to clock_gettime";
+        }
+        // TODO(b/390648694): Ideally open with O_SYNC instead of syncfs().
+        unique_fd fd(open(filename.c_str(), O_CREAT | O_WRONLY, 00666));
+        if (fd.get() == -1) {
+            return ErrnoError() << "Write: opening " << filename << " failed";
+        }
+        if (stat(filename.c_str(), &file_stats) == -1) {
+            return Error() << "failed to get file stats";
+        }
+
+        for (auto i = 0; i < block_count; ++i) {
+            auto bytes = write(fd, buf, kBlockSizeBytes);
+            if (bytes == 0) {
+                return Error() << "unexpected end of file";
+            } else if (bytes == -1) {
+                return ErrnoError() << "failed to write";
+            }
+        }
+        syncfs(fd);
+        struct timespec finish;
+        if (clock_gettime(CLOCK_MONOTONIC, &finish) == -1) {
+            return ErrnoError() << "failed to clock_gettime";
+        }
+        double elapsed_seconds =
+                finish.tv_sec - start.tv_sec + (finish.tv_nsec - start.tv_nsec) / 1e9;
+        double file_size_mb = (double)size_bytes / kNumBytesPerMB;
+        return {file_size_mb / elapsed_seconds};
+    }
+
     void* alloc_anon_memory(long mb) {
         long bytes = mb << 20;
         void* p = malloc(bytes);
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");
+        }
     }
 }