Merge aosp-24Q3-ts-dev

Bug: 358400644
Merged-In: Ic626e2a2c9fbd168312daab6eb18a13084935492
Change-Id: Ibadcfaa1f1157bc9302a128598e2a0c1f39cc0ba
diff --git a/Android.bp b/Android.bp
index 3b6b8b5..a246e08 100644
--- a/Android.bp
+++ b/Android.bp
@@ -1,124 +1,3 @@
-//
-// 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.
-
 package {
-    default_team: "trendy_team_virtualization",
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-soong_config_module_type {
-    name: "avf_flag_aware_rust_defaults",
-    module_type: "rust_defaults",
-    config_namespace: "ANDROID",
-    bool_variables: [
-        "release_avf_enable_device_assignment",
-        "release_avf_enable_dice_changes",
-        "release_avf_enable_llpvm_changes",
-        "release_avf_enable_multi_tenant_microdroid_vm",
-        "release_avf_enable_network",
-        "release_avf_enable_remote_attestation",
-        "release_avf_enable_vendor_modules",
-        "release_avf_enable_virt_cpufreq",
-        "release_avf_support_custom_vm_with_paravirtualized_devices",
-    ],
-    properties: [
-        "cfgs",
-    ],
-}
-
-avf_flag_aware_rust_defaults {
-    name: "avf_build_flags_rust",
-    soong_config_variables: {
-        release_avf_enable_device_assignment: {
-            cfgs: ["device_assignment"],
-        },
-        release_avf_enable_dice_changes: {
-            cfgs: ["dice_changes"],
-        },
-        release_avf_enable_llpvm_changes: {
-            cfgs: ["llpvm_changes"],
-        },
-        release_avf_enable_multi_tenant_microdroid_vm: {
-            cfgs: ["multi_tenant"],
-        },
-        release_avf_enable_network: {
-            cfgs: ["network"],
-        },
-        release_avf_enable_remote_attestation: {
-            cfgs: ["remote_attestation"],
-        },
-        release_avf_enable_vendor_modules: {
-            cfgs: ["vendor_modules"],
-        },
-        release_avf_enable_virt_cpufreq: {
-            cfgs: ["virt_cpufreq"],
-        },
-        release_avf_support_custom_vm_with_paravirtualized_devices: {
-            cfgs: ["paravirtualized_devices"],
-        },
-    },
-}
-
-soong_config_module_type {
-    name: "avf_flag_aware_cc_defaults",
-    module_type: "cc_defaults",
-    config_namespace: "ANDROID",
-    bool_variables: [
-        "release_avf_enable_dice_changes",
-        "release_avf_enable_vendor_modules",
-        "release_avf_enable_virt_cpufreq",
-    ],
-    properties: [
-        "cflags",
-    ],
-}
-
-avf_flag_aware_cc_defaults {
-    name: "avf_build_flags_cc",
-    soong_config_variables: {
-        release_avf_enable_dice_changes: {
-            cflags: ["-DAVF_OPEN_DICE_CHANGES=1"],
-        },
-        release_avf_enable_vendor_modules: {
-            cflags: ["-DAVF_ENABLE_VENDOR_MODULES=1"],
-        },
-        release_avf_enable_virt_cpufreq: {
-            cflags: ["-DAVF_ENABLE_VIRT_CPUFREQ=1"],
-        },
-    },
-}
-
-genrule_defaults {
-    name: "dts_to_dtb",
-    tools: ["dtc"],
-    cmd: "FILES=($(in)) && $(location dtc) -@ -I dts -O dtb $${FILES[-1]} -o $(out)",
-}
-
-// This is a temporary workaround until b/309090563 is implemented.
-aconfig_declarations {
-    name: "avf_aconfig_flags",
-    package: "com.android.system.virtualmachine.flags",
-    container: "com.android.virt",
-    srcs: [
-        "avf_flags.aconfig",
-    ],
-}
-
-java_aconfig_library {
-    name: "avf_aconfig_flags_java",
-    aconfig_declarations: "avf_aconfig_flags",
-    sdk_version: "module_current",
-    apex_available: ["com.android.virt"],
+    default_team: "trendy_team_android_kvm",
 }
diff --git a/README.md b/README.md
index f417b00..4a10c89 100644
--- a/README.md
+++ b/README.md
@@ -14,21 +14,21 @@
 For in-depth explanations about individual topics and components, visit the following links.
 
 AVF components:
-* [pVM firmware](pvmfw/README.md)
+* [pVM firmware](guest/pvmfw/README.md)
 * [Android Boot Loader (ABL)](docs/abl.md)
-* [Microdroid](microdroid/README.md)
-* [Microdroid kernel](microdroid/kernel/README.md)
-* [Microdroid payload](microdroid/payload/README.md)
-* [vmbase](vmbase/README.md)
-* [Encrypted Storage](encryptedstore/README.md)
+* [Microdroid](build/microdroid/README.md)
+* [Microdroid kernel](guest/kernel/README.md)
+* [Microdroid payload](libs/libmicrodroid_payload_metadata/README.md)
+* [vmbase](libs/libvmbase/README.md)
+* [Encrypted Storage](guest/encryptedstore/README.md)
 
 AVF APIs:
-* [Java API](java/framework/README.md)
-* [VM Payload API](vm_payload/README.md)
+* [Java API](libs/framework-virtualization/README.md)
+* [VM Payload API](libs/libvm_payload/README.md)
 
 How-Tos:
-* [Building and running a demo app in Java](demo/README.md)
-* [Building and running a demo app in C++](demo_native/README.md)
+* [Building and running a demo app in Java](android/MicrodroidDemoApp/README.md)
+* [Building and running a demo app in C++](android/vm_demo_native/README.md)
 * [Debugging](docs/debug)
 * [Using custom VM](docs/custom_vm.md)
 * [Device assignment](docs/device_assignment.md)
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 3651dfa..2112125 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -46,6 +46,9 @@
     },
     {
       "name": "libdice_driver_test"
+    },
+    {
+      "name": "vm_accessor_test"
     }
   ],
   "avf-postsubmit": [
@@ -78,13 +81,13 @@
   ],
   "imports": [
     {
-      "path": "packages/modules/Virtualization/apkdmverity"
+      "path": "packages/modules/Virtualization/guest/apkdmverity"
     },
     {
-      "path": "packages/modules/Virtualization/encryptedstore"
+      "path": "packages/modules/Virtualization/guest/encryptedstore"
     },
     {
-      "path": "packages/modules/Virtualization/virtualizationmanager"
+      "path": "packages/modules/Virtualization/android/virtmgr"
     },
     {
       "path": "packages/modules/Virtualization/libs/apexutil"
@@ -117,34 +120,34 @@
       "path": "packages/modules/Virtualization/authfs"
     },
     {
-      "path": "packages/modules/Virtualization/microdroid_manager"
+      "path": "packages/modules/Virtualization/guest/microdroid_manager"
     },
     {
-      "path": "packages/modules/Virtualization/pvmfw"
+      "path": "packages/modules/Virtualization/guest/pvmfw"
     },
     {
-      "path": "packages/modules/Virtualization/rialto"
+      "path": "packages/modules/Virtualization/guest/rialto"
     },
     {
-      "path": "packages/modules/Virtualization/service_vm/client_vm_csr"
+      "path": "packages/modules/Virtualization/libs/libclient_vm_csr"
     },
     {
-      "path": "packages/modules/Virtualization/service_vm/comm"
+      "path": "packages/modules/Virtualization/libs/libservice_vm_comm"
     },
     {
-      "path": "packages/modules/Virtualization/service_vm/requests"
+      "path": "packages/modules/Virtualization/libs/libservice_vm_requests"
     },
     {
-      "path": "packages/modules/Virtualization/virtualizationservice"
+      "path": "packages/modules/Virtualization/android/virtualizationservice"
     },
     {
-      "path": "packages/modules/Virtualization/vm"
+      "path": "packages/modules/Virtualization/android/vm"
     },
     {
-      "path": "packages/modules/Virtualization/vmbase"
+      "path": "packages/modules/Virtualization/tests/vmbase_example"
     },
     {
-      "path": "packages/modules/Virtualization/zipfuse"
+      "path": "packages/modules/Virtualization/guest/zipfuse"
     }
   ]
 }
diff --git a/apex/empty-payload-apk/Android.bp b/android/EmptyPayloadApp/Android.bp
similarity index 100%
rename from apex/empty-payload-apk/Android.bp
rename to android/EmptyPayloadApp/Android.bp
diff --git a/apex/empty-payload-apk/AndroidManifest.xml b/android/EmptyPayloadApp/AndroidManifest.xml
similarity index 100%
rename from apex/empty-payload-apk/AndroidManifest.xml
rename to android/EmptyPayloadApp/AndroidManifest.xml
diff --git a/apex/empty-payload-apk/empty_binary.cpp b/android/EmptyPayloadApp/empty_binary.cpp
similarity index 100%
rename from apex/empty-payload-apk/empty_binary.cpp
rename to android/EmptyPayloadApp/empty_binary.cpp
diff --git a/ferrochrome_app/Android.bp b/android/FerrochromeApp/Android.bp
similarity index 100%
rename from ferrochrome_app/Android.bp
rename to android/FerrochromeApp/Android.bp
diff --git a/ferrochrome_app/AndroidManifest.xml b/android/FerrochromeApp/AndroidManifest.xml
similarity index 61%
rename from ferrochrome_app/AndroidManifest.xml
rename to android/FerrochromeApp/AndroidManifest.xml
index 62b065d..f6d3f6a 100644
--- a/ferrochrome_app/AndroidManifest.xml
+++ b/android/FerrochromeApp/AndroidManifest.xml
@@ -6,22 +6,38 @@
     <uses-permission android:name="android.permission.KILL_BACKGROUND_PROCESSES" />
     <uses-permission android:name="android.permission.KILL_ALL_BACKGROUND_PROCESSES" />
     <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
+    <uses-permission android:name="com.android.virtualization.vmlauncher.permission.USE_VM_LAUNCHER" />
+
     <queries>
         <intent>
             <action android:name="android.virtualization.VM_LAUNCHER" />
         </intent>
+        <intent>
+            <action android:name="android.virtualization.FERROCHROME_DOWNLOADER" />
+        </intent>
     </queries>
     <application
         android:label="Ferrochrome">
         <activity android:name=".FerrochromeActivity"
                   android:configChanges="orientation|screenSize|keyboard|keyboardHidden|navigation|uiMode"
                   android:screenOrientation="landscape"
+                  android:resizeableActivity="false"
                   android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
                 <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
         </activity>
+        <activity android:name=".OpenUrlActivity"
+                  android:theme="@android:style/Theme.NoDisplay"
+                  android:launchMode="singleTask"
+                  android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.SEND" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data android:mimeType="text/*" />
+            </intent-filter>
+        </activity>
     </application>
 
 </manifest>
diff --git a/ferrochrome_app/custom_vm_setup.rc b/android/FerrochromeApp/custom_vm_setup.rc
similarity index 100%
rename from ferrochrome_app/custom_vm_setup.rc
rename to android/FerrochromeApp/custom_vm_setup.rc
diff --git a/ferrochrome_app/custom_vm_setup.sh b/android/FerrochromeApp/custom_vm_setup.sh
similarity index 73%
rename from ferrochrome_app/custom_vm_setup.sh
rename to android/FerrochromeApp/custom_vm_setup.sh
index a5480ff..4dce0c7 100644
--- a/ferrochrome_app/custom_vm_setup.sh
+++ b/android/FerrochromeApp/custom_vm_setup.sh
@@ -7,13 +7,13 @@
 }
 
 function install() {
-  user=$(cmd user get-main-user)
-  src_dir=/data/media/${user}/ferrochrome/
+  src_dir=$(getprop debug.custom_vm_setup.path)
+  src_dir=${src_dir/#\/storage\/emulated\//\/data\/media\/}
   dst_dir=/data/local/tmp/
 
   cat $(find ${src_dir} -name "images.tar.gz*" | sort) | tar xz -C ${dst_dir}
-  cp -u ${src_dir}vm_config.json ${dst_dir}
-  chmod 666 ${dst_dir}*
+  cp -u ${src_dir}/vm_config.json ${dst_dir}
+  chmod 666 ${dst_dir}/*
 
   # increase the size of state.img to the multiple of 4096
   num_blocks=$(du -b -K ${dst_dir}state.img | cut -f 1)
@@ -21,8 +21,8 @@
   additional_blocks=$((( ${required_num_blocks} - ${num_blocks} )))
   dd if=/dev/zero bs=512 count=${additional_blocks} >> ${dst_dir}state.img
 
-  rm ${src_dir}images.tar.gz*
-  rm ${src_dir}vm_config.json
+  rm ${src_dir}/images.tar.gz*
+  rm ${src_dir}/vm_config.json
 }
 
 setprop debug.custom_vm_setup.done false
diff --git a/android/FerrochromeApp/java/com/android/virtualization/ferrochrome/FerrochromeActivity.java b/android/FerrochromeApp/java/com/android/virtualization/ferrochrome/FerrochromeActivity.java
new file mode 100644
index 0000000..dba0078
--- /dev/null
+++ b/android/FerrochromeApp/java/com/android/virtualization/ferrochrome/FerrochromeActivity.java
@@ -0,0 +1,307 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.virtualization.ferrochrome;
+
+import android.annotation.WorkerThread;
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.os.Bundle;
+import android.os.Environment;
+import android.os.SystemProperties;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.WindowManager;
+import android.widget.TextView;
+
+import java.io.BufferedReader;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.util.List;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+public class FerrochromeActivity extends Activity {
+    private static final String TAG = FerrochromeActivity.class.getName();
+    private static final String ACTION_VM_LAUNCHER = "android.virtualization.VM_LAUNCHER";
+    private static final String ACTION_FERROCHROME_DOWNLOAD =
+            "android.virtualization.FERROCHROME_DOWNLOADER";
+    private static final String EXTRA_FERROCHROME_DEST_DIR = "dest_dir";
+    private static final String EXTRA_FERROCHROME_UPDATE_NEEDED = "update_needed";
+
+    private static final Path DEST_DIR =
+            Path.of(Environment.getExternalStorageDirectory().getPath(), "ferrochrome");
+    private static final String ASSET_DIR = "ferrochrome";
+    private static final Path VERSION_FILE = Path.of(DEST_DIR.toString(), "version");
+
+    private static final int REQUEST_CODE_VMLAUNCHER = 1;
+    private static final int REQUEST_CODE_FERROCHROME_DOWNLOADER = 2;
+
+    private ResolvedActivity mVmLauncher;
+
+    ExecutorService executorService = Executors.newSingleThreadExecutor();
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        if (!isTaskRoot()) {
+            // In case we launched this activity multiple times, only start one instance of this
+            // activity by only starting this as the root activity in task.
+            finish();
+            Log.w(TAG, "Not starting because not task root");
+            return;
+        }
+        setContentView(R.layout.activity_ferrochrome);
+        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+
+        // Find VM Launcher
+        mVmLauncher = ResolvedActivity.resolve(getPackageManager(), ACTION_VM_LAUNCHER);
+        if (mVmLauncher == null) {
+            updateStatus("Failed to resolve VM Launcher");
+            return;
+        }
+
+        // Clean up the existing vm launcher process if there is
+        ActivityManager am = getSystemService(ActivityManager.class);
+        am.killBackgroundProcesses(mVmLauncher.activityInfo.packageName);
+
+        executorService.execute(
+                () -> {
+                    if (hasLocalAssets()) {
+                        if (updateImageIfNeeded()) {
+                            updateStatus("Starting Ferrochrome...");
+                            runOnUiThread(
+                                    () ->
+                                            startActivityForResult(
+                                                    mVmLauncher.intent, REQUEST_CODE_VMLAUNCHER));
+                        }
+                    } else {
+                        tryLaunchDownloader();
+                    }
+                });
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        if (requestCode == REQUEST_CODE_VMLAUNCHER) {
+            finishAndRemoveTask();
+        } else if (requestCode == REQUEST_CODE_FERROCHROME_DOWNLOADER) {
+            String destDir = data.getStringExtra(EXTRA_FERROCHROME_DEST_DIR);
+            boolean updateNeeded =
+                    data.getBooleanExtra(EXTRA_FERROCHROME_UPDATE_NEEDED, /* default= */ true);
+
+            if (resultCode != RESULT_OK || TextUtils.isEmpty(destDir)) {
+                Log.w(
+                        TAG,
+                        "Ferrochrome downloader returned error, code="
+                                + resultCode
+                                + ", dest="
+                                + destDir);
+                updateStatus("User didn't accepted ferrochrome download..");
+                return;
+            }
+
+            Log.w(TAG, "Ferrochrome downloader returned OK");
+
+            if (!updateNeeded) {
+                updateStatus("Starting Ferrochrome...");
+                startActivityForResult(mVmLauncher.intent, REQUEST_CODE_VMLAUNCHER);
+            }
+
+            executorService.execute(
+                    () -> {
+                        if (!extractImages(destDir)) {
+                            updateStatus("Images from downloader looks bad..");
+                            return;
+                        }
+                        updateStatus("Starting Ferrochrome...");
+                        runOnUiThread(
+                                () ->
+                                        startActivityForResult(
+                                                mVmLauncher.intent, REQUEST_CODE_VMLAUNCHER));
+                    });
+        }
+    }
+
+    @WorkerThread
+    private boolean hasLocalAssets() {
+        try {
+            String[] files = getAssets().list(ASSET_DIR);
+            return files != null && files.length > 0;
+        } catch (IOException e) {
+            return false;
+        }
+    }
+
+    @WorkerThread
+    private boolean updateImageIfNeeded() {
+        if (!isUpdateNeeded()) {
+            Log.d(TAG, "No update needed.");
+            return true;
+        }
+
+        try {
+            if (Files.notExists(DEST_DIR)) {
+                Files.createDirectory(DEST_DIR);
+            }
+
+            updateStatus("Copying images...");
+            String[] files = getAssets().list("ferrochrome");
+            for (String file : files) {
+                updateStatus(file);
+                Path dst = Path.of(DEST_DIR.toString(), file);
+                updateFile(getAssets().open("ferrochrome/" + file), dst);
+            }
+        } catch (IOException e) {
+            Log.e(TAG, "Error while updating image: " + e);
+            updateStatus("Failed.");
+            return false;
+        }
+        updateStatus("Done.");
+
+        return extractImages(DEST_DIR.toAbsolutePath().toString());
+    }
+
+    @WorkerThread
+    private void tryLaunchDownloader() {
+        // TODO(jaewan): Add safeguard to check whether ferrochrome downloader is valid.
+        Log.w(TAG, "No built-in assets found. Try again with ferrochrome downloader");
+
+        ResolvedActivity downloader =
+                ResolvedActivity.resolve(getPackageManager(), ACTION_FERROCHROME_DOWNLOAD);
+        if (downloader == null) {
+            Log.d(TAG, "Ferrochrome downloader doesn't exist");
+            updateStatus("ChromeOS image not found. Please go/try-ferrochrome");
+            return;
+        }
+        String pkgName = downloader.activityInfo.packageName;
+        Log.d(TAG, "Resolved Ferrochrome Downloader, pkgName=" + pkgName);
+        updateStatus("Launching Ferrochrome downloader for update");
+
+        // onActivityResult() will handle downloader result.
+        startActivityForResult(downloader.intent, REQUEST_CODE_FERROCHROME_DOWNLOADER);
+    }
+
+    @WorkerThread
+    private boolean extractImages(String destDir) {
+        updateStatus("Extracting images...");
+
+        if (TextUtils.isEmpty(destDir)) {
+            throw new RuntimeException("Internal error: destDir shouldn't be null");
+        }
+
+        SystemProperties.set("debug.custom_vm_setup.path", destDir);
+        SystemProperties.set("debug.custom_vm_setup.done", "false");
+        SystemProperties.set("debug.custom_vm_setup.start", "true");
+        while (!SystemProperties.getBoolean("debug.custom_vm_setup.done", false)) {
+            try {
+                Thread.sleep(1000);
+            } catch (Exception e) {
+                Log.e(TAG, "Error while extracting image: " + e);
+                updateStatus("Failed.");
+                return false;
+            }
+        }
+
+        updateStatus("Done.");
+        return true;
+    }
+
+    @WorkerThread
+    private boolean isUpdateNeeded() {
+        Path[] pathsToCheck = {DEST_DIR, VERSION_FILE};
+        for (Path p : pathsToCheck) {
+            if (Files.notExists(p)) {
+                Log.d(TAG, p.toString() + " does not exist.");
+                return true;
+            }
+        }
+
+        try {
+            String installedVer = readLine(new FileInputStream(VERSION_FILE.toFile()));
+            String updatedVer = readLine(getAssets().open("ferrochrome/version"));
+            if (installedVer.equals(updatedVer)) {
+                return false;
+            }
+            Log.d(TAG, "Version mismatch. Installed: " + installedVer + "  Updated: " + updatedVer);
+        } catch (IOException e) {
+            Log.e(TAG, "Error while checking version: " + e);
+        }
+        return true;
+    }
+
+    private static String readLine(InputStream input) throws IOException {
+        try (BufferedReader reader = new BufferedReader(new InputStreamReader(input))) {
+            return reader.readLine();
+        } catch (IOException e) {
+            throw e;
+        }
+    }
+
+    private static void updateFile(InputStream input, Path path) throws IOException {
+        try {
+            Files.copy(input, path, StandardCopyOption.REPLACE_EXISTING);
+        } finally {
+            input.close();
+        }
+    }
+
+    private void updateStatus(String line) {
+        runOnUiThread(
+                () -> {
+                    TextView statusView = findViewById(R.id.status_txt_view);
+                    statusView.append(line + "\n");
+                });
+    }
+
+    private static final class ResolvedActivity {
+        public final ActivityInfo activityInfo;
+        public final Intent intent;
+
+        private ResolvedActivity(ActivityInfo activityInfo, Intent intent) {
+            this.activityInfo = activityInfo;
+            this.intent = intent;
+        }
+
+        /* synthetic access */
+        static ResolvedActivity resolve(PackageManager pm, String action) {
+            Intent intent = new Intent(action).setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
+            List<ResolveInfo> resolveInfos =
+                    pm.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
+            if (resolveInfos == null || resolveInfos.size() != 1) {
+                Log.w(
+                        TAG,
+                        "Failed to resolve activity, action="
+                                + action
+                                + ", resolved="
+                                + resolveInfos);
+                return null;
+            }
+            ActivityInfo activityInfo = resolveInfos.get(0).activityInfo;
+            intent.setClassName(activityInfo.packageName, activityInfo.name);
+            return new ResolvedActivity(activityInfo, intent);
+        }
+    }
+}
diff --git a/android/FerrochromeApp/java/com/android/virtualization/ferrochrome/OpenUrlActivity.java b/android/FerrochromeApp/java/com/android/virtualization/ferrochrome/OpenUrlActivity.java
new file mode 100644
index 0000000..c32d017
--- /dev/null
+++ b/android/FerrochromeApp/java/com/android/virtualization/ferrochrome/OpenUrlActivity.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.virtualization.ferrochrome;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.util.Log;
+
+public class OpenUrlActivity extends Activity {
+    private static final String TAG = OpenUrlActivity.class.getSimpleName();
+
+    private static final String ACTION_VM_OPEN_URL = "android.virtualization.VM_OPEN_URL";
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        boolean isRoot = isTaskRoot();
+        finish();
+        if (!Intent.ACTION_SEND.equals(getIntent().getAction())) {
+            return;
+        }
+        String text = getIntent().getStringExtra(Intent.EXTRA_TEXT);
+        if (text == null) {
+            return;
+        }
+        Uri uri = Uri.parse(text);
+        if (uri == null) {
+            return;
+        }
+        String scheme = uri.getScheme();
+        if (!("http".equals(scheme) || "https".equals(scheme) || "mailto".equals(scheme))) {
+            Log.e(TAG, "Unsupported URL scheme: " + scheme);
+            return;
+        }
+        Log.i(TAG, "Sending " + scheme + " URL to VM");
+        if (isRoot) {
+            Log.w(
+                    TAG,
+                    "Cannot open URL without starting "
+                            + FerrochromeActivity.class.getSimpleName()
+                            + " first, starting it now");
+            startActivity(
+                    new Intent(this, FerrochromeActivity.class).setAction(Intent.ACTION_MAIN));
+            return;
+        }
+        startActivity(
+                new Intent(ACTION_VM_OPEN_URL)
+                        .setFlags(
+                                Intent.FLAG_ACTIVITY_SINGLE_TOP
+                                        | Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP)
+                        .putExtra(Intent.EXTRA_TEXT, text));
+    }
+}
diff --git a/ferrochrome_app/privapp-permissions-ferrochrome.xml b/android/FerrochromeApp/privapp-permissions-ferrochrome.xml
similarity index 100%
rename from ferrochrome_app/privapp-permissions-ferrochrome.xml
rename to android/FerrochromeApp/privapp-permissions-ferrochrome.xml
diff --git a/ferrochrome_app/repack.sh b/android/FerrochromeApp/repack.sh
similarity index 93%
rename from ferrochrome_app/repack.sh
rename to android/FerrochromeApp/repack.sh
index d47b529..b2a96dd 100755
--- a/ferrochrome_app/repack.sh
+++ b/android/FerrochromeApp/repack.sh
@@ -31,9 +31,6 @@
 echo Archiving. This can take long...
 tar czvS -f images.tar.gz *.img
 
-echo Calculating hash...
-hash=$(sha1sum images.tar.gz | cut -d' ' -f 1)
-
 echo Splitting...
 split -b 100M -d images.tar.gz images.tar.gz.part
 
@@ -45,6 +42,9 @@
 rm ${asset_dir}/images.tar.gz.part*
 mv ${tempdir}/images.tar.gz.part* ${asset_dir}
 sed -E s/GUID/${root_guid}/ ${vm_config_template} > ${asset_dir}/vm_config.json
+
+echo Calculating hash...
+hash=$(cat ${tempdir}/images.tar.gz ${asset_dir}/vm_config.json | sha1sum | cut -d' ' -f 1)
 echo ${hash} > ${asset_dir}/version
 
 echo Cleanup...
diff --git a/ferrochrome_app/res/layout/activity_ferrochrome.xml b/android/FerrochromeApp/res/layout/activity_ferrochrome.xml
similarity index 100%
rename from ferrochrome_app/res/layout/activity_ferrochrome.xml
rename to android/FerrochromeApp/res/layout/activity_ferrochrome.xml
diff --git a/ferrochrome_app/vm_config.json.template b/android/FerrochromeApp/vm_config.json.template
similarity index 68%
rename from ferrochrome_app/vm_config.json.template
rename to android/FerrochromeApp/vm_config.json.template
index cb968ec..380f016 100644
--- a/ferrochrome_app/vm_config.json.template
+++ b/android/FerrochromeApp/vm_config.json.template
@@ -29,9 +29,28 @@
     "cpu_topology": "match_host",
     "platform_version": "~1.0",
     "memory_mib": 8096,
+    "debuggable": true,
+    "console_out": true,
+    "connect_console": true,
+    "console_input_device": "hvc0",
+    "network": true,
+    "input": {
+        "touchscreen": true,
+        "keyboard": true,
+        "mouse": true,
+        "trackpad": true,
+        "switches": true
+    },
+    "audio": {
+        "speaker": true,
+        "microphone": true
+    },
     "gpu": {
         "backend": "virglrenderer",
         "context_types": ["virgl2"]
     },
-    "console_input_device": "ttyS0"
+    "display": {
+        "scale": "0.77",
+        "refresh_rate": "30"
+    }
 }
diff --git a/demo/Android.bp b/android/MicrodroidDemoApp/Android.bp
similarity index 100%
rename from demo/Android.bp
rename to android/MicrodroidDemoApp/Android.bp
diff --git a/demo/AndroidManifest.xml b/android/MicrodroidDemoApp/AndroidManifest.xml
similarity index 100%
rename from demo/AndroidManifest.xml
rename to android/MicrodroidDemoApp/AndroidManifest.xml
diff --git a/demo/README.md b/android/MicrodroidDemoApp/README.md
similarity index 100%
rename from demo/README.md
rename to android/MicrodroidDemoApp/README.md
diff --git a/demo/java/com/android/microdroid/demo/MainActivity.java b/android/MicrodroidDemoApp/java/com/android/microdroid/demo/MainActivity.java
similarity index 98%
rename from demo/java/com/android/microdroid/demo/MainActivity.java
rename to android/MicrodroidDemoApp/java/com/android/microdroid/demo/MainActivity.java
index 906d18e..cbea5b9 100644
--- a/demo/java/com/android/microdroid/demo/MainActivity.java
+++ b/android/MicrodroidDemoApp/java/com/android/microdroid/demo/MainActivity.java
@@ -112,9 +112,7 @@
                             logView.append(line + "\n");
                             scrollLogView.fullScroll(View.FOCUS_DOWN);
                         });
-        model.getPayloadOutput()
-                .observeForever(
-                        line -> payloadView.append(line + "\n"));
+        model.getPayloadOutput().observeForever(line -> payloadView.append(line + "\n"));
     }
 
     /** Reads data from an input stream and posts it to the output data */
diff --git a/demo/res/layout/activity_main.xml b/android/MicrodroidDemoApp/res/layout/activity_main.xml
similarity index 100%
rename from demo/res/layout/activity_main.xml
rename to android/MicrodroidDemoApp/res/layout/activity_main.xml
diff --git a/demo/res/values/colors.xml b/android/MicrodroidDemoApp/res/values/colors.xml
similarity index 100%
rename from demo/res/values/colors.xml
rename to android/MicrodroidDemoApp/res/values/colors.xml
diff --git a/demo/res/values/themes.xml b/android/MicrodroidDemoApp/res/values/themes.xml
similarity index 100%
rename from demo/res/values/themes.xml
rename to android/MicrodroidDemoApp/res/values/themes.xml
diff --git a/android/TerminalApp/Android.bp b/android/TerminalApp/Android.bp
new file mode 100644
index 0000000..f5f39e3
--- /dev/null
+++ b/android/TerminalApp/Android.bp
@@ -0,0 +1,17 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_app {
+    name: "VmTerminalApp",
+    srcs: ["java/**/*.java"],
+    resource_dirs: ["res"],
+    static_libs: [
+        "vm_launcher_lib",
+    ],
+    sdk_version: "system_current",
+    product_specific: true,
+    optimize: {
+        shrink_resources: true,
+    },
+}
diff --git a/android/TerminalApp/AndroidManifest.xml b/android/TerminalApp/AndroidManifest.xml
new file mode 100644
index 0000000..07e6147
--- /dev/null
+++ b/android/TerminalApp/AndroidManifest.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.virtualization.terminal" >
+
+    <uses-permission android:name="android.permission.INTERNET" />
+    <uses-permission android:name="com.android.virtualization.vmlauncher.permission.USE_VM_LAUNCHER"/>
+
+    <application
+        android:label="VmTerminalApp"
+        android:usesCleartextTraffic="true">
+        <activity android:name=".MainActivity"
+                  android:screenOrientation="landscape"
+                  android:configChanges="orientation|screenSize|keyboard|keyboardHidden|navigation|uiMode"
+                  android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+</manifest>
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.java b/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.java
new file mode 100644
index 0000000..e6e56d9
--- /dev/null
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.virtualization.terminal;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.Log;
+import android.webkit.WebChromeClient;
+import android.webkit.WebView;
+import android.webkit.WebViewClient;
+import android.widget.TextView;
+
+import com.android.virtualization.vmlauncher.VmLauncherServices;
+
+public class MainActivity extends Activity implements VmLauncherServices.VmLauncherServiceCallback {
+    private static final String TAG = "VmTerminalApp";
+    private String mVmIpAddr;
+    private WebView mWebView;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        VmLauncherServices.startVmLauncherService(this, this);
+
+        setContentView(R.layout.activity_headless);
+        mWebView = (WebView) findViewById(R.id.webview);
+        mWebView.getSettings().setDatabaseEnabled(true);
+        mWebView.getSettings().setDomStorageEnabled(true);
+        mWebView.getSettings().setJavaScriptEnabled(true);
+        mWebView.setWebChromeClient(new WebChromeClient());
+        mWebView.setWebViewClient(
+                new WebViewClient() {
+                    @Override
+                    public boolean shouldOverrideUrlLoading(WebView view, String url) {
+                        view.loadUrl(url);
+                        return true;
+                    }
+                });
+    }
+
+    private void gotoURL(String url) {
+        runOnUiThread(() -> mWebView.loadUrl(url));
+    }
+
+    public void onVmStart() {
+        Log.i(TAG, "onVmStart()");
+    }
+
+    public void onVmStop() {
+        Log.i(TAG, "onVmStop()");
+        finish();
+    }
+
+    public void onVmError() {
+        Log.i(TAG, "onVmError()");
+        finish();
+    }
+
+    public void onIpAddrAvailable(String ipAddr) {
+        mVmIpAddr = ipAddr;
+        ((TextView) findViewById(R.id.ip_addr_textview)).setText(mVmIpAddr);
+
+        // TODO(b/359523803): Use AVF API to be notified when shell is ready instead of using dealy
+        new Handler(Looper.getMainLooper())
+                .postDelayed(() -> gotoURL("http://" + mVmIpAddr + ":7681"), 2000);
+    }
+}
diff --git a/android/TerminalApp/res/layout/activity_headless.xml b/android/TerminalApp/res/layout/activity_headless.xml
new file mode 100644
index 0000000..2a640f3
--- /dev/null
+++ b/android/TerminalApp/res/layout/activity_headless.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+    tools:context=".MainActivity">
+    <TextView
+        android:id="@+id/ip_addr_textview"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content" />
+  <WebView
+      android:id="@+id/webview"
+      android:layout_width="match_parent"
+      android:layout_height="match_parent"
+      android:layout_marginBottom="5dp" />
+
+</LinearLayout>
diff --git a/service_vm/demo_apk/Android.bp b/android/VmAttestationDemoApp/Android.bp
similarity index 100%
rename from service_vm/demo_apk/Android.bp
rename to android/VmAttestationDemoApp/Android.bp
diff --git a/service_vm/demo_apk/AndroidManifest.xml b/android/VmAttestationDemoApp/AndroidManifest.xml
similarity index 100%
rename from service_vm/demo_apk/AndroidManifest.xml
rename to android/VmAttestationDemoApp/AndroidManifest.xml
diff --git a/service_vm/demo_apk/README.md b/android/VmAttestationDemoApp/README.md
similarity index 100%
rename from service_vm/demo_apk/README.md
rename to android/VmAttestationDemoApp/README.md
diff --git a/service_vm/demo_apk/assets/config.json b/android/VmAttestationDemoApp/assets/config.json
similarity index 100%
rename from service_vm/demo_apk/assets/config.json
rename to android/VmAttestationDemoApp/assets/config.json
diff --git a/service_vm/demo_apk/src/main.rs b/android/VmAttestationDemoApp/src/main.rs
similarity index 100%
rename from service_vm/demo_apk/src/main.rs
rename to android/VmAttestationDemoApp/src/main.rs
diff --git a/vmlauncher_app/Android.bp b/android/VmLauncherApp/Android.bp
similarity index 91%
rename from vmlauncher_app/Android.bp
rename to android/VmLauncherApp/Android.bp
index 7103d53..7dd2473 100644
--- a/vmlauncher_app/Android.bp
+++ b/android/VmLauncherApp/Android.bp
@@ -11,6 +11,7 @@
         "android.system.virtualizationservice_internal-java",
         // TODO(b/331708504): will be removed when AVF framework handles surface
         "libcrosvm_android_display_service-java",
+        "gson",
     ],
     libs: [
         "framework-virtualization.impl",
@@ -22,7 +23,7 @@
         "com.android.virt",
     ],
     optimize: {
-        optimize: true,
+        proguard_flags_files: ["proguard.flags"],
         shrink_resources: true,
     },
 }
diff --git a/android/VmLauncherApp/AndroidManifest.xml b/android/VmLauncherApp/AndroidManifest.xml
new file mode 100644
index 0000000..583fce7
--- /dev/null
+++ b/android/VmLauncherApp/AndroidManifest.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.virtualization.vmlauncher" >
+
+    <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.INTERNET" />
+    <uses-permission android:name="android.permission.RECORD_AUDIO" />
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE"/>
+    <uses-feature android:name="android.software.virtualization_framework" android:required="true" />
+
+    <permission android:name="com.android.virtualization.vmlauncher.permission.USE_VM_LAUNCHER"
+        android:protectionLevel="signature|preinstalled"/>
+
+    <application
+        android:label="VmLauncherApp">
+        <activity android:name=".MainActivity"
+                  android:screenOrientation="landscape"
+                  android:configChanges="orientation|screenSize|keyboard|keyboardHidden|navigation|uiMode"
+                  android:theme="@style/MyTheme"
+                  android:resizeableActivity="false"
+                  android:permission="com.android.virtualization.vmlauncher.permission.USE_VM_LAUNCHER"
+                  android:exported="true">
+            <intent-filter>
+                <action android:name="android.virtualization.VM_LAUNCHER" />
+                <action android:name="android.virtualization.VM_OPEN_URL" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
+        <service
+            android:name=".VmLauncherService"
+            android:enabled="true"
+            android:exported="true"
+            android:permission="com.android.virtualization.vmlauncher.permission.USE_VM_LAUNCHER"
+            android:foregroundServiceType="specialUse">
+            <property
+                android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
+                android:value="Run VM instances" />
+            <intent-filter>
+                <action android:name="android.virtualization.START_VM_LAUNCHER_SERVICE" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </service>
+
+    </application>
+
+</manifest>
diff --git a/vmlauncher_app/README.md b/android/VmLauncherApp/README.md
similarity index 100%
rename from vmlauncher_app/README.md
rename to android/VmLauncherApp/README.md
diff --git a/android/VmLauncherApp/java/com/android/virtualization/vmlauncher/ClipboardHandler.java b/android/VmLauncherApp/java/com/android/virtualization/vmlauncher/ClipboardHandler.java
new file mode 100644
index 0000000..828d923
--- /dev/null
+++ b/android/VmLauncherApp/java/com/android/virtualization/vmlauncher/ClipboardHandler.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.virtualization.vmlauncher;
+
+import android.content.ClipData;
+import android.content.ClipboardManager;
+import android.content.Context;
+import android.util.Log;
+
+import java.nio.charset.StandardCharsets;
+
+/** Provide methods to synchronize clipboard across Android and VM. */
+class ClipboardHandler {
+    private static final String TAG = MainActivity.TAG;
+    private final ClipboardManager mClipboardManager;
+    private final VmAgent mVmAgent;
+
+    ClipboardHandler(Context context, VmAgent vmAgent) {
+        mClipboardManager = context.getSystemService(ClipboardManager.class);
+        mVmAgent = vmAgent;
+    }
+
+    private VmAgent.Connection getConnection() {
+        return mVmAgent.connect();
+    }
+
+    /** Read a text clip from Android's clipboard and send it to VM. */
+    void writeClipboardToVm() {
+        if (!mClipboardManager.hasPrimaryClip()) {
+            return;
+        }
+
+        ClipData clip = mClipboardManager.getPrimaryClip();
+        String text = clip.getItemAt(0).getText().toString();
+        // TODO: remove this trailing null character. The size is already encoded in the header.
+        text = text + '\0';
+        // TODO: use UTF-8 encoding
+        byte[] data = text.getBytes();
+
+        try {
+            getConnection().sendData(VmAgent.WRITE_CLIPBOARD_TYPE_TEXT_PLAIN, data);
+        } catch (RuntimeException e) {
+            Log.e(TAG, "Failed to write clipboard data to VM", e);
+        }
+    }
+
+    /** Read a text clip from VM and paste it to Android's clipboard. */
+    void readClipboardFromVm() {
+        VmAgent.Data data;
+        try {
+            data = getConnection().sendAndReceive(VmAgent.READ_CLIPBOARD_FROM_VM, null);
+        } catch (RuntimeException e) {
+            Log.e(TAG, "Failed to read clipboard data from VM", e);
+            return;
+        }
+
+        switch (data.type) {
+            case VmAgent.WRITE_CLIPBOARD_TYPE_EMPTY:
+                Log.d(TAG, "clipboard data from VM is empty");
+                break;
+            case VmAgent.WRITE_CLIPBOARD_TYPE_TEXT_PLAIN:
+                String text = new String(data.data, StandardCharsets.UTF_8);
+                ClipData clip = ClipData.newPlainText(null, text);
+                mClipboardManager.setPrimaryClip(clip);
+                break;
+            default:
+                Log.e(TAG, "Unknown clipboard response type: " + data.type);
+        }
+    }
+}
diff --git a/android/VmLauncherApp/java/com/android/virtualization/vmlauncher/ConfigJson.java b/android/VmLauncherApp/java/com/android/virtualization/vmlauncher/ConfigJson.java
new file mode 100644
index 0000000..6d39b46
--- /dev/null
+++ b/android/VmLauncherApp/java/com/android/virtualization/vmlauncher/ConfigJson.java
@@ -0,0 +1,260 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.virtualization.vmlauncher;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.system.virtualmachine.VirtualMachineConfig;
+import android.system.virtualmachine.VirtualMachineCustomImageConfig;
+import android.system.virtualmachine.VirtualMachineCustomImageConfig.AudioConfig;
+import android.system.virtualmachine.VirtualMachineCustomImageConfig.Disk;
+import android.system.virtualmachine.VirtualMachineCustomImageConfig.DisplayConfig;
+import android.system.virtualmachine.VirtualMachineCustomImageConfig.GpuConfig;
+import android.system.virtualmachine.VirtualMachineCustomImageConfig.Partition;
+import android.util.DisplayMetrics;
+import android.view.WindowManager;
+import android.view.WindowMetrics;
+
+import com.google.gson.Gson;
+import com.google.gson.annotations.SerializedName;
+
+import java.io.FileReader;
+import java.util.Arrays;
+
+/** This class and its inner classes model vm_config.json. */
+class ConfigJson {
+    private static final boolean DEBUG = true;
+
+    private ConfigJson() {}
+
+    @SerializedName("protected")
+    private boolean isProtected;
+
+    private String name;
+    private String cpu_topology;
+    private String platform_version;
+    private int memory_mib = 1024;
+    private String console_input_device;
+    private String bootloader;
+    private String kernel;
+    private String initrd;
+    private String params;
+    private boolean debuggable;
+    private boolean console_out;
+    private boolean connect_console;
+    private boolean network;
+    private InputJson input;
+    private AudioJson audio;
+    private DiskJson[] disks;
+    private DisplayJson display;
+    private GpuJson gpu;
+
+    /** Parses JSON file at jsonPath */
+    static ConfigJson from(String jsonPath) {
+        try (FileReader r = new FileReader(jsonPath)) {
+            return new Gson().fromJson(r, ConfigJson.class);
+        } catch (Exception e) {
+            throw new RuntimeException("Failed to parse " + jsonPath, e);
+        }
+    }
+
+    private int getCpuTopology() {
+        switch (cpu_topology) {
+            case "one_cpu":
+                return VirtualMachineConfig.CPU_TOPOLOGY_ONE_CPU;
+            case "match_host":
+                return VirtualMachineConfig.CPU_TOPOLOGY_MATCH_HOST;
+            default:
+                throw new RuntimeException("invalid cpu topology: " + cpu_topology);
+        }
+    }
+
+    private int getDebugLevel() {
+        return debuggable
+                ? VirtualMachineConfig.DEBUG_LEVEL_FULL
+                : VirtualMachineConfig.DEBUG_LEVEL_NONE;
+    }
+
+    /** Converts this parsed JSON into VirtualMachieConfig */
+    VirtualMachineConfig toConfig(Context context) {
+        return new VirtualMachineConfig.Builder(context)
+                .setProtectedVm(isProtected)
+                .setMemoryBytes((long) memory_mib * 1024 * 1024)
+                .setConsoleInputDevice(console_input_device)
+                .setCpuTopology(getCpuTopology())
+                .setCustomImageConfig(toCustomImageConfig(context))
+                .setDebugLevel(getDebugLevel())
+                .setVmOutputCaptured(console_out)
+                .setConnectVmConsole(connect_console)
+                .build();
+    }
+
+    private VirtualMachineCustomImageConfig toCustomImageConfig(Context context) {
+        VirtualMachineCustomImageConfig.Builder builder =
+                new VirtualMachineCustomImageConfig.Builder();
+
+        builder.setName(name)
+                .setBootloaderPath(bootloader)
+                .setKernelPath(kernel)
+                .setInitrdPath(initrd)
+                .useNetwork(network);
+
+        if (input != null) {
+            builder.useTouch(input.touchscreen)
+                    .useKeyboard(input.keyboard)
+                    .useMouse(input.mouse)
+                    .useTrackpad(input.trackpad)
+                    .useSwitches(input.switches);
+        }
+
+        if (audio != null) {
+            builder.setAudioConfig(audio.toConfig());
+        }
+
+        if (display != null) {
+            builder.setDisplayConfig(display.toConfig(context));
+        }
+
+        if (gpu != null) {
+            builder.setGpuConfig(gpu.toConfig());
+        }
+
+        if (params != null) {
+            Arrays.stream(params.split(" ")).forEach(builder::addParam);
+        }
+
+        if (disks != null) {
+            Arrays.stream(disks).map(d -> d.toConfig()).forEach(builder::addDisk);
+        }
+
+        return builder.build();
+    }
+
+    private static class InputJson {
+        private InputJson() {}
+
+        private boolean touchscreen;
+        private boolean keyboard;
+        private boolean mouse;
+        private boolean switches;
+        private boolean trackpad;
+    }
+
+    private static class AudioJson {
+        private AudioJson() {}
+
+        private boolean microphone;
+        private boolean speaker;
+
+        private AudioConfig toConfig() {
+            return new AudioConfig.Builder()
+                    .setUseMicrophone(microphone)
+                    .setUseSpeaker(speaker)
+                    .build();
+        }
+    }
+
+    private static class DiskJson {
+        private DiskJson() {}
+
+        private boolean writable;
+        private String image;
+        private PartitionJson[] partitions;
+
+        private Disk toConfig() {
+            Disk d = writable ? Disk.RWDisk(image) : Disk.RODisk(image);
+            for (PartitionJson pj : partitions) {
+                boolean writable = this.writable && pj.writable;
+                d.addPartition(new Partition(pj.label, pj.path, writable, pj.guid));
+            }
+            return d;
+        }
+    }
+
+    private static class PartitionJson {
+        private PartitionJson() {}
+
+        private boolean writable;
+        private String label;
+        private String path;
+        private String guid;
+    }
+
+    private static class DisplayJson {
+        private DisplayJson() {}
+
+        private float scale;
+        private int refresh_rate;
+        private int width_pixels;
+        private int height_pixels;
+
+        private DisplayConfig toConfig(Context context) {
+            WindowManager wm = context.getSystemService(WindowManager.class);
+            WindowMetrics metrics = wm.getCurrentWindowMetrics();
+            Rect dispBounds = metrics.getBounds();
+
+            int width = width_pixels > 0 ? width_pixels : dispBounds.right;
+            int height = height_pixels > 0 ? height_pixels : dispBounds.bottom;
+
+            int dpi = (int) (DisplayMetrics.DENSITY_DEFAULT * metrics.getDensity());
+            if (scale > 0.0f) {
+                dpi = (int) (dpi * scale);
+            }
+
+            int refreshRate = (int) context.getDisplay().getRefreshRate();
+            if (this.refresh_rate != 0) {
+                refreshRate = this.refresh_rate;
+            }
+
+            return new DisplayConfig.Builder()
+                    .setWidth(width)
+                    .setHeight(height)
+                    .setHorizontalDpi(dpi)
+                    .setVerticalDpi(dpi)
+                    .setRefreshRate(refreshRate)
+                    .build();
+        }
+    }
+
+    private static class GpuJson {
+        private GpuJson() {}
+
+        private String backend;
+        private String pci_address;
+        private String renderer_features;
+        private boolean renderer_use_egl = true;
+        private boolean renderer_use_gles = true;
+        private boolean renderer_use_glx = false;
+        private boolean renderer_use_surfaceless = true;
+        private boolean renderer_use_vulkan = false;
+        private String[] context_types;
+
+        private GpuConfig toConfig() {
+            return new GpuConfig.Builder()
+                    .setBackend(backend)
+                    .setPciAddress(pci_address)
+                    .setRendererFeatures(renderer_features)
+                    .setRendererUseEgl(renderer_use_egl)
+                    .setRendererUseGles(renderer_use_gles)
+                    .setRendererUseGlx(renderer_use_glx)
+                    .setRendererUseSurfaceless(renderer_use_surfaceless)
+                    .setRendererUseVulkan(renderer_use_vulkan)
+                    .setContextTypes(context_types)
+                    .build();
+        }
+    }
+}
diff --git a/android/VmLauncherApp/java/com/android/virtualization/vmlauncher/DisplayProvider.java b/android/VmLauncherApp/java/com/android/virtualization/vmlauncher/DisplayProvider.java
new file mode 100644
index 0000000..6eba709
--- /dev/null
+++ b/android/VmLauncherApp/java/com/android/virtualization/vmlauncher/DisplayProvider.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.virtualization.vmlauncher;
+
+import android.crosvm.ICrosvmAndroidDisplayService;
+import android.graphics.PixelFormat;
+import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.system.virtualizationservice_internal.IVirtualizationServiceInternal;
+import android.util.Log;
+import android.view.SurfaceControl;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+
+import libcore.io.IoBridge;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+/** Presents Android-side surface where VM can use as a display */
+class DisplayProvider {
+    private static final String TAG = MainActivity.TAG;
+    private final SurfaceView mMainView;
+    private final SurfaceView mCursorView;
+    private final IVirtualizationServiceInternal mVirtService;
+    private CursorHandler mCursorHandler;
+
+    DisplayProvider(SurfaceView mainView, SurfaceView cursorView) {
+        mMainView = mainView;
+        mCursorView = cursorView;
+
+        mMainView.setSurfaceLifecycle(SurfaceView.SURFACE_LIFECYCLE_FOLLOWS_ATTACHMENT);
+        mMainView.getHolder().addCallback(new Callback(Callback.SurfaceKind.MAIN));
+
+        mCursorView.setSurfaceLifecycle(SurfaceView.SURFACE_LIFECYCLE_FOLLOWS_ATTACHMENT);
+        mCursorView.getHolder().addCallback(new Callback(Callback.SurfaceKind.CURSOR));
+        mCursorView.getHolder().setFormat(PixelFormat.RGBA_8888);
+        // TODO: do we need this z-order?
+        mCursorView.setZOrderMediaOverlay(true);
+
+        IBinder b = ServiceManager.waitForService("android.system.virtualizationservice");
+        mVirtService = IVirtualizationServiceInternal.Stub.asInterface(b);
+        try {
+            // To ensure that the previous display service is removed.
+            mVirtService.clearDisplayService();
+        } catch (RemoteException e) {
+            throw new RuntimeException("Failed to clear prior display service", e);
+        }
+    }
+
+    void notifyDisplayIsGoingToInvisible() {
+        // When the display is going to be invisible (by putting in the background), save the frame
+        // of the main surface so that we can re-draw it next time the display becomes visible. This
+        // is to save the duration of time where nothing is drawn by VM.
+        try {
+            getDisplayService().saveFrameForSurface(false /* forCursor */);
+        } catch (RemoteException e) {
+            throw new RuntimeException("Failed to save frame for the main surface", e);
+        }
+    }
+
+    private synchronized ICrosvmAndroidDisplayService getDisplayService() {
+        try {
+            IBinder b = mVirtService.waitDisplayService();
+            return ICrosvmAndroidDisplayService.Stub.asInterface(b);
+        } catch (Exception e) {
+            throw new RuntimeException("Error while getting display service", e);
+        }
+    }
+
+    private class Callback implements SurfaceHolder.Callback {
+        enum SurfaceKind {
+            MAIN,
+            CURSOR
+        }
+
+        private final SurfaceKind mSurfaceKind;
+
+        Callback(SurfaceKind kind) {
+            mSurfaceKind = kind;
+        }
+
+        private boolean isForCursor() {
+            return mSurfaceKind == SurfaceKind.CURSOR;
+        }
+
+        @Override
+        public void surfaceCreated(SurfaceHolder holder) {
+            try {
+                getDisplayService().setSurface(holder.getSurface(), isForCursor());
+            } catch (Exception e) {
+                // TODO: don't consume this exception silently. For some unknown reason, setSurface
+                // call above throws IllegalArgumentException and that fails the surface
+                // configuration.
+                Log.e(TAG, "Failed to present surface " + mSurfaceKind + " to VM", e);
+            }
+
+            try {
+                switch (mSurfaceKind) {
+                    case MAIN:
+                        getDisplayService().drawSavedFrameForSurface(isForCursor());
+                        break;
+                    case CURSOR:
+                        ParcelFileDescriptor stream = createNewCursorStream();
+                        getDisplayService().setCursorStream(stream);
+                        break;
+                }
+            } catch (Exception e) {
+                // TODO: don't consume exceptions here too
+                Log.e(TAG, "Failed to configure surface " + mSurfaceKind, e);
+            }
+        }
+
+        @Override
+        public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+            // TODO: support resizeable display. We could actually change the display size that the
+            // VM sees, or keep the size and render it by fitting it in the new surface.
+        }
+
+        @Override
+        public void surfaceDestroyed(SurfaceHolder holder) {
+            try {
+                getDisplayService().removeSurface(isForCursor());
+            } catch (RemoteException e) {
+                throw new RuntimeException("Error while destroying surface for " + mSurfaceKind, e);
+            }
+        }
+    }
+
+    private ParcelFileDescriptor createNewCursorStream() {
+        if (mCursorHandler != null) {
+            mCursorHandler.interrupt();
+        }
+        ParcelFileDescriptor[] pfds;
+        try {
+            pfds = ParcelFileDescriptor.createSocketPair();
+        } catch (IOException e) {
+            throw new RuntimeException("Failed to create socketpair for cursor stream", e);
+        }
+        mCursorHandler = new CursorHandler(pfds[0]);
+        mCursorHandler.start();
+        return pfds[1];
+    }
+
+    /**
+     * Thread reading cursor coordinate from a stream, and updating the position of the cursor
+     * surface accordingly.
+     */
+    private class CursorHandler extends Thread {
+        private final ParcelFileDescriptor mStream;
+        private final SurfaceControl mCursor;
+        private final SurfaceControl.Transaction mTransaction;
+
+        CursorHandler(ParcelFileDescriptor stream) {
+            mStream = stream;
+            mCursor = DisplayProvider.this.mCursorView.getSurfaceControl();
+            mTransaction = new SurfaceControl.Transaction();
+
+            SurfaceControl main = DisplayProvider.this.mMainView.getSurfaceControl();
+            mTransaction.reparent(mCursor, main).apply();
+        }
+
+        @Override
+        public void run() {
+            try {
+                ByteBuffer byteBuffer = ByteBuffer.allocate(8 /* (x: u32, y: u32) */);
+                byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
+                while (true) {
+                    if (Thread.interrupted()) {
+                        Log.d(TAG, "CursorHandler thread interrupted!");
+                        return;
+                    }
+                    byteBuffer.clear();
+                    int bytes =
+                            IoBridge.read(
+                                    mStream.getFileDescriptor(),
+                                    byteBuffer.array(),
+                                    0,
+                                    byteBuffer.array().length);
+                    if (bytes == -1) {
+                        Log.e(TAG, "cannot read from cursor stream, stop the handler");
+                        return;
+                    }
+                    float x = (float) (byteBuffer.getInt() & 0xFFFFFFFF);
+                    float y = (float) (byteBuffer.getInt() & 0xFFFFFFFF);
+                    mTransaction.setPosition(mCursor, x, y).apply();
+                }
+            } catch (IOException e) {
+                Log.e(TAG, "failed to run CursorHandler", e);
+            }
+        }
+    }
+}
diff --git a/android/VmLauncherApp/java/com/android/virtualization/vmlauncher/InputForwarder.java b/android/VmLauncherApp/java/com/android/virtualization/vmlauncher/InputForwarder.java
new file mode 100644
index 0000000..1be362b
--- /dev/null
+++ b/android/VmLauncherApp/java/com/android/virtualization/vmlauncher/InputForwarder.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.virtualization.vmlauncher;
+
+import android.content.Context;
+import android.hardware.input.InputManager;
+import android.os.Handler;
+import android.system.virtualmachine.VirtualMachine;
+import android.system.virtualmachine.VirtualMachineCustomImageConfig;
+import android.util.Log;
+import android.view.InputDevice;
+import android.view.KeyEvent;
+import android.view.View;
+
+/** Forwards input events (touch, mouse, ...) from Android to VM */
+class InputForwarder {
+    private static final String TAG = MainActivity.TAG;
+    private final Context mContext;
+    private final VirtualMachine mVirtualMachine;
+    private InputManager.InputDeviceListener mInputDeviceListener;
+
+    private boolean isTabletMode = false;
+
+    InputForwarder(
+            Context context,
+            VirtualMachine vm,
+            View touchReceiver,
+            View mouseReceiver,
+            View keyReceiver) {
+        mContext = context;
+        mVirtualMachine = vm;
+
+        VirtualMachineCustomImageConfig config = vm.getConfig().getCustomImageConfig();
+        if (config.useTouch()) {
+            setupTouchReceiver(touchReceiver);
+        }
+        if (config.useMouse() || config.useTrackpad()) {
+            setupMouseReceiver(mouseReceiver);
+        }
+        if (config.useKeyboard()) {
+            setupKeyReceiver(keyReceiver);
+        }
+        if (config.useSwitches()) {
+            // Any view's handler is fine.
+            setupTabletModeHandler(touchReceiver.getHandler());
+        }
+    }
+
+    void cleanUp() {
+        if (mInputDeviceListener != null) {
+            InputManager im = mContext.getSystemService(InputManager.class);
+            im.unregisterInputDeviceListener(mInputDeviceListener);
+            mInputDeviceListener = null;
+        }
+    }
+
+    private void setupTouchReceiver(View receiver) {
+        receiver.setOnTouchListener(
+                (v, event) -> {
+                    return mVirtualMachine.sendMultiTouchEvent(event);
+                });
+    }
+
+    private void setupMouseReceiver(View receiver) {
+        receiver.requestUnbufferedDispatch(InputDevice.SOURCE_ANY);
+        receiver.setOnCapturedPointerListener(
+                (v, event) -> {
+                    int eventSource = event.getSource();
+                    if ((eventSource & InputDevice.SOURCE_CLASS_POSITION) != 0) {
+                        return mVirtualMachine.sendTrackpadEvent(event);
+                    }
+                    return mVirtualMachine.sendMouseEvent(event);
+                });
+    }
+
+    private void setupKeyReceiver(View receiver) {
+        receiver.setOnKeyListener(
+                (v, code, event) -> {
+                    // TODO: this is guest-os specific. It shouldn't be handled here.
+                    if (isVolumeKey(code)) {
+                        return false;
+                    }
+                    return mVirtualMachine.sendKeyEvent(event);
+                });
+    }
+
+    private static boolean isVolumeKey(int keyCode) {
+        return keyCode == KeyEvent.KEYCODE_VOLUME_UP
+                || keyCode == KeyEvent.KEYCODE_VOLUME_DOWN
+                || keyCode == KeyEvent.KEYCODE_VOLUME_MUTE;
+    }
+
+    private void setupTabletModeHandler(Handler handler) {
+        InputManager im = mContext.getSystemService(InputManager.class);
+        mInputDeviceListener =
+                new InputManager.InputDeviceListener() {
+                    @Override
+                    public void onInputDeviceAdded(int deviceId) {
+                        setTabletModeConditionally();
+                    }
+
+                    @Override
+                    public void onInputDeviceRemoved(int deviceId) {
+                        setTabletModeConditionally();
+                    }
+
+                    @Override
+                    public void onInputDeviceChanged(int deviceId) {
+                        setTabletModeConditionally();
+                    }
+                };
+        im.registerInputDeviceListener(mInputDeviceListener, handler);
+    }
+
+    private static boolean hasPhysicalKeyboard() {
+        for (int id : InputDevice.getDeviceIds()) {
+            InputDevice d = InputDevice.getDevice(id);
+            if (!d.isVirtual() && d.isEnabled() && d.isFullKeyboard()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    void setTabletModeConditionally() {
+        boolean tabletModeNeeded = !hasPhysicalKeyboard();
+        if (tabletModeNeeded != isTabletMode) {
+            String mode = tabletModeNeeded ? "tablet mode" : "desktop mode";
+            Log.d(TAG, "switching to " + mode);
+            isTabletMode = tabletModeNeeded;
+            mVirtualMachine.sendTabletModeEvent(tabletModeNeeded);
+        }
+    }
+}
diff --git a/android/VmLauncherApp/java/com/android/virtualization/vmlauncher/Logger.java b/android/VmLauncherApp/java/com/android/virtualization/vmlauncher/Logger.java
new file mode 100644
index 0000000..e1cb285
--- /dev/null
+++ b/android/VmLauncherApp/java/com/android/virtualization/vmlauncher/Logger.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.virtualization.vmlauncher;
+
+import android.system.virtualmachine.VirtualMachine;
+import android.system.virtualmachine.VirtualMachineConfig;
+import android.system.virtualmachine.VirtualMachineException;
+import android.util.Log;
+
+import libcore.io.Streams;
+
+import java.io.BufferedOutputStream;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import java.util.concurrent.ExecutorService;
+
+/**
+ * Forwards VM's console output to a file on the Android side, and VM's log output to Android logd.
+ */
+class Logger {
+    private Logger() {}
+
+    static void setup(VirtualMachine vm, Path path, ExecutorService executor) {
+        if (vm.getConfig().getDebugLevel() != VirtualMachineConfig.DEBUG_LEVEL_FULL) {
+            return;
+        }
+
+        try {
+            InputStream console = vm.getConsoleOutput();
+            OutputStream file = Files.newOutputStream(path, StandardOpenOption.CREATE);
+            executor.submit(() -> Streams.copy(console, new LineBufferedOutputStream(file)));
+
+            InputStream log = vm.getLogOutput();
+            executor.submit(() -> writeToLogd(log, vm.getName()));
+        } catch (VirtualMachineException | IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private static boolean writeToLogd(InputStream input, String vmName) throws IOException {
+        BufferedReader reader = new BufferedReader(new InputStreamReader(input));
+        String line;
+        while ((line = reader.readLine()) != null && !Thread.interrupted()) {
+            Log.d(vmName, line);
+        }
+        // TODO: find out why javac complains when the return type of this method is void. It
+        // (incorrectly?) thinks that IOException should be caught inside the lambda.
+        return true;
+    }
+
+    private static class LineBufferedOutputStream extends BufferedOutputStream {
+        LineBufferedOutputStream(OutputStream out) {
+            super(out);
+        }
+
+        @Override
+        public void write(byte[] buf, int off, int len) throws IOException {
+            super.write(buf, off, len);
+            for (int i = 0; i < len; ++i) {
+                if (buf[off + i] == '\n') {
+                    flush();
+                    break;
+                }
+            }
+        }
+    }
+}
diff --git a/android/VmLauncherApp/java/com/android/virtualization/vmlauncher/MainActivity.java b/android/VmLauncherApp/java/com/android/virtualization/vmlauncher/MainActivity.java
new file mode 100644
index 0000000..54543b0
--- /dev/null
+++ b/android/VmLauncherApp/java/com/android/virtualization/vmlauncher/MainActivity.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.virtualization.vmlauncher;
+
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+
+import android.Manifest.permission;
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.system.virtualmachine.VirtualMachine;
+import android.system.virtualmachine.VirtualMachineConfig;
+import android.system.virtualmachine.VirtualMachineException;
+import android.util.Log;
+import android.view.SurfaceView;
+import android.view.View;
+import android.view.Window;
+import android.view.WindowInsets;
+import android.view.WindowInsetsController;
+
+import java.nio.file.Path;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+public class MainActivity extends Activity {
+    static final String TAG = "VmLauncherApp";
+    // TODO: this path should be from outside of this activity
+    private static final String VM_CONFIG_PATH = "/data/local/tmp/vm_config.json";
+
+    private static final int RECORD_AUDIO_PERMISSION_REQUEST_CODE = 101;
+
+    private static final String ACTION_VM_LAUNCHER = "android.virtualization.VM_LAUNCHER";
+    private static final String ACTION_VM_OPEN_URL = "android.virtualization.VM_OPEN_URL";
+
+    private ExecutorService mExecutorService;
+    private VirtualMachine mVirtualMachine;
+    private InputForwarder mInputForwarder;
+    private DisplayProvider mDisplayProvider;
+    private VmAgent mVmAgent;
+    private ClipboardHandler mClipboardHandler;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        String action = getIntent().getAction();
+        if (!ACTION_VM_LAUNCHER.equals(action)) {
+            finish();
+            Log.e(TAG, "onCreate unsupported intent action: " + action);
+            return;
+        }
+        checkAndRequestRecordAudioPermission();
+        mExecutorService = Executors.newCachedThreadPool();
+
+        ConfigJson json = ConfigJson.from(VM_CONFIG_PATH);
+        VirtualMachineConfig config = json.toConfig(this);
+
+        Runner runner;
+        try {
+            runner = Runner.create(this, config);
+        } catch (VirtualMachineException e) {
+            throw new RuntimeException(e);
+        }
+        mVirtualMachine = runner.getVm();
+        runner.getExitStatus()
+                .thenAcceptAsync(
+                        success -> {
+                            setResult(success ? RESULT_OK : RESULT_CANCELED);
+                            finish();
+                        });
+
+        // Setup UI
+        setContentView(R.layout.activity_main);
+        SurfaceView mainView = findViewById(R.id.surface_view);
+        SurfaceView cursorView = findViewById(R.id.cursor_surface_view);
+        View touchView = findViewById(R.id.background_touch_view);
+        makeFullscreen();
+
+        // Connect the views to the VM
+        mInputForwarder = new InputForwarder(this, mVirtualMachine, touchView, mainView, mainView);
+        mDisplayProvider = new DisplayProvider(mainView, cursorView);
+
+        Path logPath = getFileStreamPath(mVirtualMachine.getName() + ".log").toPath();
+        Logger.setup(mVirtualMachine, logPath, mExecutorService);
+
+        mVmAgent = new VmAgent(mVirtualMachine);
+        mClipboardHandler = new ClipboardHandler(this, mVmAgent);
+    }
+
+    private void makeFullscreen() {
+        Window w = getWindow();
+        w.setDecorFitsSystemWindows(false);
+        WindowInsetsController insetsCtrl = w.getInsetsController();
+        insetsCtrl.hide(WindowInsets.Type.systemBars());
+        insetsCtrl.setSystemBarsBehavior(
+                WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE);
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        mInputForwarder.setTabletModeConditionally();
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+        mDisplayProvider.notifyDisplayIsGoingToInvisible();
+    }
+
+    @Override
+    protected void onStop() {
+        super.onStop();
+        try {
+            mVirtualMachine.suspend();
+        } catch (VirtualMachineException e) {
+            Log.e(TAG, "Failed to suspend VM" + e);
+        }
+    }
+
+    @Override
+    protected void onRestart() {
+        super.onRestart();
+        try {
+            mVirtualMachine.resume();
+        } catch (VirtualMachineException e) {
+            Log.e(TAG, "Failed to resume VM" + e);
+        }
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        mExecutorService.shutdownNow();
+        mInputForwarder.cleanUp();
+        Log.d(TAG, "destroyed");
+    }
+
+    @Override
+    public void onWindowFocusChanged(boolean hasFocus) {
+        super.onWindowFocusChanged(hasFocus);
+
+        // TODO: explain why we have to do this on every focus change
+        if (hasFocus) {
+            SurfaceView mainView = findViewById(R.id.surface_view);
+            mainView.requestPointerCapture();
+        }
+
+        // TODO: remove executor here. Let clipboard handler handle this.
+        mExecutorService.execute(
+                () -> {
+                    if (hasFocus) {
+                        mClipboardHandler.writeClipboardToVm();
+                    } else {
+                        mClipboardHandler.readClipboardFromVm();
+                    }
+                });
+    }
+
+    @Override
+    protected void onNewIntent(Intent intent) {
+        String action = intent.getAction();
+        if (!ACTION_VM_OPEN_URL.equals(action)) {
+            Log.e(TAG, "onNewIntent unsupported intent action: " + action);
+            return;
+        }
+        Log.d(TAG, "onNewIntent intent action: " + action);
+        String text = intent.getStringExtra(Intent.EXTRA_TEXT);
+        if (text != null) {
+            mExecutorService.execute(
+                    () -> {
+                        mVmAgent.connect().sendData(VmAgent.OPEN_URL, text.getBytes());
+                    });
+        }
+    }
+
+    private void checkAndRequestRecordAudioPermission() {
+        if (getApplicationContext().checkSelfPermission(permission.RECORD_AUDIO)
+                != PERMISSION_GRANTED) {
+            requestPermissions(
+                    new String[] {permission.RECORD_AUDIO}, RECORD_AUDIO_PERMISSION_REQUEST_CODE);
+        }
+    }
+}
diff --git a/android/VmLauncherApp/java/com/android/virtualization/vmlauncher/Runner.java b/android/VmLauncherApp/java/com/android/virtualization/vmlauncher/Runner.java
new file mode 100644
index 0000000..a5f58fe
--- /dev/null
+++ b/android/VmLauncherApp/java/com/android/virtualization/vmlauncher/Runner.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.virtualization.vmlauncher;
+
+import android.content.Context;
+import android.system.virtualmachine.VirtualMachine;
+import android.system.virtualmachine.VirtualMachineCallback;
+import android.system.virtualmachine.VirtualMachineConfig;
+import android.system.virtualmachine.VirtualMachineCustomImageConfig;
+import android.system.virtualmachine.VirtualMachineException;
+import android.system.virtualmachine.VirtualMachineManager;
+import android.util.Log;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ForkJoinPool;
+
+/** Utility class for creating a VM and waiting for it to finish. */
+class Runner {
+    private static final String TAG = MainActivity.TAG;
+    private final VirtualMachine mVirtualMachine;
+    private final Callback mCallback;
+
+    private Runner(VirtualMachine vm, Callback cb) {
+        mVirtualMachine = vm;
+        mCallback = cb;
+    }
+
+    /** Create a virtual machine of the given config, under the given context. */
+    static Runner create(Context context, VirtualMachineConfig config)
+            throws VirtualMachineException {
+        // context may already be the app context, but calling this again is not harmful.
+        // See b/359439878 on why vmm should be obtained from the app context.
+        Context appContext = context.getApplicationContext();
+        VirtualMachineManager vmm = appContext.getSystemService(VirtualMachineManager.class);
+        VirtualMachineCustomImageConfig customConfig = config.getCustomImageConfig();
+        if (customConfig == null) {
+            throw new RuntimeException("CustomImageConfig is missing");
+        }
+
+        String name = customConfig.getName();
+        if (name == null || name.isEmpty()) {
+            throw new RuntimeException("Virtual machine's name is missing in the config");
+        }
+
+        VirtualMachine vm = vmm.getOrCreate(name, config);
+        try {
+            vm.setConfig(config);
+        } catch (VirtualMachineException e) {
+            vmm.delete(name);
+            vm = vmm.create(name, config);
+            Log.w(TAG, "Re-creating virtual machine (" + name + ")", e);
+        }
+
+        Callback cb = new Callback();
+        vm.setCallback(ForkJoinPool.commonPool(), cb);
+        vm.run();
+        return new Runner(vm, cb);
+    }
+
+    /** Give access to the underlying VirtualMachine object. */
+    VirtualMachine getVm() {
+        return mVirtualMachine;
+    }
+
+    /** Get future about VM's exit status. */
+    CompletableFuture<Boolean> getExitStatus() {
+        return mCallback.mFinishedSuccessfully;
+    }
+
+    private static class Callback implements VirtualMachineCallback {
+        final CompletableFuture<Boolean> mFinishedSuccessfully = new CompletableFuture<>();
+
+        @Override
+        public void onPayloadStarted(VirtualMachine vm) {
+            // This event is only from Microdroid-based VM. Custom VM shouldn't emit this.
+        }
+
+        @Override
+        public void onPayloadReady(VirtualMachine vm) {
+            // This event is only from Microdroid-based VM. Custom VM shouldn't emit this.
+        }
+
+        @Override
+        public void onPayloadFinished(VirtualMachine vm, int exitCode) {
+            // This event is only from Microdroid-based VM. Custom VM shouldn't emit this.
+        }
+
+        @Override
+        public void onError(VirtualMachine vm, int errorCode, String message) {
+            Log.e(TAG, "Error from VM. code: " + errorCode + " (" + message + ")");
+            mFinishedSuccessfully.complete(false);
+        }
+
+        @Override
+        public void onStopped(VirtualMachine vm, int reason) {
+            Log.d(TAG, "VM stopped. Reason: " + reason);
+            mFinishedSuccessfully.complete(true);
+        }
+    }
+}
diff --git a/android/VmLauncherApp/java/com/android/virtualization/vmlauncher/VmAgent.java b/android/VmLauncherApp/java/com/android/virtualization/vmlauncher/VmAgent.java
new file mode 100644
index 0000000..78da6c0
--- /dev/null
+++ b/android/VmLauncherApp/java/com/android/virtualization/vmlauncher/VmAgent.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.virtualization.vmlauncher;
+
+import android.os.ParcelFileDescriptor;
+import android.system.virtualmachine.VirtualMachine;
+import android.system.virtualmachine.VirtualMachineException;
+
+import libcore.io.Streams;
+
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+/**
+ * Agent running in the VM. This class provides connection to the agent and ways to communicate with
+ * it.
+ */
+class VmAgent {
+    private static final String TAG = MainActivity.TAG;
+    private static final int DATA_SHARING_SERVICE_PORT = 3580;
+    private static final int HEADER_SIZE = 8; // size of the header
+    private static final int SIZE_OFFSET = 4; // offset of the size field in the header
+
+    static final byte READ_CLIPBOARD_FROM_VM = 0;
+    static final byte WRITE_CLIPBOARD_TYPE_EMPTY = 1;
+    static final byte WRITE_CLIPBOARD_TYPE_TEXT_PLAIN = 2;
+    static final byte OPEN_URL = 3;
+
+    private final VirtualMachine mVirtualMachine;
+
+    VmAgent(VirtualMachine vm) {
+        mVirtualMachine = vm;
+    }
+
+    /** Connect to the agent and returns the communication channel established. This can block. */
+    Connection connect() {
+        try {
+            // TODO: wait until the VM is up and the agent is running
+            return new Connection(mVirtualMachine.connectVsock(DATA_SHARING_SERVICE_PORT));
+        } catch (VirtualMachineException e) {
+            throw new RuntimeException("Failed to connect to the VM agent", e);
+        }
+    }
+
+    static class Data {
+        final int type;
+        final byte[] data;
+
+        Data(int type, byte[] data) {
+            this.type = type;
+            this.data = data;
+        }
+    }
+
+    /** Represents a connection to the agent */
+    class Connection {
+        private final ParcelFileDescriptor mConn;
+
+        private Connection(ParcelFileDescriptor conn) {
+            mConn = conn;
+        }
+
+        /** Send data of a given type. This can block. */
+        void sendData(byte type, byte[] data) {
+            // Byte 0: Data type
+            // Byte 1-3: Padding alignment & Reserved for other use cases in the future
+            // Byte 4-7: Data size of the payload
+            ByteBuffer header = ByteBuffer.allocate(HEADER_SIZE);
+            header.clear();
+            header.order(ByteOrder.LITTLE_ENDIAN);
+            header.put(0, type);
+            int dataSize = data == null ? 0 : data.length;
+            header.putInt(SIZE_OFFSET, dataSize);
+
+            try (OutputStream out = new FileOutputStream(mConn.getFileDescriptor())) {
+                out.write(header.array());
+                if (data != null) {
+                    out.write(data);
+                }
+            } catch (IOException e) {
+                throw new RuntimeException("Failed to send message of type: " + type, e);
+            }
+        }
+
+        /** Read data from agent. This can block. */
+        Data readData() {
+            ByteBuffer header = ByteBuffer.allocate(HEADER_SIZE);
+            header.clear();
+            header.order(ByteOrder.LITTLE_ENDIAN);
+            byte[] data;
+
+            try (InputStream in = new FileInputStream(mConn.getFileDescriptor())) {
+                Streams.readFully(in, header.array());
+                byte type = header.get(0);
+                int dataSize = header.getInt(SIZE_OFFSET);
+                data = new byte[dataSize];
+                Streams.readFully(in, data);
+                return new Data(type, data);
+            } catch (IOException e) {
+                throw new RuntimeException("Failed to read data", e);
+            }
+        }
+
+        /** Convenient method for sending data and then reading response for it. This can block. */
+        Data sendAndReceive(byte type, byte[] data) {
+            sendData(type, data);
+            return readData();
+        }
+    }
+}
diff --git a/android/VmLauncherApp/java/com/android/virtualization/vmlauncher/VmLauncherService.java b/android/VmLauncherApp/java/com/android/virtualization/vmlauncher/VmLauncherService.java
new file mode 100644
index 0000000..ec98f4c
--- /dev/null
+++ b/android/VmLauncherApp/java/com/android/virtualization/vmlauncher/VmLauncherService.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.virtualization.vmlauncher;
+
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.Service;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.ParcelFileDescriptor;
+import android.os.ResultReceiver;
+import android.system.virtualmachine.VirtualMachine;
+import android.system.virtualmachine.VirtualMachineConfig;
+import android.system.virtualmachine.VirtualMachineException;
+import android.util.Log;
+
+import java.io.BufferedReader;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.nio.file.Path;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+public class VmLauncherService extends Service {
+    private static final String TAG = "VmLauncherService";
+    // TODO: this path should be from outside of this service
+    private static final String VM_CONFIG_PATH = "/data/local/tmp/vm_config.json";
+
+    private static final int RESULT_START = 0;
+    private static final int RESULT_STOP = 1;
+    private static final int RESULT_ERROR = 2;
+    private static final int RESULT_IPADDR = 3;
+    private static final String KEY_VM_IP_ADDR = "ip_addr";
+
+    private ExecutorService mExecutorService;
+    private VirtualMachine mVirtualMachine;
+    private ResultReceiver mResultReceiver;
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return null;
+    }
+
+    private void startForeground() {
+        NotificationManager notificationManager = getSystemService(NotificationManager.class);
+        NotificationChannel notificationChannel =
+                new NotificationChannel(TAG, TAG, NotificationManager.IMPORTANCE_LOW);
+        notificationManager.createNotificationChannel(notificationChannel);
+        startForeground(
+                this.hashCode(),
+                new Notification.Builder(this, TAG)
+                        .setChannelId(TAG)
+                        .setSmallIcon(android.R.drawable.ic_dialog_info)
+                        .setContentText("A VM " + mVirtualMachine.getName() + " is running")
+                        .build());
+    }
+
+    @Override
+    public int onStartCommand(Intent intent, int flags, int startId) {
+        mExecutorService = Executors.newCachedThreadPool();
+
+        ConfigJson json = ConfigJson.from(VM_CONFIG_PATH);
+        VirtualMachineConfig config = json.toConfig(this);
+
+        Runner runner;
+        try {
+            runner = Runner.create(this, config);
+        } catch (VirtualMachineException e) {
+            throw new RuntimeException(e);
+        }
+        mVirtualMachine = runner.getVm();
+        mResultReceiver =
+                intent.getParcelableExtra(Intent.EXTRA_RESULT_RECEIVER, ResultReceiver.class);
+
+        runner.getExitStatus()
+                .thenAcceptAsync(
+                        success -> {
+                            if (mResultReceiver != null) {
+                                mResultReceiver.send(success ? RESULT_STOP : RESULT_ERROR, null);
+                            }
+                            if (!success) {
+                                stopSelf();
+                            }
+                        });
+        Path logPath = getFileStreamPath(mVirtualMachine.getName() + ".log").toPath();
+        Logger.setup(mVirtualMachine, logPath, mExecutorService);
+
+        startForeground();
+
+        mResultReceiver.send(RESULT_START, null);
+        if (config.getCustomImageConfig().useNetwork()) {
+            Handler handler = new Handler(Looper.getMainLooper());
+            gatherIpAddrFromVm(handler);
+        }
+        return START_NOT_STICKY;
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        mExecutorService.shutdownNow();
+    }
+
+    // TODO(b/359523803): Use AVF API to get ip addr when it exists
+    private void gatherIpAddrFromVm(Handler handler) {
+        handler.postDelayed(
+                () -> {
+                    int INTERNAL_VSOCK_SERVER_PORT = 1024;
+                    try (ParcelFileDescriptor pfd =
+                            mVirtualMachine.connectVsock(INTERNAL_VSOCK_SERVER_PORT)) {
+                        try (BufferedReader input =
+                                new BufferedReader(
+                                        new InputStreamReader(
+                                                new FileInputStream(pfd.getFileDescriptor())))) {
+                            String vmIpAddr = input.readLine().strip();
+                            Bundle b = new Bundle();
+                            b.putString(KEY_VM_IP_ADDR, vmIpAddr);
+                            mResultReceiver.send(RESULT_IPADDR, b);
+                            return;
+                        } catch (IOException e) {
+                            Log.e(TAG, e.toString());
+                        }
+                    } catch (Exception e) {
+                        Log.e(TAG, e.toString());
+                    }
+                    gatherIpAddrFromVm(handler);
+                },
+                1000);
+    }
+}
diff --git a/android/VmLauncherApp/proguard.flags b/android/VmLauncherApp/proguard.flags
new file mode 100644
index 0000000..13ec24e
--- /dev/null
+++ b/android/VmLauncherApp/proguard.flags
@@ -0,0 +1,7 @@
+# Keep the no-args constructor of the deserialized class
+-keepclassmembers class com.android.virtualization.vmlauncher.ConfigJson {
+  <init>();
+}
+-keepclassmembers class com.android.virtualization.vmlauncher.ConfigJson$* {
+  <init>();
+}
diff --git a/vmlauncher_app/res/layout/activity_main.xml b/android/VmLauncherApp/res/layout/activity_main.xml
similarity index 100%
rename from vmlauncher_app/res/layout/activity_main.xml
rename to android/VmLauncherApp/res/layout/activity_main.xml
diff --git a/vmlauncher_app/res/values/themes.xml b/android/VmLauncherApp/res/values/themes.xml
similarity index 100%
rename from vmlauncher_app/res/values/themes.xml
rename to android/VmLauncherApp/res/values/themes.xml
diff --git a/java/Android.bp b/android/android.system.virtualmachine.res/Android.bp
similarity index 100%
rename from java/Android.bp
rename to android/android.system.virtualmachine.res/Android.bp
diff --git a/java/AndroidManifest.xml b/android/android.system.virtualmachine.res/AndroidManifest.xml
similarity index 100%
rename from java/AndroidManifest.xml
rename to android/android.system.virtualmachine.res/AndroidManifest.xml
diff --git a/java/AndroidManifestNext.xml b/android/android.system.virtualmachine.res/AndroidManifestNext.xml
similarity index 100%
rename from java/AndroidManifestNext.xml
rename to android/android.system.virtualmachine.res/AndroidManifestNext.xml
diff --git a/compos/verify/Android.bp b/android/compos_verify/Android.bp
similarity index 100%
rename from compos/verify/Android.bp
rename to android/compos_verify/Android.bp
diff --git a/compos/verify/native/Android.bp b/android/compos_verify/native/Android.bp
similarity index 100%
rename from compos/verify/native/Android.bp
rename to android/compos_verify/native/Android.bp
diff --git a/compos/verify/native/lib.rs b/android/compos_verify/native/lib.rs
similarity index 100%
rename from compos/verify/native/lib.rs
rename to android/compos_verify/native/lib.rs
diff --git a/compos/verify/native/verify_native.cpp b/android/compos_verify/native/verify_native.cpp
similarity index 100%
rename from compos/verify/native/verify_native.cpp
rename to android/compos_verify/native/verify_native.cpp
diff --git a/compos/verify/native/verify_native.h b/android/compos_verify/native/verify_native.h
similarity index 100%
rename from compos/verify/native/verify_native.h
rename to android/compos_verify/native/verify_native.h
diff --git a/compos/verify/verify.rs b/android/compos_verify/verify.rs
similarity index 100%
rename from compos/verify/verify.rs
rename to android/compos_verify/verify.rs
diff --git a/compos/composd/Android.bp b/android/composd/Android.bp
similarity index 100%
rename from compos/composd/Android.bp
rename to android/composd/Android.bp
diff --git a/compos/composd/aidl/Android.bp b/android/composd/aidl/Android.bp
similarity index 100%
rename from compos/composd/aidl/Android.bp
rename to android/composd/aidl/Android.bp
diff --git a/compos/composd/aidl/android/system/composd/ICompilationTask.aidl b/android/composd/aidl/android/system/composd/ICompilationTask.aidl
similarity index 100%
rename from compos/composd/aidl/android/system/composd/ICompilationTask.aidl
rename to android/composd/aidl/android/system/composd/ICompilationTask.aidl
diff --git a/compos/composd/aidl/android/system/composd/ICompilationTaskCallback.aidl b/android/composd/aidl/android/system/composd/ICompilationTaskCallback.aidl
similarity index 100%
rename from compos/composd/aidl/android/system/composd/ICompilationTaskCallback.aidl
rename to android/composd/aidl/android/system/composd/ICompilationTaskCallback.aidl
diff --git a/compos/composd/aidl/android/system/composd/IIsolatedCompilationService.aidl b/android/composd/aidl/android/system/composd/IIsolatedCompilationService.aidl
similarity index 100%
rename from compos/composd/aidl/android/system/composd/IIsolatedCompilationService.aidl
rename to android/composd/aidl/android/system/composd/IIsolatedCompilationService.aidl
diff --git a/compos/composd/native/Android.bp b/android/composd/native/Android.bp
similarity index 100%
rename from compos/composd/native/Android.bp
rename to android/composd/native/Android.bp
diff --git a/compos/composd/native/lib.rs b/android/composd/native/lib.rs
similarity index 100%
rename from compos/composd/native/lib.rs
rename to android/composd/native/lib.rs
diff --git a/compos/composd/src/composd_main.rs b/android/composd/src/composd_main.rs
similarity index 100%
rename from compos/composd/src/composd_main.rs
rename to android/composd/src/composd_main.rs
diff --git a/compos/composd/src/fd_server_helper.rs b/android/composd/src/fd_server_helper.rs
similarity index 100%
rename from compos/composd/src/fd_server_helper.rs
rename to android/composd/src/fd_server_helper.rs
diff --git a/compos/composd/src/instance_manager.rs b/android/composd/src/instance_manager.rs
similarity index 100%
rename from compos/composd/src/instance_manager.rs
rename to android/composd/src/instance_manager.rs
diff --git a/compos/composd/src/instance_starter.rs b/android/composd/src/instance_starter.rs
similarity index 100%
rename from compos/composd/src/instance_starter.rs
rename to android/composd/src/instance_starter.rs
diff --git a/compos/composd/src/odrefresh_task.rs b/android/composd/src/odrefresh_task.rs
similarity index 100%
rename from compos/composd/src/odrefresh_task.rs
rename to android/composd/src/odrefresh_task.rs
diff --git a/compos/composd/src/service.rs b/android/composd/src/service.rs
similarity index 100%
rename from compos/composd/src/service.rs
rename to android/composd/src/service.rs
diff --git a/compos/composd_cmd/Android.bp b/android/composd_cmd/Android.bp
similarity index 100%
rename from compos/composd_cmd/Android.bp
rename to android/composd_cmd/Android.bp
diff --git a/compos/composd_cmd/composd_cmd.rs b/android/composd_cmd/composd_cmd.rs
similarity index 100%
rename from compos/composd_cmd/composd_cmd.rs
rename to android/composd_cmd/composd_cmd.rs
diff --git a/authfs/fd_server/Android.bp b/android/fd_server/Android.bp
similarity index 100%
rename from authfs/fd_server/Android.bp
rename to android/fd_server/Android.bp
diff --git a/authfs/TEST_MAPPING b/android/fd_server/TEST_MAPPING
similarity index 100%
copy from authfs/TEST_MAPPING
copy to android/fd_server/TEST_MAPPING
diff --git a/authfs/fd_server/src/aidl.rs b/android/fd_server/src/aidl.rs
similarity index 100%
rename from authfs/fd_server/src/aidl.rs
rename to android/fd_server/src/aidl.rs
diff --git a/authfs/fd_server/src/main.rs b/android/fd_server/src/main.rs
similarity index 100%
rename from authfs/fd_server/src/main.rs
rename to android/fd_server/src/main.rs
diff --git a/virtualizationmanager/Android.bp b/android/virtmgr/Android.bp
similarity index 93%
rename from virtualizationmanager/Android.bp
rename to android/virtmgr/Android.bp
index ae85934..a21ee6c 100644
--- a/virtualizationmanager/Android.bp
+++ b/android/virtmgr/Android.bp
@@ -87,6 +87,14 @@
     apex_available: ["com.android.virt"],
 }
 
+rust_binary {
+    name: "early_virtmgr",
+    defaults: ["virtualizationmanager_defaults"],
+    srcs: ["src/main.rs"],
+    cfgs: ["early"],
+    apex_available: ["com.android.virt"],
+}
+
 rust_test {
     name: "virtualizationmanager_device_test",
     srcs: ["src/main.rs"],
diff --git a/virtualizationmanager/TEST_MAPPING b/android/virtmgr/TEST_MAPPING
similarity index 100%
rename from virtualizationmanager/TEST_MAPPING
rename to android/virtmgr/TEST_MAPPING
diff --git a/virtualizationmanager/fsfdt/Android.bp b/android/virtmgr/fsfdt/Android.bp
similarity index 100%
rename from virtualizationmanager/fsfdt/Android.bp
rename to android/virtmgr/fsfdt/Android.bp
diff --git a/virtualizationmanager/fsfdt/src/lib.rs b/android/virtmgr/fsfdt/src/lib.rs
similarity index 100%
rename from virtualizationmanager/fsfdt/src/lib.rs
rename to android/virtmgr/fsfdt/src/lib.rs
diff --git a/virtualizationmanager/fsfdt/src/main.rs b/android/virtmgr/fsfdt/src/main.rs
similarity index 100%
rename from virtualizationmanager/fsfdt/src/main.rs
rename to android/virtmgr/fsfdt/src/main.rs
diff --git a/virtualizationmanager/fsfdt/testdata/fs/avf/reference/oem/stub b/android/virtmgr/fsfdt/testdata/fs/avf/reference/oem/stub
similarity index 100%
rename from virtualizationmanager/fsfdt/testdata/fs/avf/reference/oem/stub
rename to android/virtmgr/fsfdt/testdata/fs/avf/reference/oem/stub
Binary files differ
diff --git a/virtualizationmanager/fsfdt/testdata/fs/avf/reference/vendor/empty b/android/virtmgr/fsfdt/testdata/fs/avf/reference/vendor/empty
similarity index 100%
rename from virtualizationmanager/fsfdt/testdata/fs/avf/reference/vendor/empty
rename to android/virtmgr/fsfdt/testdata/fs/avf/reference/vendor/empty
diff --git a/virtualizationmanager/fsfdt/testdata/fs/avf/reference/vendor/vendor_extra_node/flag b/android/virtmgr/fsfdt/testdata/fs/avf/reference/vendor/vendor_extra_node/flag
similarity index 100%
rename from virtualizationmanager/fsfdt/testdata/fs/avf/reference/vendor/vendor_extra_node/flag
rename to android/virtmgr/fsfdt/testdata/fs/avf/reference/vendor/vendor_extra_node/flag
Binary files differ
diff --git a/virtualizationmanager/fsfdt/testdata/fs/avf/reference/vendor_hashtree_descriptor_root_digest b/android/virtmgr/fsfdt/testdata/fs/avf/reference/vendor_hashtree_descriptor_root_digest
similarity index 100%
rename from virtualizationmanager/fsfdt/testdata/fs/avf/reference/vendor_hashtree_descriptor_root_digest
rename to android/virtmgr/fsfdt/testdata/fs/avf/reference/vendor_hashtree_descriptor_root_digest
Binary files differ
diff --git a/virtualizationmanager/fsfdt/testdata/fs/avf/reference/vendor_image_key b/android/virtmgr/fsfdt/testdata/fs/avf/reference/vendor_image_key
similarity index 100%
rename from virtualizationmanager/fsfdt/testdata/fs/avf/reference/vendor_image_key
rename to android/virtmgr/fsfdt/testdata/fs/avf/reference/vendor_image_key
Binary files differ
diff --git a/virtualizationmanager/fuzzer.cpp b/android/virtmgr/fuzzer.cpp
similarity index 100%
rename from virtualizationmanager/fuzzer.cpp
rename to android/virtmgr/fuzzer.cpp
diff --git a/virtualizationmanager/src/aidl.rs b/android/virtmgr/src/aidl.rs
similarity index 100%
rename from virtualizationmanager/src/aidl.rs
rename to android/virtmgr/src/aidl.rs
diff --git a/virtualizationmanager/src/atom.rs b/android/virtmgr/src/atom.rs
similarity index 100%
rename from virtualizationmanager/src/atom.rs
rename to android/virtmgr/src/atom.rs
diff --git a/virtualizationmanager/src/composite.rs b/android/virtmgr/src/composite.rs
similarity index 100%
rename from virtualizationmanager/src/composite.rs
rename to android/virtmgr/src/composite.rs
diff --git a/virtualizationmanager/src/crosvm.rs b/android/virtmgr/src/crosvm.rs
similarity index 99%
rename from virtualizationmanager/src/crosvm.rs
rename to android/virtmgr/src/crosvm.rs
index 4d19670..f9fbd16 100644
--- a/virtualizationmanager/src/crosvm.rs
+++ b/android/virtmgr/src/crosvm.rs
@@ -369,6 +369,7 @@
     /// Callbacks to clients of the VM.
     pub callbacks: VirtualMachineCallbacks,
     /// VirtualMachineService binder object for the VM.
+    #[allow(dead_code)]
     pub vm_service: Mutex<Option<Strong<dyn IVirtualMachineService>>>,
     /// Recorded metrics of VM such as timestamp or cpu / memory usage.
     pub vm_metric: Mutex<VmMetric>,
diff --git a/virtualizationmanager/src/debug_config.rs b/android/virtmgr/src/debug_config.rs
similarity index 100%
rename from virtualizationmanager/src/debug_config.rs
rename to android/virtmgr/src/debug_config.rs
diff --git a/virtualizationmanager/src/dt_overlay.rs b/android/virtmgr/src/dt_overlay.rs
similarity index 100%
rename from virtualizationmanager/src/dt_overlay.rs
rename to android/virtmgr/src/dt_overlay.rs
diff --git a/virtualizationmanager/src/main.rs b/android/virtmgr/src/main.rs
similarity index 96%
rename from virtualizationmanager/src/main.rs
rename to android/virtmgr/src/main.rs
index 4e88507..445260f 100644
--- a/virtualizationmanager/src/main.rs
+++ b/android/virtmgr/src/main.rs
@@ -131,7 +131,11 @@
     // Start thread pool for kernel Binder connection to VirtualizationServiceInternal.
     ProcessState::start_thread_pool();
 
-    GLOBAL_SERVICE.removeMemlockRlimit().expect("Failed to remove memlock rlimit");
+    if cfg!(early) {
+        panic!("Early VM not implemented");
+    } else {
+        GLOBAL_SERVICE.removeMemlockRlimit().expect("Failed to remove memlock rlimit");
+    }
 
     let service = VirtualizationService::init();
     let service =
diff --git a/virtualizationmanager/src/payload.rs b/android/virtmgr/src/payload.rs
similarity index 100%
rename from virtualizationmanager/src/payload.rs
rename to android/virtmgr/src/payload.rs
diff --git a/virtualizationmanager/src/selinux.rs b/android/virtmgr/src/selinux.rs
similarity index 100%
rename from virtualizationmanager/src/selinux.rs
rename to android/virtmgr/src/selinux.rs
diff --git a/virtualizationservice/Android.bp b/android/virtualizationservice/Android.bp
similarity index 100%
rename from virtualizationservice/Android.bp
rename to android/virtualizationservice/Android.bp
diff --git a/virtualizationservice/TEST_MAPPING b/android/virtualizationservice/TEST_MAPPING
similarity index 100%
rename from virtualizationservice/TEST_MAPPING
rename to android/virtualizationservice/TEST_MAPPING
diff --git a/virtualizationservice/aidl/Android.bp b/android/virtualizationservice/aidl/Android.bp
similarity index 100%
rename from virtualizationservice/aidl/Android.bp
rename to android/virtualizationservice/aidl/Android.bp
diff --git a/virtualizationservice/aidl/android/system/virtualizationcommon/Certificate.aidl b/android/virtualizationservice/aidl/android/system/virtualizationcommon/Certificate.aidl
similarity index 100%
rename from virtualizationservice/aidl/android/system/virtualizationcommon/Certificate.aidl
rename to android/virtualizationservice/aidl/android/system/virtualizationcommon/Certificate.aidl
diff --git a/virtualizationservice/aidl/android/system/virtualizationcommon/DeathReason.aidl b/android/virtualizationservice/aidl/android/system/virtualizationcommon/DeathReason.aidl
similarity index 100%
rename from virtualizationservice/aidl/android/system/virtualizationcommon/DeathReason.aidl
rename to android/virtualizationservice/aidl/android/system/virtualizationcommon/DeathReason.aidl
diff --git a/virtualizationservice/aidl/android/system/virtualizationcommon/ErrorCode.aidl b/android/virtualizationservice/aidl/android/system/virtualizationcommon/ErrorCode.aidl
similarity index 100%
rename from virtualizationservice/aidl/android/system/virtualizationcommon/ErrorCode.aidl
rename to android/virtualizationservice/aidl/android/system/virtualizationcommon/ErrorCode.aidl
diff --git a/virtualizationservice/aidl/android/system/virtualizationmaintenance/IVirtualizationMaintenance.aidl b/android/virtualizationservice/aidl/android/system/virtualizationmaintenance/IVirtualizationMaintenance.aidl
similarity index 100%
rename from virtualizationservice/aidl/android/system/virtualizationmaintenance/IVirtualizationMaintenance.aidl
rename to android/virtualizationservice/aidl/android/system/virtualizationmaintenance/IVirtualizationMaintenance.aidl
diff --git a/virtualizationservice/aidl/android/system/virtualizationmaintenance/IVirtualizationReconciliationCallback.aidl b/android/virtualizationservice/aidl/android/system/virtualizationmaintenance/IVirtualizationReconciliationCallback.aidl
similarity index 100%
rename from virtualizationservice/aidl/android/system/virtualizationmaintenance/IVirtualizationReconciliationCallback.aidl
rename to android/virtualizationservice/aidl/android/system/virtualizationmaintenance/IVirtualizationReconciliationCallback.aidl
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice/AssignableDevice.aidl b/android/virtualizationservice/aidl/android/system/virtualizationservice/AssignableDevice.aidl
similarity index 100%
rename from virtualizationservice/aidl/android/system/virtualizationservice/AssignableDevice.aidl
rename to android/virtualizationservice/aidl/android/system/virtualizationservice/AssignableDevice.aidl
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice/AudioConfig.aidl b/android/virtualizationservice/aidl/android/system/virtualizationservice/AudioConfig.aidl
similarity index 100%
rename from virtualizationservice/aidl/android/system/virtualizationservice/AudioConfig.aidl
rename to android/virtualizationservice/aidl/android/system/virtualizationservice/AudioConfig.aidl
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice/CpuTopology.aidl b/android/virtualizationservice/aidl/android/system/virtualizationservice/CpuTopology.aidl
similarity index 100%
rename from virtualizationservice/aidl/android/system/virtualizationservice/CpuTopology.aidl
rename to android/virtualizationservice/aidl/android/system/virtualizationservice/CpuTopology.aidl
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice/DiskImage.aidl b/android/virtualizationservice/aidl/android/system/virtualizationservice/DiskImage.aidl
similarity index 100%
rename from virtualizationservice/aidl/android/system/virtualizationservice/DiskImage.aidl
rename to android/virtualizationservice/aidl/android/system/virtualizationservice/DiskImage.aidl
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice/DisplayConfig.aidl b/android/virtualizationservice/aidl/android/system/virtualizationservice/DisplayConfig.aidl
similarity index 100%
rename from virtualizationservice/aidl/android/system/virtualizationservice/DisplayConfig.aidl
rename to android/virtualizationservice/aidl/android/system/virtualizationservice/DisplayConfig.aidl
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice/GpuConfig.aidl b/android/virtualizationservice/aidl/android/system/virtualizationservice/GpuConfig.aidl
similarity index 100%
rename from virtualizationservice/aidl/android/system/virtualizationservice/GpuConfig.aidl
rename to android/virtualizationservice/aidl/android/system/virtualizationservice/GpuConfig.aidl
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualMachine.aidl b/android/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualMachine.aidl
similarity index 100%
rename from virtualizationservice/aidl/android/system/virtualizationservice/IVirtualMachine.aidl
rename to android/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualMachine.aidl
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualMachineCallback.aidl b/android/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualMachineCallback.aidl
similarity index 100%
rename from virtualizationservice/aidl/android/system/virtualizationservice/IVirtualMachineCallback.aidl
rename to android/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualMachineCallback.aidl
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualizationService.aidl b/android/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualizationService.aidl
similarity index 100%
rename from virtualizationservice/aidl/android/system/virtualizationservice/IVirtualizationService.aidl
rename to android/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualizationService.aidl
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice/InputDevice.aidl b/android/virtualizationservice/aidl/android/system/virtualizationservice/InputDevice.aidl
similarity index 100%
rename from virtualizationservice/aidl/android/system/virtualizationservice/InputDevice.aidl
rename to android/virtualizationservice/aidl/android/system/virtualizationservice/InputDevice.aidl
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice/Partition.aidl b/android/virtualizationservice/aidl/android/system/virtualizationservice/Partition.aidl
similarity index 100%
rename from virtualizationservice/aidl/android/system/virtualizationservice/Partition.aidl
rename to android/virtualizationservice/aidl/android/system/virtualizationservice/Partition.aidl
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice/PartitionType.aidl b/android/virtualizationservice/aidl/android/system/virtualizationservice/PartitionType.aidl
similarity index 100%
rename from virtualizationservice/aidl/android/system/virtualizationservice/PartitionType.aidl
rename to android/virtualizationservice/aidl/android/system/virtualizationservice/PartitionType.aidl
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineAppConfig.aidl b/android/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineAppConfig.aidl
similarity index 100%
rename from virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineAppConfig.aidl
rename to android/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineAppConfig.aidl
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineConfig.aidl b/android/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineConfig.aidl
similarity index 100%
rename from virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineConfig.aidl
rename to android/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineConfig.aidl
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineDebugInfo.aidl b/android/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineDebugInfo.aidl
similarity index 100%
rename from virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineDebugInfo.aidl
rename to android/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineDebugInfo.aidl
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachinePayloadConfig.aidl b/android/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachinePayloadConfig.aidl
similarity index 100%
rename from virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachinePayloadConfig.aidl
rename to android/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachinePayloadConfig.aidl
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineRawConfig.aidl b/android/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineRawConfig.aidl
similarity index 100%
rename from virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineRawConfig.aidl
rename to android/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineRawConfig.aidl
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineState.aidl b/android/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineState.aidl
similarity index 100%
rename from virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineState.aidl
rename to android/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineState.aidl
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice_internal/AtomVmBooted.aidl b/android/virtualizationservice/aidl/android/system/virtualizationservice_internal/AtomVmBooted.aidl
similarity index 100%
rename from virtualizationservice/aidl/android/system/virtualizationservice_internal/AtomVmBooted.aidl
rename to android/virtualizationservice/aidl/android/system/virtualizationservice_internal/AtomVmBooted.aidl
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice_internal/AtomVmCreationRequested.aidl b/android/virtualizationservice/aidl/android/system/virtualizationservice_internal/AtomVmCreationRequested.aidl
similarity index 100%
rename from virtualizationservice/aidl/android/system/virtualizationservice_internal/AtomVmCreationRequested.aidl
rename to android/virtualizationservice/aidl/android/system/virtualizationservice_internal/AtomVmCreationRequested.aidl
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice_internal/AtomVmExited.aidl b/android/virtualizationservice/aidl/android/system/virtualizationservice_internal/AtomVmExited.aidl
similarity index 100%
rename from virtualizationservice/aidl/android/system/virtualizationservice_internal/AtomVmExited.aidl
rename to android/virtualizationservice/aidl/android/system/virtualizationservice_internal/AtomVmExited.aidl
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice_internal/IBoundDevice.aidl b/android/virtualizationservice/aidl/android/system/virtualizationservice_internal/IBoundDevice.aidl
similarity index 100%
rename from virtualizationservice/aidl/android/system/virtualizationservice_internal/IBoundDevice.aidl
rename to android/virtualizationservice/aidl/android/system/virtualizationservice_internal/IBoundDevice.aidl
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice_internal/IGlobalVmContext.aidl b/android/virtualizationservice/aidl/android/system/virtualizationservice_internal/IGlobalVmContext.aidl
similarity index 100%
rename from virtualizationservice/aidl/android/system/virtualizationservice_internal/IGlobalVmContext.aidl
rename to android/virtualizationservice/aidl/android/system/virtualizationservice_internal/IGlobalVmContext.aidl
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVfioHandler.aidl b/android/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVfioHandler.aidl
similarity index 100%
rename from virtualizationservice/aidl/android/system/virtualizationservice_internal/IVfioHandler.aidl
rename to android/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVfioHandler.aidl
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVirtualizationServiceInternal.aidl b/android/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVirtualizationServiceInternal.aidl
similarity index 100%
rename from virtualizationservice/aidl/android/system/virtualizationservice_internal/IVirtualizationServiceInternal.aidl
rename to android/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVirtualizationServiceInternal.aidl
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVmnic.aidl b/android/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVmnic.aidl
similarity index 100%
rename from virtualizationservice/aidl/android/system/virtualizationservice_internal/IVmnic.aidl
rename to android/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVmnic.aidl
diff --git a/virtualizationservice/aidl/android/system/virtualmachineservice/IVirtualMachineService.aidl b/android/virtualizationservice/aidl/android/system/virtualmachineservice/IVirtualMachineService.aidl
similarity index 100%
rename from virtualizationservice/aidl/android/system/virtualmachineservice/IVirtualMachineService.aidl
rename to android/virtualizationservice/aidl/android/system/virtualmachineservice/IVirtualMachineService.aidl
diff --git a/virtualizationservice/aidl/android/system/vmtethering/IVmTethering.aidl b/android/virtualizationservice/aidl/android/system/vmtethering/IVmTethering.aidl
similarity index 100%
rename from virtualizationservice/aidl/android/system/vmtethering/IVmTethering.aidl
rename to android/virtualizationservice/aidl/android/system/vmtethering/IVmTethering.aidl
diff --git a/virtualizationservice/assignable_devices.xsd b/android/virtualizationservice/assignable_devices.xsd
similarity index 100%
rename from virtualizationservice/assignable_devices.xsd
rename to android/virtualizationservice/assignable_devices.xsd
diff --git a/virtualizationservice/schema/current.txt b/android/virtualizationservice/schema/current.txt
similarity index 100%
rename from virtualizationservice/schema/current.txt
rename to android/virtualizationservice/schema/current.txt
diff --git a/virtualizationservice/schema/last_current.txt b/android/virtualizationservice/schema/last_current.txt
similarity index 100%
rename from virtualizationservice/schema/last_current.txt
rename to android/virtualizationservice/schema/last_current.txt
diff --git a/virtualizationservice/schema/last_removed.txt b/android/virtualizationservice/schema/last_removed.txt
similarity index 100%
rename from virtualizationservice/schema/last_removed.txt
rename to android/virtualizationservice/schema/last_removed.txt
diff --git a/virtualizationservice/schema/removed.txt b/android/virtualizationservice/schema/removed.txt
similarity index 100%
rename from virtualizationservice/schema/removed.txt
rename to android/virtualizationservice/schema/removed.txt
diff --git a/virtualizationservice/src/aidl.rs b/android/virtualizationservice/src/aidl.rs
similarity index 100%
rename from virtualizationservice/src/aidl.rs
rename to android/virtualizationservice/src/aidl.rs
diff --git a/virtualizationservice/src/atom.rs b/android/virtualizationservice/src/atom.rs
similarity index 100%
rename from virtualizationservice/src/atom.rs
rename to android/virtualizationservice/src/atom.rs
diff --git a/virtualizationservice/src/main.rs b/android/virtualizationservice/src/main.rs
similarity index 100%
rename from virtualizationservice/src/main.rs
rename to android/virtualizationservice/src/main.rs
diff --git a/virtualizationservice/src/maintenance.rs b/android/virtualizationservice/src/maintenance.rs
similarity index 100%
rename from virtualizationservice/src/maintenance.rs
rename to android/virtualizationservice/src/maintenance.rs
diff --git a/virtualizationservice/src/maintenance/vmdb.rs b/android/virtualizationservice/src/maintenance/vmdb.rs
similarity index 100%
rename from virtualizationservice/src/maintenance/vmdb.rs
rename to android/virtualizationservice/src/maintenance/vmdb.rs
diff --git a/virtualizationservice/src/remote_provisioning.rs b/android/virtualizationservice/src/remote_provisioning.rs
similarity index 100%
rename from virtualizationservice/src/remote_provisioning.rs
rename to android/virtualizationservice/src/remote_provisioning.rs
diff --git a/virtualizationservice/src/rkpvm.rs b/android/virtualizationservice/src/rkpvm.rs
similarity index 100%
rename from virtualizationservice/src/rkpvm.rs
rename to android/virtualizationservice/src/rkpvm.rs
diff --git a/virtualizationservice/testdata/rkp_cert_chain.der b/android/virtualizationservice/testdata/rkp_cert_chain.der
similarity index 100%
rename from virtualizationservice/testdata/rkp_cert_chain.der
rename to android/virtualizationservice/testdata/rkp_cert_chain.der
Binary files differ
diff --git a/virtualizationservice/vfio_handler/Android.bp b/android/virtualizationservice/vfio_handler/Android.bp
similarity index 100%
rename from virtualizationservice/vfio_handler/Android.bp
rename to android/virtualizationservice/vfio_handler/Android.bp
diff --git a/virtualizationservice/vfio_handler/src/aidl.rs b/android/virtualizationservice/vfio_handler/src/aidl.rs
similarity index 100%
rename from virtualizationservice/vfio_handler/src/aidl.rs
rename to android/virtualizationservice/vfio_handler/src/aidl.rs
diff --git a/virtualizationservice/vfio_handler/src/main.rs b/android/virtualizationservice/vfio_handler/src/main.rs
similarity index 100%
rename from virtualizationservice/vfio_handler/src/main.rs
rename to android/virtualizationservice/vfio_handler/src/main.rs
diff --git a/virtualizationservice/vmnic/Android.bp b/android/virtualizationservice/vmnic/Android.bp
similarity index 100%
rename from virtualizationservice/vmnic/Android.bp
rename to android/virtualizationservice/vmnic/Android.bp
diff --git a/virtualizationservice/vmnic/src/aidl.rs b/android/virtualizationservice/vmnic/src/aidl.rs
similarity index 100%
rename from virtualizationservice/vmnic/src/aidl.rs
rename to android/virtualizationservice/vmnic/src/aidl.rs
diff --git a/virtualizationservice/vmnic/src/main.rs b/android/virtualizationservice/vmnic/src/main.rs
similarity index 100%
rename from virtualizationservice/vmnic/src/main.rs
rename to android/virtualizationservice/vmnic/src/main.rs
diff --git a/vm/Android.bp b/android/vm/Android.bp
similarity index 100%
rename from vm/Android.bp
rename to android/vm/Android.bp
diff --git a/vm/TEST_MAPPING b/android/vm/TEST_MAPPING
similarity index 100%
rename from vm/TEST_MAPPING
rename to android/vm/TEST_MAPPING
diff --git a/vm/src/create_idsig.rs b/android/vm/src/create_idsig.rs
similarity index 100%
rename from vm/src/create_idsig.rs
rename to android/vm/src/create_idsig.rs
diff --git a/vm/src/create_partition.rs b/android/vm/src/create_partition.rs
similarity index 100%
rename from vm/src/create_partition.rs
rename to android/vm/src/create_partition.rs
diff --git a/vm/src/main.rs b/android/vm/src/main.rs
similarity index 100%
rename from vm/src/main.rs
rename to android/vm/src/main.rs
diff --git a/vm/src/run.rs b/android/vm/src/run.rs
similarity index 100%
rename from vm/src/run.rs
rename to android/vm/src/run.rs
diff --git a/vm/vm_shell.sh b/android/vm/vm_shell.sh
similarity index 100%
rename from vm/vm_shell.sh
rename to android/vm/vm_shell.sh
diff --git a/demo_native/Android.bp b/android/vm_demo_native/Android.bp
similarity index 100%
rename from demo_native/Android.bp
rename to android/vm_demo_native/Android.bp
diff --git a/demo_native/README.md b/android/vm_demo_native/README.md
similarity index 97%
rename from demo_native/README.md
rename to android/vm_demo_native/README.md
index 700ca83..4bbbc28 100644
--- a/demo_native/README.md
+++ b/android/vm_demo_native/README.md
@@ -8,7 +8,7 @@
 like creating and using a VM from a HAL process.
 
 For non-system-level VMs, you must use the Java APIs from an Android app. See the [Java demo
-app](../demo/README.md).
+app](../MicrodroidDemoApp/README.md).
 
 ## Building
 
diff --git a/demo_native/main.cpp b/android/vm_demo_native/main.cpp
similarity index 100%
rename from demo_native/main.cpp
rename to android/vm_demo_native/main.cpp
diff --git a/apex b/apex
new file mode 120000
index 0000000..db9c327
--- /dev/null
+++ b/apex
@@ -0,0 +1 @@
+build/apex
\ No newline at end of file
diff --git a/apex/Android.bp b/apex/Android.bp
deleted file mode 100644
index 5e74aca..0000000
--- a/apex/Android.bp
+++ /dev/null
@@ -1,428 +0,0 @@
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-microdroid_filesystem_images = [
-    "microdroid_super",
-    "microdroid_vbmeta",
-]
-
-soong_config_module_type {
-    name: "virt_apex",
-    module_type: "apex",
-    config_namespace: "ANDROID",
-    bool_variables: [
-        "avf_enabled",
-    ],
-    properties: [
-        "defaults",
-        "prebuilts",
-    ],
-}
-
-virt_apex {
-    name: "com.android.virt",
-    soong_config_variables: {
-        avf_enabled: {
-            defaults: ["com.android.virt_avf_enabled"],
-            conditions_default: {
-                defaults: ["com.android.virt_avf_disabled"],
-            },
-        },
-    },
-}
-
-soong_config_string_variable {
-    name: "avf_microdroid_guest_gki_version",
-    values: [
-        "android15_66",
-    ],
-}
-
-soong_config_module_type {
-    name: "avf_flag_aware_apex_defaults",
-    module_type: "apex_defaults",
-    config_namespace: "ANDROID",
-    bool_variables: [
-        "release_avf_enable_device_assignment",
-        "release_avf_enable_llpvm_changes",
-        "release_avf_enable_network",
-        "avf_remote_attestation_enabled",
-        "release_avf_enable_vendor_modules",
-        "release_avf_enable_virt_cpufreq",
-        "release_avf_support_custom_vm_with_paravirtualized_devices",
-    ],
-    variables: [
-        "avf_microdroid_guest_gki_version",
-    ],
-    properties: [
-        "androidManifest",
-        "arch",
-        "canned_fs_config",
-        "prebuilts",
-        "systemserverclasspath_fragments",
-        "vintf_fragments",
-        "apps",
-    ],
-}
-
-avf_flag_aware_apex_defaults {
-    name: "com.android.virt_common",
-    // TODO(jiyong): make it updatable
-    updatable: false,
-    future_updatable: false,
-    platform_apis: true,
-
-    manifest: "manifest.json",
-
-    key: "com.android.virt.key",
-    certificate: ":com.android.virt.certificate",
-
-    apps: [
-        "android.system.virtualmachine.res",
-    ],
-
-    file_contexts: ":com.android.virt-file_contexts",
-
-    bootclasspath_fragments: [
-        "com.android.virt-bootclasspath-fragment",
-    ],
-    jni_libs: [
-        "libvirtualizationservice_jni",
-        "libvirtualmachine_jni",
-    ],
-    // TODO(b/295593640) Unfortunately these are added to the apex even though they are unused.
-    // Once the build system is fixed, remove this.
-    unwanted_transitive_deps: [
-        "libsso",
-        "libutils",
-    ],
-    soong_config_variables: {
-        release_avf_enable_llpvm_changes: {
-            systemserverclasspath_fragments: [
-                "com.android.virt-systemserver-fragment",
-            ],
-        },
-        release_avf_enable_virt_cpufreq: {
-            canned_fs_config: "canned_fs_config_sys_nice",
-            conditions_default: {
-                canned_fs_config: "canned_fs_config",
-            },
-        },
-        release_avf_support_custom_vm_with_paravirtualized_devices: {
-            apps: [
-                "VmLauncherApp",
-            ],
-        },
-    },
-}
-
-avf_flag_aware_apex_defaults {
-    name: "com.android.virt_avf_enabled",
-
-    defaults: ["com.android.virt_common"],
-
-    custom_sign_tool: "sign_virt_apex",
-
-    // crosvm and virtualizationservice are only enabled for 64-bit targets on device
-    arch: {
-        arm64: {
-            binaries: [
-                "crosvm",
-                "virtmgr",
-                "virtualizationservice",
-            ],
-            filesystems: microdroid_filesystem_images,
-            prebuilts: [
-                "rialto_bin",
-            ],
-        },
-        x86_64: {
-            binaries: [
-                "crosvm",
-                "virtmgr",
-                "virtualizationservice",
-            ],
-            filesystems: microdroid_filesystem_images,
-        },
-    },
-    binaries: [
-        "fd_server",
-        "vm",
-    ],
-    prebuilts: [
-        "features_com.android.virt.xml",
-        "microdroid_initrd_debuggable",
-        "microdroid_initrd_normal",
-        "microdroid.json",
-        "microdroid_kernel",
-        "com.android.virt.init.rc",
-        "android_bootloader_crosvm_aarch64",
-    ],
-    host_required: [
-        "vm_shell",
-    ],
-    apps: [
-        "EmptyPayloadApp",
-    ],
-    soong_config_variables: {
-        avf_microdroid_guest_gki_version: {
-            android15_66: {
-                prebuilts: [
-                    "microdroid_gki-android15-6.6_initrd_debuggable",
-                    "microdroid_gki-android15-6.6_initrd_normal",
-                    "microdroid_gki-android15-6.6_kernel",
-                    "microdroid_gki-android15-6.6.json",
-                ],
-            },
-        },
-        release_avf_enable_device_assignment: {
-            prebuilts: [
-                "com.android.virt.vfio_handler.rc",
-            ],
-            arch: {
-                arm64: {
-                    binaries: ["vfio_handler"],
-                },
-                x86_64: {
-                    binaries: ["vfio_handler"],
-                },
-            },
-        },
-        release_avf_enable_llpvm_changes: {
-            androidManifest: "AndroidManifest.xml",
-        },
-        release_avf_enable_network: {
-            prebuilts: [
-                "com.android.virt.vmnic.rc",
-            ],
-            arch: {
-                arm64: {
-                    binaries: ["vmnic"],
-                },
-                x86_64: {
-                    binaries: ["vmnic"],
-                },
-            },
-        },
-        avf_remote_attestation_enabled: {
-            vintf_fragments: [
-                "virtualizationservice.xml",
-            ],
-        },
-    },
-}
-
-apex_defaults {
-    name: "com.android.virt_avf_disabled",
-
-    defaults: ["com.android.virt_common"],
-}
-
-apex_key {
-    name: "com.android.virt.key",
-    public_key: "com.android.virt.avbpubkey",
-    private_key: "com.android.virt.pem",
-}
-
-android_app_certificate {
-    name: "com.android.virt.certificate",
-    certificate: "com.android.virt",
-}
-
-soong_config_module_type {
-    name: "avf_flag_aware_genrule",
-    module_type: "genrule",
-    config_namespace: "ANDROID",
-    bool_variables: [
-        "release_avf_enable_llpvm_changes",
-        "avf_remote_attestation_enabled",
-    ],
-    properties: ["srcs"],
-}
-
-avf_flag_aware_genrule {
-    name: "virtualizationservice_rc_combined",
-    srcs: ["virtualizationservice.rc.base"],
-    soong_config_variables: {
-        release_avf_enable_llpvm_changes: {
-            srcs: ["virtualizationservice.rc.llpvm"],
-        },
-        avf_remote_attestation_enabled: {
-            srcs: ["virtualizationservice.rc.ra"],
-        },
-    },
-    out: ["virtualizationservice.rc"],
-    cmd: "cat $(in) > $(out)",
-}
-
-prebuilt_etc {
-    name: "com.android.virt.init.rc",
-    src: ":virtualizationservice_rc_combined",
-    filename: "virtualizationservice.rc",
-    no_full_install: true,
-}
-
-prebuilt_etc {
-    name: "com.android.virt.vfio_handler.rc",
-    src: "vfio_handler.rc",
-    filename: "vfio_handler.rc",
-    no_full_install: true,
-}
-
-prebuilt_etc {
-    name: "com.android.virt.vmnic.rc",
-    src: "vmnic.rc",
-    filename: "vmnic.rc",
-    no_full_install: true,
-}
-
-// Virt apex needs a custom signer for its payload
-python_binary_host {
-    name: "sign_virt_apex",
-    srcs: [
-        "sign_virt_apex.py",
-    ],
-    version: {
-        py3: {
-            embedded_launcher: true,
-        },
-    },
-    required: [
-        // sign_virt_apex should be runnable from outside the source tree,
-        // therefore, any required tool should be listed in build/make/core/Makefile as well.
-        "img2simg",
-        "initrd_bootconfig",
-        "lpmake",
-        "lpunpack",
-        "lz4",
-        "simg2img",
-    ],
-}
-
-sh_test_host {
-    name: "sign_virt_apex_test",
-    src: "sign_virt_apex_test.sh",
-    test_config: "sign_virt_apex_test.xml",
-    data_bins: [
-        // deapexer
-        "deapexer",
-        "debugfs_static",
-        "fsck.erofs",
-
-        // sign_virt_apex
-        "avbtool",
-        "img2simg",
-        "initrd_bootconfig",
-        "lpmake",
-        "lpunpack",
-        "lz4",
-        "sign_virt_apex",
-        "simg2img",
-    ],
-    data_libs: [
-        "libbase",
-        "libc++",
-        "libcrypto_utils",
-        "libcrypto",
-        "libext4_utils",
-        "liblog",
-        "liblp",
-        "libsparse",
-        "libz",
-    ],
-    data: [
-        ":com.android.virt",
-        ":test.com.android.virt.pem",
-    ],
-    test_suites: ["general-tests"],
-}
-
-filegroup {
-    name: "test.com.android.virt.pem",
-    srcs: ["test.com.android.virt.pem"],
-}
-
-filegroup {
-    name: "test2.com.android.virt.pem",
-    srcs: ["test2.com.android.virt.pem"],
-}
-
-// custom tool to replace bytes in a file
-python_binary_host {
-    name: "replace_bytes",
-    srcs: [
-        "replace_bytes.py",
-    ],
-    version: {
-        py3: {
-            embedded_launcher: true,
-        },
-    },
-}
-
-// Encapsulate the contributions made by the com.android.virt to the bootclasspath.
-bootclasspath_fragment {
-    name: "com.android.virt-bootclasspath-fragment",
-    contents: ["framework-virtualization"],
-    apex_available: ["com.android.virt"],
-
-    // The bootclasspath_fragments that provide APIs on which this depends.
-    fragments: [
-        {
-            apex: "com.android.art",
-            module: "art-bootclasspath-fragment",
-        },
-    ],
-
-    // Additional stubs libraries that this fragment's contents use which are
-    // not provided by another bootclasspath_fragment.
-    additional_stubs: [
-        "android-non-updatable",
-    ],
-
-    hidden_api: {
-
-        // This module does not contain any split packages.
-        split_packages: [],
-
-        // The following packages and all their subpackages currently only
-        // contain classes from this bootclasspath_fragment. Listing a package
-        // here won't prevent other bootclasspath modules from adding classes in
-        // any of those packages but it will prevent them from adding those
-        // classes into an API surface, e.g. public, system, etc.. Doing so will
-        // result in a build failure due to inconsistent flags.
-        package_prefixes: [
-            "android.system.virtualmachine",
-            "android.system.virtualizationservice",
-            // android.sysprop.*, renamed by jarjar
-            "com.android.system.virtualmachine.sysprop",
-        ],
-    },
-}
-
-soong_config_module_type {
-    name: "avf_flag_aware_systemserverclasspath_fragment",
-    module_type: "systemserverclasspath_fragment",
-    config_namespace: "ANDROID",
-    bool_variables: [
-        "release_avf_enable_llpvm_changes",
-    ],
-    properties: [
-        "enabled",
-    ],
-}
-
-avf_flag_aware_systemserverclasspath_fragment {
-    name: "com.android.virt-systemserver-fragment",
-    contents: [
-        "service-virtualization",
-    ],
-    apex_available: ["com.android.virt"],
-    enabled: false,
-    soong_config_variables: {
-        release_avf_enable_llpvm_changes: {
-            enabled: true,
-        },
-    },
-}
diff --git a/build/Android.bp b/build/Android.bp
new file mode 100644
index 0000000..66cc626
--- /dev/null
+++ b/build/Android.bp
@@ -0,0 +1,88 @@
+//
+// 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.
+
+package {
+    default_team: "trendy_team_virtualization",
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+rust_defaults {
+    name: "avf_build_flags_rust",
+    cfgs: select(release_flag("RELEASE_AVF_ENABLE_DEVICE_ASSIGNMENT"), {
+        true: ["device_assignment"],
+        default: [],
+    }) + select(release_flag("RELEASE_AVF_ENABLE_DICE_CHANGES"), {
+        true: ["dice_changes"],
+        default: [],
+    }) + select(release_flag("RELEASE_AVF_ENABLE_LLPVM_CHANGES"), {
+        true: ["llpvm_changes"],
+        default: [],
+    }) + select(release_flag("RELEASE_AVF_ENABLE_MULTI_TENANT_MICRODROID_VM"), {
+        true: ["multi_tenant"],
+        default: [],
+    }) + select(release_flag("RELEASE_AVF_ENABLE_NETWORK"), {
+        true: ["network"],
+        default: [],
+    }) + select(release_flag("RELEASE_AVF_ENABLE_REMOTE_ATTESTATION"), {
+        true: ["remote_attestation"],
+        default: [],
+    }) + select(release_flag("RELEASE_AVF_ENABLE_VENDOR_MODULES"), {
+        true: ["vendor_modules"],
+        default: [],
+    }) + select(release_flag("RELEASE_AVF_ENABLE_VIRT_CPUFREQ"), {
+        true: ["virt_cpufreq"],
+        default: [],
+    }) + select(release_flag("RELEASE_AVF_SUPPORT_CUSTOM_VM_WITH_PARAVIRTUALIZED_DEVICES"), {
+        true: ["paravirtualized_devices"],
+        default: [],
+    }),
+}
+
+cc_defaults {
+    name: "avf_build_flags_cc",
+    cflags: select(release_flag("RELEASE_AVF_ENABLE_DICE_CHANGES"), {
+        true: ["-DAVF_OPEN_DICE_CHANGES=1"],
+        default: [],
+    }) + select(release_flag("RELEASE_AVF_ENABLE_VENDOR_MODULES"), {
+        true: ["-DAVF_ENABLE_VENDOR_MODULES=1"],
+        default: [],
+    }) + select(release_flag("RELEASE_AVF_ENABLE_VIRT_CPUFREQ"), {
+        true: ["-DAVF_ENABLE_VIRT_CPUFREQ=1"],
+        default: [],
+    }),
+}
+
+genrule_defaults {
+    name: "dts_to_dtb",
+    tools: ["dtc"],
+    cmd: "FILES=($(in)) && $(location dtc) -@ -I dts -O dtb $${FILES[-1]} -o $(out)",
+}
+
+// This is a temporary workaround until b/309090563 is implemented.
+aconfig_declarations {
+    name: "avf_aconfig_flags",
+    package: "com.android.system.virtualmachine.flags",
+    container: "com.android.virt",
+    srcs: [
+        "avf_flags.aconfig",
+    ],
+}
+
+java_aconfig_library {
+    name: "avf_aconfig_flags_java",
+    aconfig_declarations: "avf_aconfig_flags",
+    sdk_version: "module_current",
+    apex_available: ["com.android.virt"],
+}
diff --git a/build/apex/Android.bp b/build/apex/Android.bp
new file mode 100644
index 0000000..0a2b2de
--- /dev/null
+++ b/build/apex/Android.bp
@@ -0,0 +1,341 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+microdroid_filesystem_images = [
+    "microdroid_super",
+    "microdroid_vbmeta",
+]
+
+apex {
+    name: "com.android.virt",
+    defaults: select(soong_config_variable("ANDROID", "avf_enabled"), {
+        "true": ["com.android.virt_avf_enabled"],
+        default: ["com.android.virt_avf_disabled"],
+    }),
+}
+
+apex_defaults {
+    name: "com.android.virt_common",
+    // TODO(jiyong): make it updatable
+    updatable: false,
+    future_updatable: false,
+    platform_apis: true,
+
+    manifest: "manifest.json",
+
+    key: "com.android.virt.key",
+    certificate: ":com.android.virt.certificate",
+
+    apps: [
+        "android.system.virtualmachine.res",
+    ] + select(release_flag("RELEASE_AVF_SUPPORT_CUSTOM_VM_WITH_PARAVIRTUALIZED_DEVICES"), {
+        true: ["VmLauncherApp"],
+        default: [],
+    }),
+
+    file_contexts: ":com.android.virt-file_contexts",
+
+    bootclasspath_fragments: [
+        "com.android.virt-bootclasspath-fragment",
+    ],
+    jni_libs: [
+        "libvirtualizationservice_jni",
+        "libvirtualmachine_jni",
+    ],
+    // TODO(b/295593640) Unfortunately these are added to the apex even though they are unused.
+    // Once the build system is fixed, remove this.
+    unwanted_transitive_deps: [
+        "libsso",
+        "libutils",
+    ],
+
+    systemserverclasspath_fragments: select(release_flag("RELEASE_AVF_ENABLE_LLPVM_CHANGES"), {
+        true: ["com.android.virt-systemserver-fragment"],
+        default: [],
+    }),
+
+    canned_fs_config: select(release_flag("RELEASE_AVF_ENABLE_VIRT_CPUFREQ"), {
+        true: "canned_fs_config_sys_nice",
+        default: "canned_fs_config",
+    }),
+}
+
+apex_defaults {
+    name: "com.android.virt_avf_enabled",
+
+    defaults: ["com.android.virt_common"],
+
+    custom_sign_tool: "sign_virt_apex",
+
+    // crosvm and virtualizationservice are only enabled for 64-bit targets on device
+    arch: {
+        arm64: {
+            binaries: [
+                "crosvm",
+                "virtmgr",
+                "virtualizationservice",
+            ] + select(release_flag("RELEASE_AVF_ENABLE_DEVICE_ASSIGNMENT"), {
+                true: ["vfio_handler"],
+                default: [],
+            }) + select(release_flag("RELEASE_AVF_ENABLE_NETWORK"), {
+                true: ["vmnic"],
+                default: [],
+            }) + select(release_flag("RELEASE_AVF_ENABLE_EARLY_VM"), {
+                true: ["early_virtmgr"],
+                default: [],
+            }),
+            filesystems: microdroid_filesystem_images,
+            prebuilts: [
+                "rialto_bin",
+            ],
+        },
+        x86_64: {
+            binaries: [
+                "crosvm",
+                "virtmgr",
+                "virtualizationservice",
+            ] + select(release_flag("RELEASE_AVF_ENABLE_DEVICE_ASSIGNMENT"), {
+                true: ["vfio_handler"],
+                default: [],
+            }) + select(release_flag("RELEASE_AVF_ENABLE_NETWORK"), {
+                true: ["vmnic"],
+                default: [],
+            }) + select(release_flag("RELEASE_AVF_ENABLE_EARLY_VM"), {
+                true: ["early_virtmgr"],
+                default: [],
+            }),
+            filesystems: microdroid_filesystem_images,
+        },
+    },
+    binaries: [
+        "fd_server",
+        "vm",
+    ],
+    prebuilts: [
+        "features_com.android.virt.xml",
+        "microdroid_initrd_debuggable",
+        "microdroid_initrd_normal",
+        "microdroid.json",
+        "microdroid_kernel",
+        "com.android.virt.init.rc",
+        "android_bootloader_crosvm_aarch64",
+    ] + select(soong_config_variable("ANDROID", "avf_microdroid_guest_gki_version"), {
+        "android15_66": [
+            "microdroid_gki-android15-6.6_initrd_debuggable",
+            "microdroid_gki-android15-6.6_initrd_normal",
+            "microdroid_gki-android15-6.6_kernel",
+            "microdroid_gki-android15-6.6.json",
+        ],
+        default: [],
+    }) + select(release_flag("RELEASE_AVF_ENABLE_DEVICE_ASSIGNMENT"), {
+        true: ["com.android.virt.vfio_handler.rc"],
+        default: [],
+    }) + select(release_flag("RELEASE_AVF_ENABLE_NETWORK"), {
+        true: ["com.android.virt.vmnic.rc"],
+        default: [],
+    }),
+    host_required: [
+        "vm_shell",
+    ],
+    apps: [
+        "EmptyPayloadApp",
+    ],
+    androidManifest: select(release_flag("RELEASE_AVF_ENABLE_LLPVM_CHANGES"), {
+        true: "AndroidManifest.xml",
+        default: unset,
+    }),
+    vintf_fragments: select(soong_config_variable("ANDROID", "avf_remote_attestation_enabled"), {
+        "true": ["virtualizationservice.xml"],
+        default: unset,
+    }),
+}
+
+apex_defaults {
+    name: "com.android.virt_avf_disabled",
+
+    defaults: ["com.android.virt_common"],
+}
+
+apex_key {
+    name: "com.android.virt.key",
+    public_key: "com.android.virt.avbpubkey",
+    private_key: "com.android.virt.pem",
+}
+
+android_app_certificate {
+    name: "com.android.virt.certificate",
+    certificate: "com.android.virt",
+}
+
+genrule {
+    name: "virtualizationservice_rc_combined",
+    srcs: [
+        "virtualizationservice.rc.base",
+    ] + select(release_flag("RELEASE_AVF_ENABLE_LLPVM_CHANGES"), {
+        true: ["virtualizationservice.rc.llpvm"],
+        default: [],
+    }) + select(soong_config_variable("ANDROID", "avf_remote_attestation_enabled"), {
+        "true": ["virtualizationservice.rc.ra"],
+        default: [],
+    }),
+    out: ["virtualizationservice.rc"],
+    cmd: "cat $(in) > $(out)",
+}
+
+prebuilt_etc {
+    name: "com.android.virt.init.rc",
+    src: ":virtualizationservice_rc_combined",
+    filename: "virtualizationservice.rc",
+    no_full_install: true,
+}
+
+prebuilt_etc {
+    name: "com.android.virt.vfio_handler.rc",
+    src: "vfio_handler.rc",
+    filename: "vfio_handler.rc",
+    no_full_install: true,
+}
+
+prebuilt_etc {
+    name: "com.android.virt.vmnic.rc",
+    src: "vmnic.rc",
+    filename: "vmnic.rc",
+    no_full_install: true,
+}
+
+// Virt apex needs a custom signer for its payload
+python_binary_host {
+    name: "sign_virt_apex",
+    srcs: [
+        "sign_virt_apex.py",
+    ],
+    version: {
+        py3: {
+            embedded_launcher: true,
+        },
+    },
+    required: [
+        // sign_virt_apex should be runnable from outside the source tree,
+        // therefore, any required tool should be listed in build/make/core/Makefile as well.
+        "img2simg",
+        "initrd_bootconfig",
+        "lpmake",
+        "lpunpack",
+        "lz4",
+        "simg2img",
+    ],
+}
+
+sh_test_host {
+    name: "sign_virt_apex_test",
+    src: "sign_virt_apex_test.sh",
+    test_config: "sign_virt_apex_test.xml",
+    data_bins: [
+        // deapexer
+        "deapexer",
+        "debugfs_static",
+        "fsck.erofs",
+
+        // sign_virt_apex
+        "avbtool",
+        "img2simg",
+        "initrd_bootconfig",
+        "lpmake",
+        "lpunpack",
+        "lz4",
+        "sign_virt_apex",
+        "simg2img",
+    ],
+    data_libs: [
+        "libbase",
+        "libc++",
+        "libcrypto_utils",
+        "libcrypto",
+        "libext4_utils",
+        "liblog",
+        "liblp",
+        "libsparse",
+        "libz",
+    ],
+    data: [
+        ":com.android.virt",
+        ":test.com.android.virt.pem",
+    ],
+    test_suites: ["general-tests"],
+}
+
+filegroup {
+    name: "test.com.android.virt.pem",
+    srcs: ["test.com.android.virt.pem"],
+}
+
+filegroup {
+    name: "test2.com.android.virt.pem",
+    srcs: ["test2.com.android.virt.pem"],
+}
+
+// custom tool to replace bytes in a file
+python_binary_host {
+    name: "replace_bytes",
+    srcs: [
+        "replace_bytes.py",
+    ],
+    version: {
+        py3: {
+            embedded_launcher: true,
+        },
+    },
+}
+
+// Encapsulate the contributions made by the com.android.virt to the bootclasspath.
+bootclasspath_fragment {
+    name: "com.android.virt-bootclasspath-fragment",
+    contents: ["framework-virtualization"],
+    apex_available: ["com.android.virt"],
+
+    // The bootclasspath_fragments that provide APIs on which this depends.
+    fragments: [
+        {
+            apex: "com.android.art",
+            module: "art-bootclasspath-fragment",
+        },
+    ],
+
+    // Additional stubs libraries that this fragment's contents use which are
+    // not provided by another bootclasspath_fragment.
+    additional_stubs: [
+        "android-non-updatable",
+    ],
+
+    hidden_api: {
+
+        // This module does not contain any split packages.
+        split_packages: [],
+
+        // The following packages and all their subpackages currently only
+        // contain classes from this bootclasspath_fragment. Listing a package
+        // here won't prevent other bootclasspath modules from adding classes in
+        // any of those packages but it will prevent them from adding those
+        // classes into an API surface, e.g. public, system, etc.. Doing so will
+        // result in a build failure due to inconsistent flags.
+        package_prefixes: [
+            "android.system.virtualmachine",
+            "android.system.virtualizationservice",
+            // android.sysprop.*, renamed by jarjar
+            "com.android.system.virtualmachine.sysprop",
+        ],
+    },
+}
+
+systemserverclasspath_fragment {
+    name: "com.android.virt-systemserver-fragment",
+    contents: [
+        "service-virtualization",
+    ],
+    apex_available: ["com.android.virt"],
+    enabled: select(release_flag("RELEASE_AVF_ENABLE_LLPVM_CHANGES"), {
+        true: true,
+        default: false,
+    }),
+}
diff --git a/apex/AndroidManifest.xml b/build/apex/AndroidManifest.xml
similarity index 100%
rename from apex/AndroidManifest.xml
rename to build/apex/AndroidManifest.xml
diff --git a/apex/canned_fs_config b/build/apex/canned_fs_config
similarity index 100%
rename from apex/canned_fs_config
rename to build/apex/canned_fs_config
diff --git a/apex/canned_fs_config_sys_nice b/build/apex/canned_fs_config_sys_nice
similarity index 100%
rename from apex/canned_fs_config_sys_nice
rename to build/apex/canned_fs_config_sys_nice
diff --git a/apex/com.android.virt.avbpubkey b/build/apex/com.android.virt.avbpubkey
similarity index 100%
rename from apex/com.android.virt.avbpubkey
rename to build/apex/com.android.virt.avbpubkey
Binary files differ
diff --git a/apex/com.android.virt.pem b/build/apex/com.android.virt.pem
similarity index 100%
rename from apex/com.android.virt.pem
rename to build/apex/com.android.virt.pem
diff --git a/apex/com.android.virt.pk8 b/build/apex/com.android.virt.pk8
similarity index 100%
rename from apex/com.android.virt.pk8
rename to build/apex/com.android.virt.pk8
Binary files differ
diff --git a/apex/com.android.virt.x509.pem b/build/apex/com.android.virt.x509.pem
similarity index 100%
rename from apex/com.android.virt.x509.pem
rename to build/apex/com.android.virt.x509.pem
diff --git a/apex/manifest.json b/build/apex/manifest.json
similarity index 100%
rename from apex/manifest.json
rename to build/apex/manifest.json
diff --git a/apex/permissions/Android.bp b/build/apex/permissions/Android.bp
similarity index 100%
rename from apex/permissions/Android.bp
rename to build/apex/permissions/Android.bp
diff --git a/apex/permissions/features_com.android.virt.xml b/build/apex/permissions/features_com.android.virt.xml
similarity index 100%
rename from apex/permissions/features_com.android.virt.xml
rename to build/apex/permissions/features_com.android.virt.xml
diff --git a/apex/product_packages.mk b/build/apex/product_packages.mk
similarity index 95%
rename from apex/product_packages.mk
rename to build/apex/product_packages.mk
index 486334c..e710021 100644
--- a/apex/product_packages.mk
+++ b/build/apex/product_packages.mk
@@ -17,7 +17,7 @@
 # TODO: Remove this once the APEX is included in base system.
 
 # To include the APEX in your build, insert this in your device.mk:
-#   $(call inherit-product, packages/modules/Virtualization/apex/product_packages.mk)
+#   $(call inherit-product, packages/modules/Virtualization/build/apex/product_packages.mk)
 
 # If devices supports AVF it implies that it uses non-flattened APEXes.
 $(call inherit-product, $(SRC_TARGET_DIR)/product/updatable_apex.mk)
diff --git a/apex/replace_bytes.py b/build/apex/replace_bytes.py
similarity index 100%
rename from apex/replace_bytes.py
rename to build/apex/replace_bytes.py
diff --git a/apex/sign_virt_apex.py b/build/apex/sign_virt_apex.py
similarity index 100%
rename from apex/sign_virt_apex.py
rename to build/apex/sign_virt_apex.py
diff --git a/apex/sign_virt_apex_test.sh b/build/apex/sign_virt_apex_test.sh
similarity index 100%
rename from apex/sign_virt_apex_test.sh
rename to build/apex/sign_virt_apex_test.sh
diff --git a/apex/sign_virt_apex_test.xml b/build/apex/sign_virt_apex_test.xml
similarity index 100%
rename from apex/sign_virt_apex_test.xml
rename to build/apex/sign_virt_apex_test.xml
diff --git a/apex/test.com.android.virt.pem b/build/apex/test.com.android.virt.pem
similarity index 100%
rename from apex/test.com.android.virt.pem
rename to build/apex/test.com.android.virt.pem
diff --git a/apex/test2.com.android.virt.pem b/build/apex/test2.com.android.virt.pem
similarity index 100%
rename from apex/test2.com.android.virt.pem
rename to build/apex/test2.com.android.virt.pem
diff --git a/apex/vfio_handler.rc b/build/apex/vfio_handler.rc
similarity index 100%
rename from apex/vfio_handler.rc
rename to build/apex/vfio_handler.rc
diff --git a/apex/virtualizationservice.rc.base b/build/apex/virtualizationservice.rc.base
similarity index 100%
rename from apex/virtualizationservice.rc.base
rename to build/apex/virtualizationservice.rc.base
diff --git a/apex/virtualizationservice.rc.llpvm b/build/apex/virtualizationservice.rc.llpvm
similarity index 100%
rename from apex/virtualizationservice.rc.llpvm
rename to build/apex/virtualizationservice.rc.llpvm
diff --git a/apex/virtualizationservice.rc.ra b/build/apex/virtualizationservice.rc.ra
similarity index 100%
rename from apex/virtualizationservice.rc.ra
rename to build/apex/virtualizationservice.rc.ra
diff --git a/apex/virtualizationservice.xml b/build/apex/virtualizationservice.xml
similarity index 100%
rename from apex/virtualizationservice.xml
rename to build/apex/virtualizationservice.xml
diff --git a/apex/vmnic.rc b/build/apex/vmnic.rc
similarity index 100%
rename from apex/vmnic.rc
rename to build/apex/vmnic.rc
diff --git a/avf_flags.aconfig b/build/avf_flags.aconfig
similarity index 100%
rename from avf_flags.aconfig
rename to build/avf_flags.aconfig
diff --git a/compos/apex/Android.bp b/build/compos/Android.bp
similarity index 100%
rename from compos/apex/Android.bp
rename to build/compos/Android.bp
diff --git a/compos/apk/Android.bp b/build/compos/CompOSPayloadApp/Android.bp
similarity index 100%
rename from compos/apk/Android.bp
rename to build/compos/CompOSPayloadApp/Android.bp
diff --git a/compos/apk/AndroidManifest.xml b/build/compos/CompOSPayloadApp/AndroidManifest.xml
similarity index 100%
rename from compos/apk/AndroidManifest.xml
rename to build/compos/CompOSPayloadApp/AndroidManifest.xml
diff --git a/compos/apk/assets/vm_config.json b/build/compos/CompOSPayloadApp/assets/vm_config.json
similarity index 100%
rename from compos/apk/assets/vm_config.json
rename to build/compos/CompOSPayloadApp/assets/vm_config.json
diff --git a/compos/apk/assets/vm_config_staged.json b/build/compos/CompOSPayloadApp/assets/vm_config_staged.json
similarity index 100%
rename from compos/apk/assets/vm_config_staged.json
rename to build/compos/CompOSPayloadApp/assets/vm_config_staged.json
diff --git a/compos/apk/assets/vm_config_system_ext.json b/build/compos/CompOSPayloadApp/assets/vm_config_system_ext.json
similarity index 100%
rename from compos/apk/assets/vm_config_system_ext.json
rename to build/compos/CompOSPayloadApp/assets/vm_config_system_ext.json
diff --git a/compos/apk/assets/vm_config_system_ext_staged.json b/build/compos/CompOSPayloadApp/assets/vm_config_system_ext_staged.json
similarity index 100%
rename from compos/apk/assets/vm_config_system_ext_staged.json
rename to build/compos/CompOSPayloadApp/assets/vm_config_system_ext_staged.json
diff --git a/compos/apex/com.android.compos.avbpubkey b/build/compos/com.android.compos.avbpubkey
similarity index 100%
rename from compos/apex/com.android.compos.avbpubkey
rename to build/compos/com.android.compos.avbpubkey
Binary files differ
diff --git a/compos/apex/com.android.compos.pem b/build/compos/com.android.compos.pem
similarity index 100%
rename from compos/apex/com.android.compos.pem
rename to build/compos/com.android.compos.pem
diff --git a/compos/apex/com.android.compos.pk8 b/build/compos/com.android.compos.pk8
similarity index 100%
rename from compos/apex/com.android.compos.pk8
rename to build/compos/com.android.compos.pk8
Binary files differ
diff --git a/compos/apex/com.android.compos.x509.pem b/build/compos/com.android.compos.x509.pem
similarity index 100%
rename from compos/apex/com.android.compos.x509.pem
rename to build/compos/com.android.compos.x509.pem
diff --git a/compos/apex/composd.rc b/build/compos/composd.rc
similarity index 100%
rename from compos/apex/composd.rc
rename to build/compos/composd.rc
diff --git a/compos/apex/manifest.json b/build/compos/manifest.json
similarity index 100%
rename from compos/apex/manifest.json
rename to build/compos/manifest.json
diff --git a/microdroid/Android.bp b/build/microdroid/Android.bp
similarity index 83%
rename from microdroid/Android.bp
rename to build/microdroid/Android.bp
index 289bc82..27d0246 100644
--- a/microdroid/Android.bp
+++ b/build/microdroid/Android.bp
@@ -44,22 +44,7 @@
     },
 ]
 
-soong_config_module_type {
-    name: "flag_aware_microdroid_system_image",
-    module_type: "android_system_image",
-    config_namespace: "ANDROID",
-    bool_variables: [
-        "release_avf_enable_dice_changes",
-        "release_avf_enable_multi_tenant_microdroid_vm",
-    ],
-    properties: [
-        "deps",
-        "dirs",
-        "multilib",
-    ],
-}
-
-flag_aware_microdroid_system_image {
+android_system_image {
     name: "microdroid",
     use_avb: true,
     avb_private_key: ":microdroid_sign_key",
@@ -108,7 +93,13 @@
         "traced",
         "traced_probes",
         "perfetto",
-    ] + microdroid_shell_and_utilities,
+    ] + select(release_flag("RELEASE_AVF_ENABLE_MULTI_TENANT_MICRODROID_VM"), {
+        true: [
+            "microdroid_etc_passwd",
+            "microdroid_etc_group",
+        ],
+        default: [],
+    }) + microdroid_shell_and_utilities,
     multilib: {
         common: {
             deps: [
@@ -128,7 +119,10 @@
                 "microdroid_kexec",
                 "microdroid_manager",
                 "zipfuse",
-            ],
+            ] + select(release_flag("RELEASE_AVF_ENABLE_DICE_CHANGES"), {
+                true: ["derive_microdroid_vendor_dice_node"],
+                default: [],
+            }),
         },
     },
     arch: {
@@ -147,33 +141,16 @@
     },
     linker_config_src: "linker.config.json",
     base_dir: "system",
-    dirs: microdroid_rootdirs,
+    dirs: microdroid_rootdirs + select(release_flag("RELEASE_AVF_ENABLE_DICE_CHANGES"), {
+        true: ["microdroid_resources"],
+        default: [],
+    }),
     symlinks: microdroid_symlinks,
     file_contexts: ":microdroid_file_contexts.gen",
     // For deterministic output, use fake_timestamp, hard-coded uuid
     fake_timestamp: "1611569676",
     // python -c "import uuid; print(uuid.uuid5(uuid.NAMESPACE_URL, 'www.android.com/avf/microdroid/system'))"
     uuid: "5fe079c6-f01a-52be-87d3-d415231a72ad",
-
-    // Below are dependencies that are conditionally enabled depending on value of build flags.
-    soong_config_variables: {
-        release_avf_enable_dice_changes: {
-            multilib: {
-                lib64: {
-                    deps: ["derive_microdroid_vendor_dice_node"],
-                },
-            },
-            dirs: [
-                "microdroid_resources",
-            ],
-        },
-        release_avf_enable_multi_tenant_microdroid_vm: {
-            deps: [
-                "microdroid_etc_passwd",
-                "microdroid_etc_group",
-            ],
-        },
-    },
 }
 
 prebuilt_etc {
@@ -224,15 +201,15 @@
     name: "microdroid_build_prop_gen_x86_64",
     srcs: [
         "build.prop",
-        ":buildinfo.prop",
+        ":system-build.prop",
     ],
     out: ["build.prop.out"],
-    cmd: "(echo '# build properties from buildinfo.prop module' && " +
-        "grep ro\\.build\\.version\\.codename= $(location :buildinfo.prop) && " +
-        "grep ro\\.build\\.version\\.release= $(location :buildinfo.prop) && " +
-        "grep ro\\.build\\.version\\.sdk= $(location :buildinfo.prop) && " +
-        "grep ro\\.build\\.version\\.security_patch= $(location :buildinfo.prop) && " +
-        "grep ro\\.build\\.version\\.known_codenames= $(location :buildinfo.prop) && " +
+    cmd: "(echo '# build properties from system/build.prop' && " +
+        "grep ro\\.build\\.version\\.codename= $(location :system-build.prop) && " +
+        "grep ro\\.build\\.version\\.release= $(location :system-build.prop) && " +
+        "grep ro\\.build\\.version\\.sdk= $(location :system-build.prop) && " +
+        "grep ro\\.build\\.version\\.security_patch= $(location :system-build.prop) && " +
+        "grep ro\\.build\\.version\\.known_codenames= $(location :system-build.prop) && " +
         "cat $(location build.prop) && " +
         "echo ro.product.cpu.abilist=x86_64 && " +
         "echo ro.product.cpu.abi=x86_64) > $(out)",
@@ -242,15 +219,15 @@
     name: "microdroid_build_prop_gen_arm64",
     srcs: [
         "build.prop",
-        ":buildinfo.prop",
+        ":system-build.prop",
     ],
     out: ["build.prop.out"],
-    cmd: "(echo '# build properties from buildinfo.prop module' && " +
-        "grep ro\\.build\\.version\\.codename= $(location :buildinfo.prop) && " +
-        "grep ro\\.build\\.version\\.release= $(location :buildinfo.prop) && " +
-        "grep ro\\.build\\.version\\.sdk= $(location :buildinfo.prop) && " +
-        "grep ro\\.build\\.version\\.security_patch= $(location :buildinfo.prop) && " +
-        "grep ro\\.build\\.version\\.known_codenames= $(location :buildinfo.prop) && " +
+    cmd: "(echo '# build properties from system/build.prop' && " +
+        "grep ro\\.build\\.version\\.codename= $(location :system-build.prop) && " +
+        "grep ro\\.build\\.version\\.release= $(location :system-build.prop) && " +
+        "grep ro\\.build\\.version\\.sdk= $(location :system-build.prop) && " +
+        "grep ro\\.build\\.version\\.security_patch= $(location :system-build.prop) && " +
+        "grep ro\\.build\\.version\\.known_codenames= $(location :system-build.prop) && " +
         "cat $(location build.prop) && " +
         "echo ro.product.cpu.abilist=arm64-v8a && " +
         "echo ro.product.cpu.abi=arm64-v8a) > $(out)",
@@ -308,19 +285,7 @@
     },
 }
 
-soong_config_module_type {
-    name: "flag_aware_microdroid_filesystem",
-    module_type: "android_filesystem",
-    config_namespace: "ANDROID",
-    bool_variables: [
-        "release_avf_enable_dice_changes",
-    ],
-    properties: [
-        "dirs",
-    ],
-}
-
-flag_aware_microdroid_filesystem {
+android_filesystem {
     name: "microdroid_ramdisk",
     deps: [
         "init_first_stage.microdroid",
@@ -333,17 +298,11 @@
         "mnt",
         "debug_ramdisk",
         "second_stage_resources",
-    ],
+    ] + select(release_flag("RELEASE_AVF_ENABLE_DICE_CHANGES"), {
+        true: ["microdroid_resources"],
+        default: [],
+    }),
     type: "compressed_cpio",
-
-    // Below are dependencies that are conditionally enabled depending on value of build flags.
-    soong_config_variables: {
-        release_avf_enable_dice_changes: {
-            dirs: [
-                "microdroid_resources",
-            ],
-        },
-    },
 }
 
 android_filesystem {
@@ -400,32 +359,16 @@
     srcs: [":pvmfw_embedded_key"],
 }
 
-soong_config_module_type {
-    name: "flag_aware_microdroid_vbmeta",
-    module_type: "vbmeta",
-    config_namespace: "ANDROID",
-    bool_variables: [
-        "release_avf_enable_vendor_modules",
-    ],
-    properties: [
-        "partitions",
-    ],
-}
-
-flag_aware_microdroid_vbmeta {
+vbmeta {
     name: "microdroid_vbmeta",
     partition_name: "vbmeta",
     private_key: ":microdroid_sign_key",
     partitions: [
         "microdroid",
-    ],
-    soong_config_variables: {
-        release_avf_enable_vendor_modules: {
-            conditions_default: {
-                partitions: ["microdroid_vendor"],
-            },
-        },
-    },
+    ] + select(release_flag("RELEASE_AVF_ENABLE_VENDOR_MODULES"), {
+        true: [],
+        default: ["microdroid_vendor"],
+    }),
 }
 
 prebuilt_etc {
diff --git a/microdroid/README.md b/build/microdroid/README.md
similarity index 94%
rename from microdroid/README.md
rename to build/microdroid/README.md
index c0cba97..197eec7 100644
--- a/microdroid/README.md
+++ b/build/microdroid/README.md
@@ -14,7 +14,7 @@
 pre-installed. To do this, add the following line in your product makefile.
 
 ```make
-$(call inherit-product, packages/modules/Virtualization/apex/product_packages.mk)
+$(call inherit-product, packages/modules/Virtualization/build/apex/product_packages.mk)
 ```
 
 Build the target product after adding the line, and flash it. This step needs
@@ -41,7 +41,7 @@
 ## Building an app
 
 A [vm
-payload](https://android.googlesource.com/platform/packages/modules/Virtualization/+/refs/heads/main/vm_payload/)
+payload](https://android.googlesource.com/platform/packages/modules/Virtualization/+/refs/heads/main/libs/libvm_payload/)
 is a shared library file that gets executed in microdroid. It is packaged as
 part of an Android application.  The library should have an entry point
 `AVmPayload_main` as shown below:
@@ -132,12 +132,12 @@
 ### Using the APIs
 
 Use the [Android Virtualization Framework Java
-APIs](https://android.googlesource.com/platform/packages/modules/Virtualization/+/refs/heads/main/java/framework/README.md)
+APIs](https://android.googlesource.com/platform/packages/modules/Virtualization/+/refs/heads/main/libs/framework-virtualization/README.md)
 in your app to create a microdroid VM and run payload in it. The APIs are currently
 @SystemApi, and only available to preinstalled apps.
 
 If you are looking for an example usage of the APIs, you may refer to the [demo
-app](https://android.googlesource.com/platform/packages/modules/Virtualization/+/refs/heads/main/demo/).
+app](https://android.googlesource.com/platform/packages/modules/Virtualization/+/refs/heads/main/android/MicrodroidDemoApp/).
 
 
 ## Running Microdroid with vendor image
@@ -182,7 +182,7 @@
 [fstab.microdroid]: fstab.microdroid
 [dm-verity]: https://source.android.com/docs/security/features/verifiedboot/dm-verity
 [DTBO]: https://android.googlesource.com/platform/external/dtc/+/refs/heads/main/Documentation/dt-object-internal.txt
-[pvmfw config data]: ../pvmfw/README.md#configuration-data-format
+[pvmfw config data]: ../guest/pvmfw/README.md#configuration-data-format
 [bootloader]: https://source.android.com/docs/core/architecture/bootloader
 [Makefile]: https://cs.android.com/android/platform/superproject/main/+/main:build/make/core/Makefile
 
diff --git a/microdroid/bootconfig.arm64 b/build/microdroid/bootconfig.arm64
similarity index 100%
rename from microdroid/bootconfig.arm64
rename to build/microdroid/bootconfig.arm64
diff --git a/microdroid/bootconfig.common b/build/microdroid/bootconfig.common
similarity index 100%
rename from microdroid/bootconfig.common
rename to build/microdroid/bootconfig.common
diff --git a/microdroid/bootconfig.debuggable b/build/microdroid/bootconfig.debuggable
similarity index 100%
rename from microdroid/bootconfig.debuggable
rename to build/microdroid/bootconfig.debuggable
diff --git a/microdroid/bootconfig.normal b/build/microdroid/bootconfig.normal
similarity index 100%
rename from microdroid/bootconfig.normal
rename to build/microdroid/bootconfig.normal
diff --git a/microdroid/bootconfig.x86_64 b/build/microdroid/bootconfig.x86_64
similarity index 100%
rename from microdroid/bootconfig.x86_64
rename to build/microdroid/bootconfig.x86_64
diff --git a/microdroid/build.prop b/build/microdroid/build.prop
similarity index 100%
rename from microdroid/build.prop
rename to build/microdroid/build.prop
diff --git a/microdroid/dummy_dtb.img b/build/microdroid/dummy_dtb.img
similarity index 100%
rename from microdroid/dummy_dtb.img
rename to build/microdroid/dummy_dtb.img
diff --git a/microdroid/extract_microdroid_kernel_hashes.py b/build/microdroid/extract_microdroid_kernel_hashes.py
similarity index 100%
rename from microdroid/extract_microdroid_kernel_hashes.py
rename to build/microdroid/extract_microdroid_kernel_hashes.py
diff --git a/microdroid/fstab.microdroid b/build/microdroid/fstab.microdroid
similarity index 100%
rename from microdroid/fstab.microdroid
rename to build/microdroid/fstab.microdroid
diff --git a/microdroid/init.rc b/build/microdroid/init.rc
similarity index 100%
rename from microdroid/init.rc
rename to build/microdroid/init.rc
diff --git a/microdroid/init_debug_policy/Android.bp b/build/microdroid/init_debug_policy/Android.bp
similarity index 100%
rename from microdroid/init_debug_policy/Android.bp
rename to build/microdroid/init_debug_policy/Android.bp
diff --git a/microdroid/init_debug_policy/src/init_debug_policy.rs b/build/microdroid/init_debug_policy/src/init_debug_policy.rs
similarity index 100%
rename from microdroid/init_debug_policy/src/init_debug_policy.rs
rename to build/microdroid/init_debug_policy/src/init_debug_policy.rs
diff --git a/microdroid/initrd/Android.bp b/build/microdroid/initrd/Android.bp
similarity index 100%
rename from microdroid/initrd/Android.bp
rename to build/microdroid/initrd/Android.bp
diff --git a/microdroid/initrd/gen_vbmeta_bootconfig.py b/build/microdroid/initrd/gen_vbmeta_bootconfig.py
similarity index 100%
rename from microdroid/initrd/gen_vbmeta_bootconfig.py
rename to build/microdroid/initrd/gen_vbmeta_bootconfig.py
diff --git a/microdroid/initrd/src/main.rs b/build/microdroid/initrd/src/main.rs
similarity index 100%
rename from microdroid/initrd/src/main.rs
rename to build/microdroid/initrd/src/main.rs
diff --git a/microdroid/linker.config.json b/build/microdroid/linker.config.json
similarity index 100%
rename from microdroid/linker.config.json
rename to build/microdroid/linker.config.json
diff --git a/microdroid/microdroid.json b/build/microdroid/microdroid.json
similarity index 100%
rename from microdroid/microdroid.json
rename to build/microdroid/microdroid.json
diff --git a/microdroid/microdroid_event-log-tags b/build/microdroid/microdroid_event-log-tags
similarity index 100%
rename from microdroid/microdroid_event-log-tags
rename to build/microdroid/microdroid_event-log-tags
diff --git a/microdroid/microdroid_gki-android15-6.6.json b/build/microdroid/microdroid_gki-android15-6.6.json
similarity index 100%
rename from microdroid/microdroid_gki-android15-6.6.json
rename to build/microdroid/microdroid_gki-android15-6.6.json
diff --git a/microdroid/microdroid_group b/build/microdroid/microdroid_group
similarity index 100%
rename from microdroid/microdroid_group
rename to build/microdroid/microdroid_group
diff --git a/microdroid/microdroid_manifest.xml b/build/microdroid/microdroid_manifest.xml
similarity index 100%
rename from microdroid/microdroid_manifest.xml
rename to build/microdroid/microdroid_manifest.xml
diff --git a/microdroid/microdroid_passwd b/build/microdroid/microdroid_passwd
similarity index 100%
rename from microdroid/microdroid_passwd
rename to build/microdroid/microdroid_passwd
diff --git a/microdroid/uboot-env.txt b/build/microdroid/uboot-env.txt
similarity index 100%
rename from microdroid/uboot-env.txt
rename to build/microdroid/uboot-env.txt
diff --git a/microdroid/ueventd.rc b/build/microdroid/ueventd.rc
similarity index 100%
rename from microdroid/ueventd.rc
rename to build/microdroid/ueventd.rc
diff --git a/docs/abl.md b/docs/abl.md
index 7139d26..c10f1bf 100644
--- a/docs/abl.md
+++ b/docs/abl.md
@@ -14,7 +14,7 @@
 * loading it into memory, and
 * describing the region where pvmfw is loaded using DT and passing it to hypervisor.
 
-See [ABL Support](../pvmfw/README.md#android-bootloader-abl_support) for more detail.
+See [ABL Support](../guest/pvmfw/README.md#android-bootloader-abl_support) for more detail.
 
 ABL is also responsible for constructing the pvmfw configuration data. The data consists of the
 following info:
@@ -26,7 +26,7 @@
     * Hashtree digest of the
       [microdroid-vendor.img](microdroid_vendor_modules.md#changes-in-abl).
 
-See [Configuration Data](../pvmfw/README.md#configuration-data) for more detail.
+See [Configuration Data](../guest/pvmfw/README.md#configuration-data) for more detail.
 
 ## Android
 
diff --git a/docs/custom_vm.md b/docs/custom_vm.md
index 6c51795..6a1cc00 100644
--- a/docs/custom_vm.md
+++ b/docs/custom_vm.md
@@ -25,19 +25,35 @@
 The `vm` command also has other subcommands for debugging; run
 `/apex/com.android.virt/bin/vm help` for details.
 
-### Running Debian with u-boot
-1. Prepare u-boot binary from `u-boot_crosvm_aarch64` in https://ci.android.com/builds/branches/aosp_u-boot-mainline/grid
-or build it by https://source.android.com/docs/devices/cuttlefish/bootloader-dev#develop-bootloader
-2. Prepare Debian image from https://cloud.debian.org/images/cloud/ (We tested nocloud image)
-3. Copy `u-boot.bin`, Debian image file(like `debian-12-nocloud-arm64.raw`) and `vm_config.json` to `/data/local/tmp`
+### Running Debian
+1. Download an ARM64 image from https://cloud.debian.org/images/cloud/ (We tested nocloud image)
+
+2. Resize the image
+```shell
+truncate -s 20G debian.img
+virt-resize --expand /dev/sda1 <download_image_file> debian.img
+```
+
+3. Copy the image file
+```shell
+tar cfS debian.img.tar debian.img
+adb push debian.img.tar /data/local/tmp/
+adb shell tar xf /data/local/tmp/debian.img.tar -C /data/local/tmp/
+adb shell rm /data/local/tmp/debian.img.tar
+adb shell chmod a+w /data/local/tmp/debian.img
+rm debian.img.tar
+```
+
+Note: we tar and untar to keep the image file sparse.
+
+4. Make the VM config file
 ```shell
 cat > vm_config.json <<EOF
 {
     "name": "debian",
-    "bootloader": "/data/local/tmp/u-boot.bin",
     "disks": [
         {
-            "image": "/data/local/tmp/debian-12-nocloud-arm64.raw",
+            "image": "/data/local/tmp/debian.img",
             "partitions": [],
             "writable": true
         }
@@ -45,14 +61,51 @@
     "protected": false,
     "cpu_topology": "match_host",
     "platform_version": "~1.0",
-    "memory_mib" : 8096
+    "memory_mib": 8096,
+    "debuggable": true,
+    "console_out": true,
+    "connect_console": true,
+    "console_input_device": "ttyS0",
+    "network": true,
+    "input": {
+        "touchscreen": true,
+        "keyboard": true,
+        "mouse": true,
+        "trackpad": true,
+        "switches": true
+    },
+    "audio": {
+        "speaker": true,
+         "microphone": true
+    },
+    "gpu": {
+        "backend": "virglrenderer",
+        "context_types": ["virgl2"]
+    },
+    "display": {
+        "refresh_rate": "30"
+    }
 }
 EOF
-adb push `u-boot.bin` /data/local/tmp
-adb push `debian-12-nocloud-arm64.raw` /data/local/tmp
-adb push vm_config.json /data/local/tmp/vm_config.json
+adb push vm_config.json /data/local/tmp/
 ```
-4. Launch VmLauncherApp(the detail will be explain below)
+
+5. Launch VmLauncherApp(the detail will be explain below)
+
+6. For console, we can refer to `Debugging` section below. (id: root)
+
+7. For graphical shell, you need to install xfce(for now, only xfce is tested)
+```
+apt install task-xfce-desktop
+dpkg --configure -a (if necessary)
+systemctl set-default graphical.target
+
+# need non-root user for graphical shell
+adduser linux
+# optional
+adduser linux sudo
+reboot
+```
 
 ## Graphical VMs
 
@@ -192,43 +245,50 @@
             "writable": true
         }
     ],
+    "protected": false,
+    "cpu_topology": "match_host",
+    "platform_version": "~1.0",
+    "memory_mib": 8096,
+    "debuggable": true,
+    "console_out": true,
+    "connect_console": true,
+    "console_input_device": "hvc0",
+    "network": true,
+    "input": {
+        "touchscreen": true,
+        "keyboard": true,
+        "mouse": true,
+        "trackpad": true,
+        "switches": true
+    },
+    "audio": {
+        "speaker": true,
+        "microphone": true
+    },
     "gpu": {
         "backend": "virglrenderer",
         "context_types": ["virgl2"]
     },
-    "params": "root=/dev/vda3 rootwait noinitrd ro enforcing=0 cros_debug cros_secure",
-    "protected": false,
-    "cpu_topology": "match_host",
-    "platform_version": "~1.0",
-    "memory_mib" : 8096,
-    "console_input_device": "ttyS0"
+    "display": {
+        "scale": "0.77",
+        "refresh_rate": "30"
+    }
 }
 ```
 
 ### Running the VM
 
-First, enable the `VmLauncherApp` app. This needs to be done only once. In the
-future, this step won't be necesssary.
+1. Grant permission to the `VmLauncherApp` if the virt apex is Google-signed.
+    ```shell
+    $ adb shell su root pm grant com.google.android.virtualization.vmlauncher android.permission.USE_CUSTOM_VIRTUAL_MACHINE
+    ```
 
-```
-$ adb root
-$ adb shell pm enable com.android.virtualization.vmlauncher/.MainActivityAlias
-$ adb unroot
-```
+2. Ensure your device is connected to the Internet.
 
-If virt apex is Google-signed, you need to enable the app and grant the
-permission to the app.
-```
-$ adb root
-$ adb shell pm enable com.google.android.virtualization.vmlauncher/com.android.virtualization.vmlauncher.MainActivityAlias
-$ adb shell pm grant com.google.android.virtualization.vmlauncher android.permission.USE_CUSTOM_VIRTUAL_MACHINE
-$ adb unroot
-```
-
-Second, ensure your device is connected to the Internet.
-
-Finally, tap the VmLauncherApp app from the launcher UI. You will see
-Ferrochrome booting!
+3. Launch the app with adb.
+    ```shell
+    $ adb shell su root am start-activity -a android.virtualization.VM_LAUNCHER
+    ```
 
 If it doesn’t work well, try
 
@@ -246,15 +306,15 @@
 ```
 
 To see console logs only, check
-`/data/data/com{,.google}.android.virtualization.vmlauncher/files/console.log`
+`/data/data/com{,.google}.android.virtualization.vmlauncher/files/${vm_name}.log`
 
 For HSUM enabled devices,
-`/data/user/${current_user_id}/com{,.google}.android.virtualization.vmlauncher/files/console.log`
+`/data/user/${current_user_id}/com{,.google}.android.virtualization.vmlauncher/files/${vm_name}.log`
 
 You can monitor console out as follows
 
 ```shell
-$ adb shell 'su root tail +0 -F /data/user/$(am get-current-user)/com{,.google}.android.virtualization.vmlauncher/files/console.log'
+$ adb shell 'su root tail +0 -F /data/user/$(am get-current-user)/com{,.google}.android.virtualization.vmlauncher/files/${vm_name}.log'
 ```
 
 For ChromiumOS, you can enter to the console via SSH connection. Check your IP
diff --git a/docs/debug/README.md b/docs/debug/README.md
index 1e5f096..4b42531 100644
--- a/docs/debug/README.md
+++ b/docs/debug/README.md
@@ -96,7 +96,7 @@
 
 1. Bootloader loads it from the `dpm` partition and verifies it.
 1. Bootloader appends the loaded debug policy as the [configuration
-   data](../../pvmfw/README.md#configuration-data) of the pvmfw.
+   data](../../guest/pvmfw/README.md#configuration-data) of the pvmfw.
 1. When a pVM is started, pvmfw [overlays][apply_debug_policy] the debug policy to the baseline
    device tree from crosvm.
 1. OS payload (e.g. Microdroid) [reads][read_debug_policy] the device tree and enables specific
diff --git a/docs/debug/gdb_kernel.md b/docs/debug/gdb_kernel.md
index 316faad..ced6d73 100644
--- a/docs/debug/gdb_kernel.md
+++ b/docs/debug/gdb_kernel.md
@@ -33,14 +33,14 @@
 ## Obtaining vmlinux for Microdroid kernels
 
 If you are debugging Microdroid kernel that you have built [locally](
-../../microdroid/kernel/README.md), then look for `out/dist/vmlinux` in your
+../../build/microdroid/kernel/README.md), then look for `out/dist/vmlinux` in your
 kernel repository.
 
 If you are debugging Microdroid kernel bundled with the `com.android.virt` APEX,
 then you need to obtain the build ID of this kernel. You can do this by
 checking the prebuilt-info.txt file in the
-`packages/modules/Virtualization/microdroid/kernel/arm64` or
-`packages/modules/Virtualization/microdroid/kernel/x86_64` directories.
+`packages/modules/Virtualization/build/microdroid/kernel/arm64` or
+`packages/modules/Virtualization/build/microdroid/kernel/x86_64` directories.
 
 Using that build ID you can download the vmlinux from the build server via:
 https://ci.android.com/builds/submitted/${BUILD_ID}/kernel_microdroid_aarch64/latest/vmlinux
diff --git a/docs/device_assignment.md b/docs/device_assignment.md
index 1166cdf..4b2296c 100644
--- a/docs/device_assignment.md
+++ b/docs/device_assignment.md
@@ -221,7 +221,7 @@
 incoming device tree with the VM DTBO.
 
 For more detail about providing VM DTBO in pvmfw,
-see: [pvmfw/README.md](../pvmfw/README.md#configuration-data-format)
+see: [pvmfw/README.md](../guest/pvmfw/README.md#configuration-data-format)
 
 
 ## Launch VM with device assignment
@@ -236,4 +236,4 @@
 
 ```sh
 adb shell /apex/com.android.virt/bin/vm run-microdroid --devices /sys/bus/platform/devices/16d00000.light
-```
\ No newline at end of file
+```
diff --git a/docs/getting_started.md b/docs/getting_started.md
index 74f2012..0a7cca6 100644
--- a/docs/getting_started.md
+++ b/docs/getting_started.md
@@ -94,7 +94,7 @@
 
 ## Step 4: Run a Microdroid VM
 
-[Microdroid](../../microdroid/README.md) is a lightweight version of Android
+[Microdroid](../../build/microdroid/README.md) is a lightweight version of Android
 that is intended to run on pVM. You can run a Microdroid-based VM with an empty
 payload using the following command:
 
diff --git a/docs/microdroid_vendor_modules.md b/docs/microdroid_vendor_modules.md
index ef55225..6ead195 100644
--- a/docs/microdroid_vendor_modules.md
+++ b/docs/microdroid_vendor_modules.md
@@ -111,7 +111,7 @@
 
 The execution flow is very similar to the non-protected case above, however
 there is one important addition. The `pvmfw` binary will use the
-[VM reference DT blob](#../pvmfw/README.md#pvmfw-data-v1-2) passed from the
+[VM reference DT blob](#../guest/pvmfw/README.md#pvmfw-data-v1-2) passed from the
 Android Bootloader (ABL), to validate the guest DT overlay passed from the host.
 
 See [Changes in Android Bootloader](#changes-in-abl) section below for more
@@ -146,7 +146,7 @@
 the host's `vendor.img`.
 
 The Android Bootloader can read that property when construction the
-[VM reference DT blob](#../pvmfw/README.md#pvmfw-data-v1-2) passed to pvmfw.
+[VM reference DT blob](#../guest/pvmfw/README.md#pvmfw-data-v1-2) passed to pvmfw.
 
 ## GKI as Microdroid guest kernel
 
diff --git a/service_vm/README.md b/docs/service_vm.md
similarity index 94%
rename from service_vm/README.md
rename to docs/service_vm.md
index b45b3ae..735c14d 100644
--- a/service_vm/README.md
+++ b/docs/service_vm.md
@@ -18,7 +18,7 @@
 
 [Rialto](../rialto) is used as the bare-metal kernel for the Service VM. It
 shares some low-level setup, such as memory management and virtio device
-parsing, with pvmfw. The common setup code is grouped in [vmbase/](../vmbase).
+parsing, with pvmfw. The common setup code is grouped in [vmbase/](../libs/libvmbase).
 
 ## Functionality
 
@@ -30,7 +30,7 @@
 -   [./requests](./requests) contains the library that processes the requests.
 -   [./manager](./manager) manages the Service VM session, ensuring that only
     one Service VM is active at any given time. The
-    [virtualizationservice](../virtualizationservice) process owns and manages
+    [virtualizationservice](../android/virtualizationservice) process owns and manages
     the Service VM instance.
 
 ### RKP VM (Remote Key Provisioning Virtual Machine)
diff --git a/docs/vm_remote_attestation.md b/docs/vm_remote_attestation.md
index 195804f..79f44b9 100644
--- a/docs/vm_remote_attestation.md
+++ b/docs/vm_remote_attestation.md
@@ -27,7 +27,7 @@
 1.  Attesting the RKP VM against the RKP server.
 2.  Attesting the pVM against the RKP VM.
 
-[rkpvm]: https://android.googlesource.com/platform/packages/modules/Virtualization/+/main/service_vm/README.md
+[rkpvm]: https://android.googlesource.com/platform/packages/modules/Virtualization/+/main/docs/service_vm.md
 
 ### RKP VM attestation
 
@@ -67,8 +67,8 @@
 For detailed information and usage examples, please refer to the
 [demo app][demo].
 
-[api]: https://android.googlesource.com/platform/packages/modules/Virtualization/+/main/vm_payload/README.md
-[demo]: https://android.googlesource.com/platform/packages/modules/Virtualization/+/main/service_vm/demo_apk
+[api]: https://android.googlesource.com/platform/packages/modules/Virtualization/+/main/libs/libvm_payload/README.md
+[demo]: https://android.googlesource.com/platform/packages/modules/Virtualization/+/main/android/VmAttestationDemoApp
 
 ## Output
 
@@ -105,7 +105,11 @@
     set to true only when all the DICE certificates in the pVM DICE chain are in
     normal mode.
 -   The `vmComponents` field contains a list of all the APKs and apexes loaded
-    by the pVM.
+    by the pVM. These components are extracted from the config descriptor of the
+    last DiceChainEntry of the pVM DICE chain. Refer to
+    [dice_for_avf_guest.cddl][dice_for_avf_guest_cddl] for more information.
+
+[dice_for_avf_guest_cddl]: https://cs.android.com/android/platform/superproject/main/+/main:packages/modules/Virtualization/dice_for_avf_guest.cddl
 
 ## To Support It
 
diff --git a/ferrochrome_app/java/com/android/virtualization/ferrochrome/FerrochromeActivity.java b/ferrochrome_app/java/com/android/virtualization/ferrochrome/FerrochromeActivity.java
deleted file mode 100644
index 5006413..0000000
--- a/ferrochrome_app/java/com/android/virtualization/ferrochrome/FerrochromeActivity.java
+++ /dev/null
@@ -1,184 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.virtualization.ferrochrome;
-
-import android.app.Activity;
-import android.app.ActivityManager;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.os.Bundle;
-import android.os.Environment;
-import android.os.SystemProperties;
-import android.util.Log;
-import android.view.WindowManager;
-import android.widget.TextView;
-
-import java.io.BufferedReader;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.StandardCopyOption;
-import java.util.List;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-
-public class FerrochromeActivity extends Activity {
-    private static final String TAG = FerrochromeActivity.class.getName();
-    private static final String ACTION_VM_LAUNCHER = "android.virtualization.VM_LAUNCHER";
-
-    private static final Path DEST_DIR =
-            Path.of(Environment.getExternalStorageDirectory().getPath(), "ferrochrome");
-    private static final Path VERSION_FILE = Path.of(DEST_DIR.toString(), "version");
-
-    private static final int REQUEST_CODE_VMLAUNCHER = 1;
-
-    ExecutorService executorService = Executors.newSingleThreadExecutor();
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        setContentView(R.layout.activity_ferrochrome);
-        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
-
-        // Find VM Launcher
-        Intent intent = new Intent(ACTION_VM_LAUNCHER);
-        PackageManager pm = getPackageManager();
-        List<ResolveInfo> resolveInfos =
-                pm.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
-        if (resolveInfos == null || resolveInfos.size() != 1) {
-            updateStatus("Failed to resolve VM Launcher");
-            return;
-        }
-
-        // Clean up the existing vm launcher process if there is
-        ActivityManager am = getSystemService(ActivityManager.class);
-        am.killBackgroundProcesses(resolveInfos.get(0).activityInfo.packageName);
-
-        executorService.execute(
-                () -> {
-                    if (updateImageIfNeeded()) {
-                        updateStatus("Starting Ferrochrome...");
-                        runOnUiThread(
-                                () -> startActivityForResult(intent, REQUEST_CODE_VMLAUNCHER));
-                    }
-                });
-    }
-
-    @Override
-    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
-        if (requestCode == REQUEST_CODE_VMLAUNCHER) {
-            finishAndRemoveTask();
-        }
-    }
-
-    private boolean updateImageIfNeeded() {
-        if (!isUpdateNeeded()) {
-            Log.d(TAG, "No update needed.");
-            return true;
-        }
-
-        try {
-            if (Files.notExists(DEST_DIR)) {
-                Files.createDirectory(DEST_DIR);
-            }
-
-            String[] files = getAssets().list("ferrochrome");
-            if (files == null || files.length == 0) {
-                updateStatus("ChromeOS image not found. Please go/try-ferrochrome");
-                return false;
-            }
-
-            updateStatus("Copying images...");
-            for (String file : files) {
-                updateStatus(file);
-                Path dst = Path.of(DEST_DIR.toString(), file);
-                updateFile(getAssets().open("ferrochrome/" + file), dst);
-            }
-        } catch (IOException e) {
-            Log.e(TAG, "Error while updating image: " + e);
-            updateStatus("Failed.");
-            return false;
-        }
-        updateStatus("Done.");
-
-        updateStatus("Extracting images...");
-        SystemProperties.set("debug.custom_vm_setup.done", "false");
-        SystemProperties.set("debug.custom_vm_setup.start", "true");
-        while (!SystemProperties.getBoolean("debug.custom_vm_setup.done", false)) {
-            try {
-                Thread.sleep(1000);
-            } catch (Exception e) {
-                Log.e(TAG, "Error while extracting image: " + e);
-                updateStatus("Failed.");
-                return false;
-            }
-        }
-
-        updateStatus("Done.");
-        return true;
-    }
-
-    private boolean isUpdateNeeded() {
-        Path[] pathsToCheck = {DEST_DIR, VERSION_FILE};
-        for (Path p : pathsToCheck) {
-            if (Files.notExists(p)) {
-                Log.d(TAG, p.toString() + " does not exist.");
-                return true;
-            }
-        }
-
-        try {
-            String installedVer = readLine(new FileInputStream(VERSION_FILE.toFile()));
-            String updatedVer = readLine(getAssets().open("ferrochrome/version"));
-            if (installedVer.equals(updatedVer)) {
-                return false;
-            }
-            Log.d(TAG, "Version mismatch. Installed: " + installedVer + "  Updated: " + updatedVer);
-        } catch (IOException e) {
-            Log.e(TAG, "Error while checking version: " + e);
-        }
-        return true;
-    }
-
-    private static String readLine(InputStream input) throws IOException {
-        try (BufferedReader reader = new BufferedReader(new InputStreamReader(input))) {
-            return reader.readLine();
-        } catch (IOException e) {
-            throw e;
-        }
-    }
-
-    private static void updateFile(InputStream input, Path path) throws IOException {
-        try {
-            Files.copy(input, path, StandardCopyOption.REPLACE_EXISTING);
-        } finally {
-            input.close();
-        }
-    }
-
-    private void updateStatus(String line) {
-        runOnUiThread(
-                () -> {
-                    TextView statusView = findViewById(R.id.status_txt_view);
-                    statusView.append(line + "\n");
-                });
-    }
-}
diff --git a/apkdmverity/.cargo/config.toml b/guest/apkdmverity/.cargo/config.toml
similarity index 100%
rename from apkdmverity/.cargo/config.toml
rename to guest/apkdmverity/.cargo/config.toml
diff --git a/apkdmverity/Android.bp b/guest/apkdmverity/Android.bp
similarity index 100%
rename from apkdmverity/Android.bp
rename to guest/apkdmverity/Android.bp
diff --git a/apkdmverity/AndroidTest.xml b/guest/apkdmverity/AndroidTest.xml
similarity index 100%
rename from apkdmverity/AndroidTest.xml
rename to guest/apkdmverity/AndroidTest.xml
diff --git a/apkdmverity/Cargo.toml b/guest/apkdmverity/Cargo.toml
similarity index 100%
rename from apkdmverity/Cargo.toml
rename to guest/apkdmverity/Cargo.toml
diff --git a/apkdmverity/TEST_MAPPING b/guest/apkdmverity/TEST_MAPPING
similarity index 100%
rename from apkdmverity/TEST_MAPPING
rename to guest/apkdmverity/TEST_MAPPING
diff --git a/apkdmverity/src/main.rs b/guest/apkdmverity/src/main.rs
similarity index 100%
rename from apkdmverity/src/main.rs
rename to guest/apkdmverity/src/main.rs
diff --git a/apkdmverity/testdata/README b/guest/apkdmverity/testdata/README
similarity index 100%
rename from apkdmverity/testdata/README
rename to guest/apkdmverity/testdata/README
diff --git a/apkdmverity/testdata/keystore b/guest/apkdmverity/testdata/keystore
similarity index 100%
rename from apkdmverity/testdata/keystore
rename to guest/apkdmverity/testdata/keystore
Binary files differ
diff --git a/apkdmverity/testdata/test.apk b/guest/apkdmverity/testdata/test.apk
similarity index 100%
rename from apkdmverity/testdata/test.apk
rename to guest/apkdmverity/testdata/test.apk
Binary files differ
diff --git a/apkdmverity/testdata/test.apk.idsig b/guest/apkdmverity/testdata/test.apk.idsig
similarity index 100%
rename from apkdmverity/testdata/test.apk.idsig
rename to guest/apkdmverity/testdata/test.apk.idsig
Binary files differ
diff --git a/guest/authfs/Android.bp b/guest/authfs/Android.bp
new file mode 100644
index 0000000..b11da3d
--- /dev/null
+++ b/guest/authfs/Android.bp
@@ -0,0 +1,52 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+rust_defaults {
+    name: "authfs_defaults",
+    crate_name: "authfs",
+    edition: "2021",
+    srcs: [":authfs_src"],
+    rustlibs: [
+        "authfs_aidl_interface-rust",
+        "libandroid_logger",
+        "libanyhow",
+        "libauthfs_fsverity_metadata",
+        "libbinder_rs",
+        "libcfg_if",
+        "libclap",
+        "libfsverity_digests_proto_rust",
+        "libfuse_rust",
+        "libhex",
+        "liblibc",
+        "liblog_rust",
+        "libnix",
+        "libopenssl",
+        "libprotobuf",
+        "librpcbinder_rs",
+        "libthiserror",
+    ],
+    prefer_rlib: true,
+    target: {
+        darwin: {
+            enabled: false,
+        },
+    },
+    defaults: [
+        "crosvm_defaults",
+        "avf_build_flags_rust",
+    ],
+}
+
+filegroup {
+    name: "authfs_src",
+    srcs: [
+        "src/main.rs",
+    ],
+}
+
+rust_binary {
+    name: "authfs",
+    defaults: ["authfs_defaults"],
+    apex_available: ["com.android.virt"],
+}
diff --git a/authfs/TEST_MAPPING b/guest/authfs/TEST_MAPPING
similarity index 100%
rename from authfs/TEST_MAPPING
rename to guest/authfs/TEST_MAPPING
diff --git a/authfs/src/common.rs b/guest/authfs/src/common.rs
similarity index 100%
rename from authfs/src/common.rs
rename to guest/authfs/src/common.rs
diff --git a/authfs/src/file.rs b/guest/authfs/src/file.rs
similarity index 100%
rename from authfs/src/file.rs
rename to guest/authfs/src/file.rs
diff --git a/authfs/src/file/attr.rs b/guest/authfs/src/file/attr.rs
similarity index 100%
rename from authfs/src/file/attr.rs
rename to guest/authfs/src/file/attr.rs
diff --git a/authfs/src/file/dir.rs b/guest/authfs/src/file/dir.rs
similarity index 100%
rename from authfs/src/file/dir.rs
rename to guest/authfs/src/file/dir.rs
diff --git a/authfs/src/file/remote_file.rs b/guest/authfs/src/file/remote_file.rs
similarity index 100%
rename from authfs/src/file/remote_file.rs
rename to guest/authfs/src/file/remote_file.rs
diff --git a/authfs/src/fsstat.rs b/guest/authfs/src/fsstat.rs
similarity index 100%
rename from authfs/src/fsstat.rs
rename to guest/authfs/src/fsstat.rs
diff --git a/authfs/src/fsverity.rs b/guest/authfs/src/fsverity.rs
similarity index 100%
rename from authfs/src/fsverity.rs
rename to guest/authfs/src/fsverity.rs
diff --git a/authfs/src/fsverity/builder.rs b/guest/authfs/src/fsverity/builder.rs
similarity index 100%
rename from authfs/src/fsverity/builder.rs
rename to guest/authfs/src/fsverity/builder.rs
diff --git a/authfs/src/fsverity/common.rs b/guest/authfs/src/fsverity/common.rs
similarity index 100%
rename from authfs/src/fsverity/common.rs
rename to guest/authfs/src/fsverity/common.rs
diff --git a/authfs/src/fsverity/editor.rs b/guest/authfs/src/fsverity/editor.rs
similarity index 100%
rename from authfs/src/fsverity/editor.rs
rename to guest/authfs/src/fsverity/editor.rs
diff --git a/authfs/src/fsverity/metadata/Android.bp b/guest/authfs/src/fsverity/metadata/Android.bp
similarity index 100%
rename from authfs/src/fsverity/metadata/Android.bp
rename to guest/authfs/src/fsverity/metadata/Android.bp
diff --git a/authfs/src/fsverity/metadata/metadata.hpp b/guest/authfs/src/fsverity/metadata/metadata.hpp
similarity index 100%
rename from authfs/src/fsverity/metadata/metadata.hpp
rename to guest/authfs/src/fsverity/metadata/metadata.hpp
diff --git a/authfs/src/fsverity/metadata/metadata.rs b/guest/authfs/src/fsverity/metadata/metadata.rs
similarity index 100%
rename from authfs/src/fsverity/metadata/metadata.rs
rename to guest/authfs/src/fsverity/metadata/metadata.rs
diff --git a/authfs/src/fsverity/sys.rs b/guest/authfs/src/fsverity/sys.rs
similarity index 100%
rename from authfs/src/fsverity/sys.rs
rename to guest/authfs/src/fsverity/sys.rs
diff --git a/authfs/src/fsverity/verifier.rs b/guest/authfs/src/fsverity/verifier.rs
similarity index 100%
rename from authfs/src/fsverity/verifier.rs
rename to guest/authfs/src/fsverity/verifier.rs
diff --git a/authfs/src/fusefs.rs b/guest/authfs/src/fusefs.rs
similarity index 98%
rename from authfs/src/fusefs.rs
rename to guest/authfs/src/fusefs.rs
index ab75dac..618b8ac 100644
--- a/authfs/src/fusefs.rs
+++ b/guest/authfs/src/fusefs.rs
@@ -99,9 +99,9 @@
     /// Number of `Handle`s (i.e. file descriptors) that are currently referring to the this inode.
     ///
     /// Technically, this does not matter to readonly entries, since they live forever. The
-    /// reference count is only needed for manageing lifetime of writable entries like `VerifiedNew`
-    /// and `VerifiedNewDirectory`. That is, when an entry is deleted, the actual entry needs to
-    /// stay alive until the reference count reaches zero.
+    /// reference count is only needed for manageing lifetime of writable entries like
+    /// `VerifiedNew` and `VerifiedNewDirectory`. That is, when an entry is deleted, the actual
+    /// entry needs to stay alive until the reference count reaches zero.
     ///
     /// Note: This is not to be confused with hardlinks, which AuthFS doesn't currently implement.
     handle_ref_count: AtomicU64,
@@ -192,9 +192,9 @@
     /// The next available inode number.
     next_inode: AtomicU64,
 
-    /// Table for `Handle` to `Arc<DirEntriesSnapshot>` lookup. On `opendir`, a new directory handle
-    /// is created and the snapshot of the current directory is created. This is not super
-    /// efficient, but is the simplest way to be compliant to the FUSE contract (see
+    /// Table for `Handle` to `Arc<DirEntriesSnapshot>` lookup. On `opendir`, a new directory
+    /// handle is created and the snapshot of the current directory is created. This is not
+    /// super efficient, but is the simplest way to be compliant to the FUSE contract (see
     /// `fuse::filesystem::readdir`).
     ///
     /// Currently, no code locks `dir_handle_table` and `inode_table` at the same time to avoid
@@ -822,9 +822,9 @@
         self.handle_inode(&inode, |config| {
             match config {
                 AuthFsEntry::VerifiedNew { editor, .. } => {
-                    // FUSE ioctl is limited, thus we can't implement fs-verity ioctls without a kernel
-                    // change (see b/196635431). Until it's possible, use xattr to expose what we need
-                    // as an authfs specific API.
+                    // FUSE ioctl is limited, thus we can't implement fs-verity ioctls without a
+                    // kernel change (see b/196635431). Until it's possible, use
+                    // xattr to expose what we need as an authfs specific API.
                     if name != CStr::from_bytes_with_nul(b"authfs.fsverity.digest\0").unwrap() {
                         return Err(io::Error::from_raw_os_error(libc::ENODATA));
                     }
diff --git a/authfs/src/fusefs/file.rs b/guest/authfs/src/fusefs/file.rs
similarity index 100%
rename from authfs/src/fusefs/file.rs
rename to guest/authfs/src/fusefs/file.rs
diff --git a/authfs/src/fusefs/mount.rs b/guest/authfs/src/fusefs/mount.rs
similarity index 100%
rename from authfs/src/fusefs/mount.rs
rename to guest/authfs/src/fusefs/mount.rs
diff --git a/authfs/src/main.rs b/guest/authfs/src/main.rs
similarity index 100%
rename from authfs/src/main.rs
rename to guest/authfs/src/main.rs
diff --git a/authfs/service/Android.bp b/guest/authfs_service/Android.bp
similarity index 100%
rename from authfs/service/Android.bp
rename to guest/authfs_service/Android.bp
diff --git a/authfs/TEST_MAPPING b/guest/authfs_service/TEST_MAPPING
similarity index 100%
copy from authfs/TEST_MAPPING
copy to guest/authfs_service/TEST_MAPPING
diff --git a/authfs/service/authfs_service.rc b/guest/authfs_service/authfs_service.rc
similarity index 100%
rename from authfs/service/authfs_service.rc
rename to guest/authfs_service/authfs_service.rc
diff --git a/authfs/service/src/authfs.rs b/guest/authfs_service/src/authfs.rs
similarity index 100%
rename from authfs/service/src/authfs.rs
rename to guest/authfs_service/src/authfs.rs
diff --git a/authfs/service/src/main.rs b/guest/authfs_service/src/main.rs
similarity index 100%
rename from authfs/service/src/main.rs
rename to guest/authfs_service/src/main.rs
diff --git a/compos/compos_key_helper/Android.bp b/guest/compos_key_helper/Android.bp
similarity index 100%
rename from compos/compos_key_helper/Android.bp
rename to guest/compos_key_helper/Android.bp
diff --git a/compos/compos_key_helper/compos_key.cpp b/guest/compos_key_helper/compos_key.cpp
similarity index 100%
rename from compos/compos_key_helper/compos_key.cpp
rename to guest/compos_key_helper/compos_key.cpp
diff --git a/compos/compos_key_helper/compos_key.h b/guest/compos_key_helper/compos_key.h
similarity index 100%
rename from compos/compos_key_helper/compos_key.h
rename to guest/compos_key_helper/compos_key.h
diff --git a/compos/compos_key_helper/compos_key_main.cpp b/guest/compos_key_helper/compos_key_main.cpp
similarity index 100%
rename from compos/compos_key_helper/compos_key_main.cpp
rename to guest/compos_key_helper/compos_key_main.cpp
diff --git a/compos/compos_key_helper/compos_key_test.cpp b/guest/compos_key_helper/compos_key_test.cpp
similarity index 100%
rename from compos/compos_key_helper/compos_key_test.cpp
rename to guest/compos_key_helper/compos_key_test.cpp
diff --git a/compos/compos_key_helper/tests/AndroidTest.xml b/guest/compos_key_helper/tests/AndroidTest.xml
similarity index 100%
rename from compos/compos_key_helper/tests/AndroidTest.xml
rename to guest/compos_key_helper/tests/AndroidTest.xml
diff --git a/compos/Android.bp b/guest/compsvc/Android.bp
similarity index 100%
rename from compos/Android.bp
rename to guest/compsvc/Android.bp
diff --git a/compos/src/artifact_signer.rs b/guest/compsvc/src/artifact_signer.rs
similarity index 100%
rename from compos/src/artifact_signer.rs
rename to guest/compsvc/src/artifact_signer.rs
diff --git a/compos/src/compilation.rs b/guest/compsvc/src/compilation.rs
similarity index 100%
rename from compos/src/compilation.rs
rename to guest/compsvc/src/compilation.rs
diff --git a/compos/src/compos_key.rs b/guest/compsvc/src/compos_key.rs
similarity index 100%
rename from compos/src/compos_key.rs
rename to guest/compsvc/src/compos_key.rs
diff --git a/compos/src/compsvc.rs b/guest/compsvc/src/compsvc.rs
similarity index 100%
rename from compos/src/compsvc.rs
rename to guest/compsvc/src/compsvc.rs
diff --git a/compos/src/compsvc_main.rs b/guest/compsvc/src/compsvc_main.rs
similarity index 100%
rename from compos/src/compsvc_main.rs
rename to guest/compsvc/src/compsvc_main.rs
diff --git a/compos/src/fsverity.rs b/guest/compsvc/src/fsverity.rs
similarity index 100%
rename from compos/src/fsverity.rs
rename to guest/compsvc/src/fsverity.rs
diff --git a/microdroid/derive_microdroid_vendor_dice_node/Android.bp b/guest/derive_microdroid_vendor_dice_node/Android.bp
similarity index 100%
rename from microdroid/derive_microdroid_vendor_dice_node/Android.bp
rename to guest/derive_microdroid_vendor_dice_node/Android.bp
diff --git a/microdroid/derive_microdroid_vendor_dice_node/src/main.rs b/guest/derive_microdroid_vendor_dice_node/src/main.rs
similarity index 100%
rename from microdroid/derive_microdroid_vendor_dice_node/src/main.rs
rename to guest/derive_microdroid_vendor_dice_node/src/main.rs
diff --git a/encryptedstore/Android.bp b/guest/encryptedstore/Android.bp
similarity index 100%
rename from encryptedstore/Android.bp
rename to guest/encryptedstore/Android.bp
diff --git a/encryptedstore/README.md b/guest/encryptedstore/README.md
similarity index 90%
rename from encryptedstore/README.md
rename to guest/encryptedstore/README.md
index 3d55d85..5627984 100644
--- a/encryptedstore/README.md
+++ b/guest/encryptedstore/README.md
@@ -26,6 +26,6 @@
 even after OTA/updates of boot images and apks. This requires chipsets to support [Secretkeeper HAL][sk_hal].
 
 
-[vm_payload_api]: https://cs.android.com/android/platform/superproject/main/+/main:packages/modules/Virtualization/vm_payload/include/vm_payload.h;l=2?q=vm_payload%2Finclude%2Fvm_payload.h&ss=android%2Fplatform%2Fsuperproject%2Fmain
+[vm_payload_api]: https://cs.android.com/android/platform/superproject/main/+/main:packages/modules/Virtualization/libs/libvm_payload/include/vm_payload.h;l=2?q=vm_payload%2Finclude%2Fvm_payload.h&ss=android%2Fplatform%2Fsuperproject%2Fmain
 [updatable_vm]: https://cs.android.com/android/platform/superproject/main/+/main:packages/modules/Virtualization/docs/updatable_vm.md
 [sk_hal]: https://cs.android.com/android/platform/superproject/main/+/main:system/secretkeeper/README.md
diff --git a/encryptedstore/TEST_MAPPING b/guest/encryptedstore/TEST_MAPPING
similarity index 100%
rename from encryptedstore/TEST_MAPPING
rename to guest/encryptedstore/TEST_MAPPING
diff --git a/encryptedstore/src/main.rs b/guest/encryptedstore/src/main.rs
similarity index 100%
rename from encryptedstore/src/main.rs
rename to guest/encryptedstore/src/main.rs
diff --git a/microdroid/kdump/Android.bp b/guest/kdump/Android.bp
similarity index 100%
rename from microdroid/kdump/Android.bp
rename to guest/kdump/Android.bp
diff --git a/microdroid/kdump/crashdump.c b/guest/kdump/crashdump.c
similarity index 100%
rename from microdroid/kdump/crashdump.c
rename to guest/kdump/crashdump.c
diff --git a/microdroid/kdump/kernel/Android.bp b/guest/kdump/kernel/Android.bp
similarity index 100%
rename from microdroid/kdump/kernel/Android.bp
rename to guest/kdump/kernel/Android.bp
diff --git a/microdroid/kdump/kernel/arm64/kernel-5.15 b/guest/kdump/kernel/arm64/kernel-5.15
similarity index 100%
rename from microdroid/kdump/kernel/arm64/kernel-5.15
rename to guest/kdump/kernel/arm64/kernel-5.15
Binary files differ
diff --git a/microdroid/kdump/kernel/empty b/guest/kdump/kernel/empty
similarity index 100%
rename from microdroid/kdump/kernel/empty
rename to guest/kdump/kernel/empty
diff --git a/microdroid/kdump/kernel/x86_64/kernel-5.15 b/guest/kdump/kernel/x86_64/kernel-5.15
similarity index 100%
rename from microdroid/kdump/kernel/x86_64/kernel-5.15
rename to guest/kdump/kernel/x86_64/kernel-5.15
Binary files differ
diff --git a/microdroid/kdump/kexec.c b/guest/kdump/kexec.c
similarity index 97%
rename from microdroid/kdump/kexec.c
rename to guest/kdump/kexec.c
index d3e8e02..fcec996 100644
--- a/microdroid/kdump/kexec.c
+++ b/guest/kdump/kexec.c
@@ -39,7 +39,7 @@
 static const char *CMDLINE = "1 panic=-1 rdinit=/bin/crashdump nr_cpus=1 reset_devices "
                              "console=hvc0 " EARLYCON;
 
-static int open_checked(const char* path) {
+static int open_checked(const char *path) {
     int fd = open(path, O_RDONLY);
     if (fd == -1) {
         fprintf(stderr, "Failed to open %s: %s\n", path, strerror(errno));
diff --git a/guest/kernel/Android.bp b/guest/kernel/Android.bp
new file mode 100644
index 0000000..19cdc49
--- /dev/null
+++ b/guest/kernel/Android.bp
@@ -0,0 +1,55 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    default_applicable_licenses: ["microdroid_kernel_prebuilts_license"],
+}
+
+// See: http://go/android-license-faq
+license {
+    name: "microdroid_kernel_prebuilts_license",
+    visibility: [":__subpackages__"],
+    license_kinds: [
+        "SPDX-license-identifier-GPL-2.0-only",
+    ],
+    // large-scale-change unable to identify any license_text files
+}
+
+filegroup {
+    name: "microdroid_kernel_prebuilt-arm64",
+    // Below are properties that are conditionally set depending on value of build flags.
+    srcs: select(release_flag("RELEASE_AVF_MICRODROID_KERNEL_VERSION"), {
+        "android14_61": ["android14-6.1/arm64/kernel-6.1"],
+        "android15_66": ["android15-6.6/arm64/kernel-6.6"],
+        // In case release configuration doesn't specify value of the
+        // RELEASE_AVF_MICRODROID_KERNEL_VERSION fallback to the kernel we
+        // already released.
+        // TODO(b/298011555): remove this once we set the flag in all release configs.
+        default: ["android14-6.1/arm64/kernel-6.1"],
+    }),
+}
+
+filegroup {
+    name: "microdroid_kernel_prebuilt-x86_64",
+    // Below are properties that are conditionally set depending on value of build flags.
+    srcs: select(release_flag("RELEASE_AVF_MICRODROID_KERNEL_VERSION"), {
+        "android14_61": ["android14-6.1/x86_64/kernel-6.1"],
+        "android15_66": ["android15-6.6/x86_64/kernel-6.6"],
+        // In case release configuration doesn't specify value of the
+        // RELEASE_AVF_MICRODROID_KERNEL_VERSION fallback to the kernel we
+        // already released.
+        // TODO(b/298011555): remove this once we set the flag in all release configs.
+        default: ["android14-6.1/x86_64/kernel-6.1"],
+    }),
+}
diff --git a/microdroid/kernel/README.md b/guest/kernel/README.md
similarity index 94%
rename from microdroid/kernel/README.md
rename to guest/kernel/README.md
index 52df333..c4ffb81 100644
--- a/microdroid/kernel/README.md
+++ b/guest/kernel/README.md
@@ -10,7 +10,7 @@
 ### Checkout the GKI source code.
 
 ```bash
-repo init -u https://android.googlesource.com/kernel/manifest -b common-android14-6.1
+repo init -u https://android.googlesource.com/kernel/manifest -b common-android15-6.6
 repo sync
 ```
 
@@ -55,12 +55,12 @@
 
 For ARM64,
 ```bash
-cp out/dist/Image <android_checkout>/packages/modules/Virtualization/microdroid/kernel/arm64/kernel-6.1
+cp out/dist/Image <android_checkout>/packages/modules/Virtualization/guest/kernel/android15-6.6/arm64/kernel-6.6
 ```
 
 For x86\_64,
 ```bash
-cp out/dist/bzImage <android_checkout>/packages/modules/Virtualization/microdroid/kernel/x86_64/kernel-6.1
+cp out/dist/bzImage <android_checkout>/packages/modules/Virtualization/guest/kernel/android15-6.6/x86_64/kernel-6.6
 ```
 
 ### For official updates
diff --git a/microdroid/kernel/android14-6.1/arm64/System.map b/guest/kernel/android14-6.1/arm64/System.map
similarity index 100%
rename from microdroid/kernel/android14-6.1/arm64/System.map
rename to guest/kernel/android14-6.1/arm64/System.map
diff --git a/microdroid/kernel/android14-6.1/arm64/kernel-6.1 b/guest/kernel/android14-6.1/arm64/kernel-6.1
similarity index 100%
rename from microdroid/kernel/android14-6.1/arm64/kernel-6.1
rename to guest/kernel/android14-6.1/arm64/kernel-6.1
Binary files differ
diff --git a/microdroid/kernel/android14-6.1/arm64/kernel-6.1-gz b/guest/kernel/android14-6.1/arm64/kernel-6.1-gz
similarity index 100%
rename from microdroid/kernel/android14-6.1/arm64/kernel-6.1-gz
rename to guest/kernel/android14-6.1/arm64/kernel-6.1-gz
Binary files differ
diff --git a/microdroid/kernel/android14-6.1/arm64/kernel-6.1-lz4 b/guest/kernel/android14-6.1/arm64/kernel-6.1-lz4
similarity index 100%
rename from microdroid/kernel/android14-6.1/arm64/kernel-6.1-lz4
rename to guest/kernel/android14-6.1/arm64/kernel-6.1-lz4
Binary files differ
diff --git a/microdroid/kernel/android14-6.1/arm64/prebuilt-info.txt b/guest/kernel/android14-6.1/arm64/prebuilt-info.txt
similarity index 100%
rename from microdroid/kernel/android14-6.1/arm64/prebuilt-info.txt
rename to guest/kernel/android14-6.1/arm64/prebuilt-info.txt
diff --git a/microdroid/kernel/android14-6.1/x86_64/System.map b/guest/kernel/android14-6.1/x86_64/System.map
similarity index 100%
rename from microdroid/kernel/android14-6.1/x86_64/System.map
rename to guest/kernel/android14-6.1/x86_64/System.map
diff --git a/microdroid/kernel/android14-6.1/x86_64/kernel-6.1 b/guest/kernel/android14-6.1/x86_64/kernel-6.1
similarity index 100%
rename from microdroid/kernel/android14-6.1/x86_64/kernel-6.1
rename to guest/kernel/android14-6.1/x86_64/kernel-6.1
Binary files differ
diff --git a/microdroid/kernel/android14-6.1/x86_64/prebuilt-info.txt b/guest/kernel/android14-6.1/x86_64/prebuilt-info.txt
similarity index 100%
rename from microdroid/kernel/android14-6.1/x86_64/prebuilt-info.txt
rename to guest/kernel/android14-6.1/x86_64/prebuilt-info.txt
diff --git a/microdroid/kernel/android15-6.6/arm64/System.map b/guest/kernel/android15-6.6/arm64/System.map
similarity index 100%
rename from microdroid/kernel/android15-6.6/arm64/System.map
rename to guest/kernel/android15-6.6/arm64/System.map
diff --git a/microdroid/kernel/android15-6.6/arm64/kernel-6.6 b/guest/kernel/android15-6.6/arm64/kernel-6.6
similarity index 100%
rename from microdroid/kernel/android15-6.6/arm64/kernel-6.6
rename to guest/kernel/android15-6.6/arm64/kernel-6.6
Binary files differ
diff --git a/microdroid/kernel/android15-6.6/arm64/kernel-6.6-gz b/guest/kernel/android15-6.6/arm64/kernel-6.6-gz
similarity index 100%
rename from microdroid/kernel/android15-6.6/arm64/kernel-6.6-gz
rename to guest/kernel/android15-6.6/arm64/kernel-6.6-gz
Binary files differ
diff --git a/microdroid/kernel/android15-6.6/arm64/kernel-6.6-lz4 b/guest/kernel/android15-6.6/arm64/kernel-6.6-lz4
similarity index 100%
rename from microdroid/kernel/android15-6.6/arm64/kernel-6.6-lz4
rename to guest/kernel/android15-6.6/arm64/kernel-6.6-lz4
Binary files differ
diff --git a/microdroid/kernel/android15-6.6/arm64/kernel_version.mk b/guest/kernel/android15-6.6/arm64/kernel_version.mk
similarity index 100%
rename from microdroid/kernel/android15-6.6/arm64/kernel_version.mk
rename to guest/kernel/android15-6.6/arm64/kernel_version.mk
diff --git a/microdroid/kernel/android15-6.6/arm64/prebuilt-info.txt b/guest/kernel/android15-6.6/arm64/prebuilt-info.txt
similarity index 100%
rename from microdroid/kernel/android15-6.6/arm64/prebuilt-info.txt
rename to guest/kernel/android15-6.6/arm64/prebuilt-info.txt
diff --git a/microdroid/kernel/android15-6.6/x86_64/System.map b/guest/kernel/android15-6.6/x86_64/System.map
similarity index 100%
rename from microdroid/kernel/android15-6.6/x86_64/System.map
rename to guest/kernel/android15-6.6/x86_64/System.map
diff --git a/microdroid/kernel/android15-6.6/x86_64/kernel-6.6 b/guest/kernel/android15-6.6/x86_64/kernel-6.6
similarity index 100%
rename from microdroid/kernel/android15-6.6/x86_64/kernel-6.6
rename to guest/kernel/android15-6.6/x86_64/kernel-6.6
Binary files differ
diff --git a/microdroid/kernel/android15-6.6/x86_64/kernel_version.mk b/guest/kernel/android15-6.6/x86_64/kernel_version.mk
similarity index 100%
rename from microdroid/kernel/android15-6.6/x86_64/kernel_version.mk
rename to guest/kernel/android15-6.6/x86_64/kernel_version.mk
diff --git a/microdroid/kernel/android15-6.6/x86_64/prebuilt-info.txt b/guest/kernel/android15-6.6/x86_64/prebuilt-info.txt
similarity index 100%
rename from microdroid/kernel/android15-6.6/x86_64/prebuilt-info.txt
rename to guest/kernel/android15-6.6/x86_64/prebuilt-info.txt
diff --git a/launcher/Android.bp b/guest/microdroid_launcher/Android.bp
similarity index 100%
rename from launcher/Android.bp
rename to guest/microdroid_launcher/Android.bp
diff --git a/launcher/main.cpp b/guest/microdroid_launcher/main.cpp
similarity index 100%
rename from launcher/main.cpp
rename to guest/microdroid_launcher/main.cpp
diff --git a/microdroid_manager/Android.bp b/guest/microdroid_manager/Android.bp
similarity index 100%
rename from microdroid_manager/Android.bp
rename to guest/microdroid_manager/Android.bp
diff --git a/microdroid_manager/TEST_MAPPING b/guest/microdroid_manager/TEST_MAPPING
similarity index 100%
rename from microdroid_manager/TEST_MAPPING
rename to guest/microdroid_manager/TEST_MAPPING
diff --git a/microdroid_manager/aidl/Android.bp b/guest/microdroid_manager/aidl/Android.bp
similarity index 100%
rename from microdroid_manager/aidl/Android.bp
rename to guest/microdroid_manager/aidl/Android.bp
diff --git a/microdroid_manager/aidl/android/system/virtualization/payload/IVmPayloadService.aidl b/guest/microdroid_manager/aidl/android/system/virtualization/payload/IVmPayloadService.aidl
similarity index 100%
rename from microdroid_manager/aidl/android/system/virtualization/payload/IVmPayloadService.aidl
rename to guest/microdroid_manager/aidl/android/system/virtualization/payload/IVmPayloadService.aidl
diff --git a/microdroid_manager/microdroid_manager.rc b/guest/microdroid_manager/microdroid_manager.rc
similarity index 100%
rename from microdroid_manager/microdroid_manager.rc
rename to guest/microdroid_manager/microdroid_manager.rc
diff --git a/microdroid_manager/src/dice.rs b/guest/microdroid_manager/src/dice.rs
similarity index 100%
rename from microdroid_manager/src/dice.rs
rename to guest/microdroid_manager/src/dice.rs
diff --git a/microdroid_manager/src/instance.rs b/guest/microdroid_manager/src/instance.rs
similarity index 100%
rename from microdroid_manager/src/instance.rs
rename to guest/microdroid_manager/src/instance.rs
diff --git a/microdroid_manager/src/ioutil.rs b/guest/microdroid_manager/src/ioutil.rs
similarity index 100%
rename from microdroid_manager/src/ioutil.rs
rename to guest/microdroid_manager/src/ioutil.rs
diff --git a/microdroid_manager/src/main.rs b/guest/microdroid_manager/src/main.rs
similarity index 100%
rename from microdroid_manager/src/main.rs
rename to guest/microdroid_manager/src/main.rs
diff --git a/microdroid_manager/src/payload.rs b/guest/microdroid_manager/src/payload.rs
similarity index 100%
rename from microdroid_manager/src/payload.rs
rename to guest/microdroid_manager/src/payload.rs
diff --git a/microdroid_manager/src/swap.rs b/guest/microdroid_manager/src/swap.rs
similarity index 100%
rename from microdroid_manager/src/swap.rs
rename to guest/microdroid_manager/src/swap.rs
diff --git a/microdroid_manager/src/verify.rs b/guest/microdroid_manager/src/verify.rs
similarity index 100%
rename from microdroid_manager/src/verify.rs
rename to guest/microdroid_manager/src/verify.rs
diff --git a/microdroid_manager/src/vm_payload_service.rs b/guest/microdroid_manager/src/vm_payload_service.rs
similarity index 100%
rename from microdroid_manager/src/vm_payload_service.rs
rename to guest/microdroid_manager/src/vm_payload_service.rs
diff --git a/microdroid_manager/src/vm_secret.rs b/guest/microdroid_manager/src/vm_secret.rs
similarity index 100%
rename from microdroid_manager/src/vm_secret.rs
rename to guest/microdroid_manager/src/vm_secret.rs
diff --git a/pvmfw/Android.bp b/guest/pvmfw/Android.bp
similarity index 100%
rename from pvmfw/Android.bp
rename to guest/pvmfw/Android.bp
diff --git a/pvmfw/README.md b/guest/pvmfw/README.md
similarity index 95%
rename from pvmfw/README.md
rename to guest/pvmfw/README.md
index 7a03f0b..cc5ae71 100644
--- a/pvmfw/README.md
+++ b/guest/pvmfw/README.md
@@ -291,6 +291,19 @@
 `/reserved-memory` device tree node marked as
 [`compatible=”google,open-dice”`][dice-dt].
 
+#### Testing
+
+To verify that the DICE handover is successful in pvmfw and eventually the pVM
+has a valid DICE chain, you can run the VSR test
+`MicrodroidTests#protectedVmHasValidDiceChain`. The test retrieves the DICE
+chain from within a Microdroid VM in protected mode and checks the following
+properties using the [hwtrust][hwtrust] library:
+
+1. All the fields in the DICE chain conform to
+   [Android Profile for DICE][android-open-dice].
+2. The DICE chain is a valid certificate chain, where the subject public key in
+   each certificate can be used to verify the signature of the next certificate.
+
 [AVB]: https://source.android.com/docs/security/features/verifiedboot/boot-flow
 [AndroidDiceHandover]: https://pigweed.googlesource.com/open-dice/+/42ae7760023/src/android.c#212
 [DiceAndroidHandoverMainFlow]: https://pigweed.googlesource.com/open-dice/+/42ae7760023/src/android.c#221
@@ -299,6 +312,8 @@
 [dice-dt]: https://www.kernel.org/doc/Documentation/devicetree/bindings/reserved-memory/google%2Copen-dice.yaml
 [Layering]: https://pigweed.googlesource.com/open-dice/+/refs/heads/main/docs/specification.md#layering-details
 [Trusty-BCC]: https://android.googlesource.com/trusty/lib/+/1696be0a8f3a7103/lib/hwbcc/common/swbcc.c#554
+[hwtrust]: https://cs.android.com/android/platform/superproject/main/+/main:tools/security/remote_provisioning/hwtrust/
+[android-open-dice]: https://android.googlesource.com/platform/external/open-dice/+/refs/heads/main/docs/android.md
 
 ### Platform Requirements
 
diff --git a/pvmfw/TEST_MAPPING b/guest/pvmfw/TEST_MAPPING
similarity index 100%
rename from pvmfw/TEST_MAPPING
rename to guest/pvmfw/TEST_MAPPING
diff --git a/pvmfw/avb/Android.bp b/guest/pvmfw/avb/Android.bp
similarity index 100%
rename from pvmfw/avb/Android.bp
rename to guest/pvmfw/avb/Android.bp
diff --git a/pvmfw/avb/fuzz/Android.bp b/guest/pvmfw/avb/fuzz/Android.bp
similarity index 100%
rename from pvmfw/avb/fuzz/Android.bp
rename to guest/pvmfw/avb/fuzz/Android.bp
diff --git a/pvmfw/avb/fuzz/with_footer_verify_fuzzer.rs b/guest/pvmfw/avb/fuzz/with_footer_verify_fuzzer.rs
similarity index 96%
rename from pvmfw/avb/fuzz/with_footer_verify_fuzzer.rs
rename to guest/pvmfw/avb/fuzz/with_footer_verify_fuzzer.rs
index 6eed15b..141361e 100644
--- a/pvmfw/avb/fuzz/with_footer_verify_fuzzer.rs
+++ b/guest/pvmfw/avb/fuzz/with_footer_verify_fuzzer.rs
@@ -55,5 +55,5 @@
         .copy_from_slice(&AVB_MAGIC[..(AVB_MAGIC_LEN as usize)]);
     modified_kernel[kernel_and_vbmeta.len()..].copy_from_slice(&avb_footer);
 
-    let _ = verify_payload(&modified_kernel, /*initrd=*/ None, &[0u8; 64]);
+    let _ = verify_payload(&modified_kernel, /* initrd= */ None, &[0u8; 64]);
 });
diff --git a/pvmfw/avb/fuzz/without_footer_verify_fuzzer.rs b/guest/pvmfw/avb/fuzz/without_footer_verify_fuzzer.rs
similarity index 93%
rename from pvmfw/avb/fuzz/without_footer_verify_fuzzer.rs
rename to guest/pvmfw/avb/fuzz/without_footer_verify_fuzzer.rs
index fc8fa85..d57f96f 100644
--- a/pvmfw/avb/fuzz/without_footer_verify_fuzzer.rs
+++ b/guest/pvmfw/avb/fuzz/without_footer_verify_fuzzer.rs
@@ -24,5 +24,5 @@
     // kernel can pass the kernel verification, so the value of `initrd`
     // is not so important as we won't reach initrd verification with
     // this fuzzer.
-    let _ = verify_payload(kernel, /*initrd=*/ None, &[0u8; 64]);
+    let _ = verify_payload(kernel, /* initrd= */ None, &[0u8; 64]);
 });
diff --git a/pvmfw/avb/src/error.rs b/guest/pvmfw/avb/src/error.rs
similarity index 100%
rename from pvmfw/avb/src/error.rs
rename to guest/pvmfw/avb/src/error.rs
diff --git a/pvmfw/avb/src/lib.rs b/guest/pvmfw/avb/src/lib.rs
similarity index 100%
rename from pvmfw/avb/src/lib.rs
rename to guest/pvmfw/avb/src/lib.rs
diff --git a/pvmfw/avb/src/ops.rs b/guest/pvmfw/avb/src/ops.rs
similarity index 100%
rename from pvmfw/avb/src/ops.rs
rename to guest/pvmfw/avb/src/ops.rs
diff --git a/pvmfw/avb/src/partition.rs b/guest/pvmfw/avb/src/partition.rs
similarity index 100%
rename from pvmfw/avb/src/partition.rs
rename to guest/pvmfw/avb/src/partition.rs
diff --git a/pvmfw/avb/src/verify.rs b/guest/pvmfw/avb/src/verify.rs
similarity index 100%
rename from pvmfw/avb/src/verify.rs
rename to guest/pvmfw/avb/src/verify.rs
diff --git a/pvmfw/avb/tests/api_test.rs b/guest/pvmfw/avb/tests/api_test.rs
similarity index 100%
rename from pvmfw/avb/tests/api_test.rs
rename to guest/pvmfw/avb/tests/api_test.rs
diff --git a/pvmfw/avb/tests/utils.rs b/guest/pvmfw/avb/tests/utils.rs
similarity index 100%
rename from pvmfw/avb/tests/utils.rs
rename to guest/pvmfw/avb/tests/utils.rs
diff --git a/pvmfw/idmap.S b/guest/pvmfw/idmap.S
similarity index 100%
rename from pvmfw/idmap.S
rename to guest/pvmfw/idmap.S
diff --git a/pvmfw/image.ld b/guest/pvmfw/image.ld
similarity index 93%
rename from pvmfw/image.ld
rename to guest/pvmfw/image.ld
index 18bb3ba..fb26806 100644
--- a/pvmfw/image.ld
+++ b/guest/pvmfw/image.ld
@@ -18,5 +18,4 @@
 {
 	image		: ORIGIN = 0x7fc00000, LENGTH = 2M
 	writable_data	: ORIGIN = 0x7fe00000, LENGTH = 2M
-	dtb_region	: ORIGIN = 0x80000000, LENGTH = 2M
 }
diff --git a/pvmfw/platform.dts b/guest/pvmfw/platform.dts
similarity index 100%
rename from pvmfw/platform.dts
rename to guest/pvmfw/platform.dts
diff --git a/pvmfw/src/bcc.rs b/guest/pvmfw/src/bcc.rs
similarity index 100%
rename from pvmfw/src/bcc.rs
rename to guest/pvmfw/src/bcc.rs
diff --git a/pvmfw/src/bootargs.rs b/guest/pvmfw/src/bootargs.rs
similarity index 100%
rename from pvmfw/src/bootargs.rs
rename to guest/pvmfw/src/bootargs.rs
diff --git a/pvmfw/src/config.rs b/guest/pvmfw/src/config.rs
similarity index 100%
rename from pvmfw/src/config.rs
rename to guest/pvmfw/src/config.rs
diff --git a/pvmfw/src/device_assignment.rs b/guest/pvmfw/src/device_assignment.rs
similarity index 100%
rename from pvmfw/src/device_assignment.rs
rename to guest/pvmfw/src/device_assignment.rs
diff --git a/pvmfw/src/dice.rs b/guest/pvmfw/src/dice.rs
similarity index 99%
rename from pvmfw/src/dice.rs
rename to guest/pvmfw/src/dice.rs
index 8be73a4..470711f 100644
--- a/pvmfw/src/dice.rs
+++ b/guest/pvmfw/src/dice.rs
@@ -36,8 +36,10 @@
 #[derive(Debug)]
 pub enum Error {
     /// Error in CBOR operations
+    #[allow(dead_code)]
     CborError(ciborium::value::Error),
     /// Error in DICE operations
+    #[allow(dead_code)]
     DiceError(diced_open_dice::DiceError),
 }
 
diff --git a/pvmfw/src/entry.rs b/guest/pvmfw/src/entry.rs
similarity index 97%
rename from pvmfw/src/entry.rs
rename to guest/pvmfw/src/entry.rs
index 0ff7270..ce04317 100644
--- a/pvmfw/src/entry.rs
+++ b/guest/pvmfw/src/entry.rs
@@ -256,7 +256,7 @@
     )?;
 
     // This wrapper allows main() to be blissfully ignorant of platform details.
-    let next_bcc = crate::main(
+    let (next_bcc, debuggable_payload) = crate::main(
         slices.fdt,
         slices.kernel,
         slices.ramdisk,
@@ -274,11 +274,15 @@
     })?;
     // Call unshare_all_memory here (instead of relying on the dtor) while UART is still mapped.
     MEMORY.lock().as_mut().unwrap().unshare_all_memory();
+
     if let Some(mmio_guard) = get_mmio_guard() {
-        mmio_guard.unmap(UART_PAGE_ADDR).map_err(|e| {
-            error!("Failed to unshare the UART: {e}");
-            RebootReason::InternalError
-        })?;
+        // Keep UART MMIO_GUARD-ed for debuggable payloads, to enable earlycon.
+        if !debuggable_payload {
+            mmio_guard.unmap(UART_PAGE_ADDR).map_err(|e| {
+                error!("Failed to unshare the UART: {e}");
+                RebootReason::InternalError
+            })?;
+        }
     }
 
     // Drop MemoryTracker and deactivate page table.
diff --git a/pvmfw/src/exceptions.rs b/guest/pvmfw/src/exceptions.rs
similarity index 100%
rename from pvmfw/src/exceptions.rs
rename to guest/pvmfw/src/exceptions.rs
diff --git a/pvmfw/src/fdt.rs b/guest/pvmfw/src/fdt.rs
similarity index 99%
rename from pvmfw/src/fdt.rs
rename to guest/pvmfw/src/fdt.rs
index 939a4ea..953fdae 100644
--- a/pvmfw/src/fdt.rs
+++ b/guest/pvmfw/src/fdt.rs
@@ -593,7 +593,6 @@
 ) -> Result<(), RebootReason> {
     let mem_flags = PciMemoryFlags(range.addr.0);
     let range_type = mem_flags.range_type();
-    let prefetchable = mem_flags.prefetchable();
     let bus_addr = range.addr.1;
     let cpu_addr = range.parent_addr;
     let size = range.size;
@@ -602,10 +601,6 @@
         error!("Invalid range type {:?} for bus address {:#x} in PCI node", range_type, bus_addr);
         return Err(RebootReason::InvalidFdt);
     }
-    if prefetchable {
-        error!("PCI bus address {:#x} in PCI node is prefetchable", bus_addr);
-        return Err(RebootReason::InvalidFdt);
-    }
     // Enforce ID bus-to-cpu mappings, as used by crosvm.
     if bus_addr != cpu_addr {
         error!("PCI bus address: {:#x} is different from CPU address: {:#x}", bus_addr, cpu_addr);
diff --git a/pvmfw/src/gpt.rs b/guest/pvmfw/src/gpt.rs
similarity index 100%
rename from pvmfw/src/gpt.rs
rename to guest/pvmfw/src/gpt.rs
diff --git a/pvmfw/src/helpers.rs b/guest/pvmfw/src/helpers.rs
similarity index 100%
rename from pvmfw/src/helpers.rs
rename to guest/pvmfw/src/helpers.rs
diff --git a/pvmfw/src/instance.rs b/guest/pvmfw/src/instance.rs
similarity index 98%
rename from pvmfw/src/instance.rs
rename to guest/pvmfw/src/instance.rs
index 43c7442..0ef57dc 100644
--- a/pvmfw/src/instance.rs
+++ b/guest/pvmfw/src/instance.rs
@@ -237,7 +237,7 @@
 
 /// Marks the start of an instance.img entry.
 ///
-/// Note: Virtualization/microdroid_manager/src/instance.rs uses the name "partition".
+/// Note: Virtualization/guest/microdroid_manager/src/instance.rs uses the name "partition".
 #[derive(AsBytes, FromZeroes, FromBytes)]
 #[repr(C, packed)]
 struct EntryHeader {
diff --git a/pvmfw/src/main.rs b/guest/pvmfw/src/main.rs
similarity index 98%
rename from pvmfw/src/main.rs
rename to guest/pvmfw/src/main.rs
index 247aa6a..10f8549 100644
--- a/pvmfw/src/main.rs
+++ b/guest/pvmfw/src/main.rs
@@ -67,7 +67,7 @@
     ramdisk: Option<&[u8]>,
     current_bcc_handover: &[u8],
     mut debug_policy: Option<&[u8]>,
-) -> Result<Range<usize>, RebootReason> {
+) -> Result<(Range<usize>, bool), RebootReason> {
     info!("pVM firmware");
     debug!("FDT: {:?}", fdt.as_ptr());
     debug!("Signed kernel: {:?} ({:#x} bytes)", signed_kernel.as_ptr(), signed_kernel.len());
@@ -240,7 +240,7 @@
         (r.start as usize)..(r.end as usize)
     };
 
-    Ok(bcc_range)
+    Ok((bcc_range, debuggable))
 }
 
 fn check_dice_measurements_match_entry(
diff --git a/pvmfw/src/memory.rs b/guest/pvmfw/src/memory.rs
similarity index 100%
rename from pvmfw/src/memory.rs
rename to guest/pvmfw/src/memory.rs
diff --git a/pvmfw/testdata/expected_dt_with_dependency.dts b/guest/pvmfw/testdata/expected_dt_with_dependency.dts
similarity index 100%
rename from pvmfw/testdata/expected_dt_with_dependency.dts
rename to guest/pvmfw/testdata/expected_dt_with_dependency.dts
diff --git a/pvmfw/testdata/expected_dt_with_dependency_loop.dts b/guest/pvmfw/testdata/expected_dt_with_dependency_loop.dts
similarity index 100%
rename from pvmfw/testdata/expected_dt_with_dependency_loop.dts
rename to guest/pvmfw/testdata/expected_dt_with_dependency_loop.dts
diff --git a/pvmfw/testdata/expected_dt_with_multiple_dependencies.dts b/guest/pvmfw/testdata/expected_dt_with_multiple_dependencies.dts
similarity index 100%
rename from pvmfw/testdata/expected_dt_with_multiple_dependencies.dts
rename to guest/pvmfw/testdata/expected_dt_with_multiple_dependencies.dts
diff --git a/pvmfw/testdata/test_crosvm_dt_base.dtsi b/guest/pvmfw/testdata/test_crosvm_dt_base.dtsi
similarity index 100%
rename from pvmfw/testdata/test_crosvm_dt_base.dtsi
rename to guest/pvmfw/testdata/test_crosvm_dt_base.dtsi
diff --git a/pvmfw/testdata/test_pvmfw_devices_overlapping_pvmfw.dts b/guest/pvmfw/testdata/test_pvmfw_devices_overlapping_pvmfw.dts
similarity index 100%
rename from pvmfw/testdata/test_pvmfw_devices_overlapping_pvmfw.dts
rename to guest/pvmfw/testdata/test_pvmfw_devices_overlapping_pvmfw.dts
diff --git a/pvmfw/testdata/test_pvmfw_devices_vm_dtbo.dts b/guest/pvmfw/testdata/test_pvmfw_devices_vm_dtbo.dts
similarity index 100%
rename from pvmfw/testdata/test_pvmfw_devices_vm_dtbo.dts
rename to guest/pvmfw/testdata/test_pvmfw_devices_vm_dtbo.dts
diff --git a/pvmfw/testdata/test_pvmfw_devices_vm_dtbo_with_dependencies.dts b/guest/pvmfw/testdata/test_pvmfw_devices_vm_dtbo_with_dependencies.dts
similarity index 100%
rename from pvmfw/testdata/test_pvmfw_devices_vm_dtbo_with_dependencies.dts
rename to guest/pvmfw/testdata/test_pvmfw_devices_vm_dtbo_with_dependencies.dts
diff --git a/pvmfw/testdata/test_pvmfw_devices_vm_dtbo_with_duplicated_iommus.dts b/guest/pvmfw/testdata/test_pvmfw_devices_vm_dtbo_with_duplicated_iommus.dts
similarity index 100%
rename from pvmfw/testdata/test_pvmfw_devices_vm_dtbo_with_duplicated_iommus.dts
rename to guest/pvmfw/testdata/test_pvmfw_devices_vm_dtbo_with_duplicated_iommus.dts
diff --git a/pvmfw/testdata/test_pvmfw_devices_vm_dtbo_without_symbols.dts b/guest/pvmfw/testdata/test_pvmfw_devices_vm_dtbo_without_symbols.dts
similarity index 100%
rename from pvmfw/testdata/test_pvmfw_devices_vm_dtbo_without_symbols.dts
rename to guest/pvmfw/testdata/test_pvmfw_devices_vm_dtbo_without_symbols.dts
diff --git a/pvmfw/testdata/test_pvmfw_devices_with_dependency.dts b/guest/pvmfw/testdata/test_pvmfw_devices_with_dependency.dts
similarity index 100%
rename from pvmfw/testdata/test_pvmfw_devices_with_dependency.dts
rename to guest/pvmfw/testdata/test_pvmfw_devices_with_dependency.dts
diff --git a/pvmfw/testdata/test_pvmfw_devices_with_dependency_loop.dts b/guest/pvmfw/testdata/test_pvmfw_devices_with_dependency_loop.dts
similarity index 100%
rename from pvmfw/testdata/test_pvmfw_devices_with_dependency_loop.dts
rename to guest/pvmfw/testdata/test_pvmfw_devices_with_dependency_loop.dts
diff --git a/pvmfw/testdata/test_pvmfw_devices_with_duplicated_pviommus.dts b/guest/pvmfw/testdata/test_pvmfw_devices_with_duplicated_pviommus.dts
similarity index 100%
rename from pvmfw/testdata/test_pvmfw_devices_with_duplicated_pviommus.dts
rename to guest/pvmfw/testdata/test_pvmfw_devices_with_duplicated_pviommus.dts
diff --git a/pvmfw/testdata/test_pvmfw_devices_with_iommu_id_conflict.dts b/guest/pvmfw/testdata/test_pvmfw_devices_with_iommu_id_conflict.dts
similarity index 100%
rename from pvmfw/testdata/test_pvmfw_devices_with_iommu_id_conflict.dts
rename to guest/pvmfw/testdata/test_pvmfw_devices_with_iommu_id_conflict.dts
diff --git a/pvmfw/testdata/test_pvmfw_devices_with_iommu_sharing.dts b/guest/pvmfw/testdata/test_pvmfw_devices_with_iommu_sharing.dts
similarity index 100%
rename from pvmfw/testdata/test_pvmfw_devices_with_iommu_sharing.dts
rename to guest/pvmfw/testdata/test_pvmfw_devices_with_iommu_sharing.dts
diff --git a/pvmfw/testdata/test_pvmfw_devices_with_multiple_dependencies.dts b/guest/pvmfw/testdata/test_pvmfw_devices_with_multiple_dependencies.dts
similarity index 100%
rename from pvmfw/testdata/test_pvmfw_devices_with_multiple_dependencies.dts
rename to guest/pvmfw/testdata/test_pvmfw_devices_with_multiple_dependencies.dts
diff --git a/pvmfw/testdata/test_pvmfw_devices_with_multiple_devices_iommus.dts b/guest/pvmfw/testdata/test_pvmfw_devices_with_multiple_devices_iommus.dts
similarity index 100%
rename from pvmfw/testdata/test_pvmfw_devices_with_multiple_devices_iommus.dts
rename to guest/pvmfw/testdata/test_pvmfw_devices_with_multiple_devices_iommus.dts
diff --git a/pvmfw/testdata/test_pvmfw_devices_with_multiple_reg_iommus.dts b/guest/pvmfw/testdata/test_pvmfw_devices_with_multiple_reg_iommus.dts
similarity index 100%
rename from pvmfw/testdata/test_pvmfw_devices_with_multiple_reg_iommus.dts
rename to guest/pvmfw/testdata/test_pvmfw_devices_with_multiple_reg_iommus.dts
diff --git a/pvmfw/testdata/test_pvmfw_devices_with_rng.dts b/guest/pvmfw/testdata/test_pvmfw_devices_with_rng.dts
similarity index 100%
rename from pvmfw/testdata/test_pvmfw_devices_with_rng.dts
rename to guest/pvmfw/testdata/test_pvmfw_devices_with_rng.dts
diff --git a/pvmfw/testdata/test_pvmfw_devices_without_device.dts b/guest/pvmfw/testdata/test_pvmfw_devices_without_device.dts
similarity index 100%
rename from pvmfw/testdata/test_pvmfw_devices_without_device.dts
rename to guest/pvmfw/testdata/test_pvmfw_devices_without_device.dts
diff --git a/pvmfw/testdata/test_pvmfw_devices_without_iommus.dts b/guest/pvmfw/testdata/test_pvmfw_devices_without_iommus.dts
similarity index 100%
rename from pvmfw/testdata/test_pvmfw_devices_without_iommus.dts
rename to guest/pvmfw/testdata/test_pvmfw_devices_without_iommus.dts
diff --git a/rialto/Android.bp b/guest/rialto/Android.bp
similarity index 100%
rename from rialto/Android.bp
rename to guest/rialto/Android.bp
diff --git a/rialto/AndroidTest.xml b/guest/rialto/AndroidTest.xml
similarity index 100%
rename from rialto/AndroidTest.xml
rename to guest/rialto/AndroidTest.xml
diff --git a/rialto/TEST_MAPPING b/guest/rialto/TEST_MAPPING
similarity index 100%
rename from rialto/TEST_MAPPING
rename to guest/rialto/TEST_MAPPING
diff --git a/rialto/idmap.S b/guest/rialto/idmap.S
similarity index 91%
rename from rialto/idmap.S
rename to guest/rialto/idmap.S
index 7281d9b..eb4d823 100644
--- a/rialto/idmap.S
+++ b/guest/rialto/idmap.S
@@ -28,9 +28,8 @@
 .set .PAGE_SIZE, .SZ_4K
 
 .set .ORIGIN_ADDR, 2 * .SZ_1G
-.set .DTB_ADDR, .ORIGIN_ADDR + (0 * .SZ_2M)
-.set .TEXT_ADDR, .ORIGIN_ADDR + (1 * .SZ_2M)
-.set .DATA_ADDR, .ORIGIN_ADDR + (2 * .SZ_2M)
+.set .TEXT_ADDR, .ORIGIN_ADDR + (0 * .SZ_2M)
+.set .DATA_ADDR, .ORIGIN_ADDR + (1 * .SZ_2M)
 
 .set .L_TT_TYPE_BLOCK, 0x1
 .set .L_TT_TYPE_PAGE,  0x3
@@ -60,7 +59,7 @@
 	.balign .PAGE_SIZE, 0				// unmapped
 
 	/* level 2 */
-0:	.quad		.L_BLOCK_RO  | .DTB_ADDR	// DT provided by VMM
+0:
 	.quad		.L_BLOCK_MEM_XIP | .TEXT_ADDR	// 2 MiB of DRAM containing image
 	.quad		.L_BLOCK_MEM | .DATA_ADDR	// 2 MiB of writable DRAM
 	.balign .PAGE_SIZE, 0				// unmapped
diff --git a/rialto/image.ld b/guest/rialto/image.ld
similarity index 81%
rename from rialto/image.ld
rename to guest/rialto/image.ld
index 368acbb..3bf910c 100644
--- a/rialto/image.ld
+++ b/guest/rialto/image.ld
@@ -16,7 +16,6 @@
 
 MEMORY
 {
-	dtb_region	: ORIGIN = 0x80000000, LENGTH = 2M
-	image		: ORIGIN = 0x80200000, LENGTH = 2M
-	writable_data	: ORIGIN = 0x80400000, LENGTH = 2M
+	image		: ORIGIN = 0x80000000, LENGTH = 2M
+	writable_data	: ORIGIN = 0x80200000, LENGTH = 2M
 }
diff --git a/rialto/src/communication.rs b/guest/rialto/src/communication.rs
similarity index 100%
rename from rialto/src/communication.rs
rename to guest/rialto/src/communication.rs
diff --git a/rialto/src/error.rs b/guest/rialto/src/error.rs
similarity index 100%
rename from rialto/src/error.rs
rename to guest/rialto/src/error.rs
diff --git a/rialto/src/exceptions.rs b/guest/rialto/src/exceptions.rs
similarity index 100%
rename from rialto/src/exceptions.rs
rename to guest/rialto/src/exceptions.rs
diff --git a/rialto/src/fdt.rs b/guest/rialto/src/fdt.rs
similarity index 85%
rename from rialto/src/fdt.rs
rename to guest/rialto/src/fdt.rs
index b220f41..e97a262 100644
--- a/rialto/src/fdt.rs
+++ b/guest/rialto/src/fdt.rs
@@ -29,3 +29,10 @@
     let node = fdt.node(cstr!("/avf"))?.ok_or(FdtError::NotFound)?;
     node.getprop(cstr!("vendor_hashtree_descriptor_root_digest"))
 }
+
+pub(crate) fn read_is_strict_boot(fdt: &Fdt) -> libfdt::Result<bool> {
+    match fdt.chosen()? {
+        Some(node) => Ok(node.getprop(cstr!("avf,strict-boot"))?.is_some()),
+        None => Ok(false),
+    }
+}
diff --git a/rialto/src/main.rs b/guest/rialto/src/main.rs
similarity index 93%
rename from rialto/src/main.rs
rename to guest/rialto/src/main.rs
index 701a287..a98ec25 100644
--- a/rialto/src/main.rs
+++ b/guest/rialto/src/main.rs
@@ -26,7 +26,7 @@
 
 use crate::communication::VsockStream;
 use crate::error::{Error, Result};
-use crate::fdt::{read_dice_range_from, read_vendor_hashtree_root_digest};
+use crate::fdt::{read_dice_range_from, read_is_strict_boot, read_vendor_hashtree_root_digest};
 use alloc::boxed::Box;
 use bssl_sys::CRYPTO_library_init;
 use ciborium_io::Write;
@@ -47,6 +47,7 @@
 use vmbase::{
     configure_heap,
     fdt::SwiotlbInfo,
+    generate_image_header,
     hyp::{get_mem_sharer, get_mmio_guard},
     layout::{self, crosvm, UART_PAGE_ADDR},
     main,
@@ -58,16 +59,15 @@
     },
 };
 
-fn host_addr() -> VsockAddr {
-    VsockAddr { cid: VMADDR_CID_HOST, port: vm_type().port() }
+fn host_addr(fdt: &libfdt::Fdt) -> Result<VsockAddr> {
+    Ok(VsockAddr { cid: VMADDR_CID_HOST, port: vm_type(fdt)?.port() })
 }
 
-fn vm_type() -> VmType {
-    // Use MMIO support to determine whether the VM is protected.
-    if get_mmio_guard().is_some() {
-        VmType::ProtectedVm
+fn vm_type(fdt: &libfdt::Fdt) -> Result<VmType> {
+    if read_is_strict_boot(fdt)? {
+        Ok(VmType::ProtectedVm)
     } else {
-        VmType::NonProtectedVm
+        Ok(VmType::NonProtectedVm)
     }
 }
 
@@ -143,7 +143,7 @@
     unsafe {
         CRYPTO_library_init();
     }
-    let bcc_handover: Box<dyn DiceArtifacts> = match vm_type() {
+    let bcc_handover: Box<dyn DiceArtifacts> = match vm_type(fdt)? {
         VmType::ProtectedVm => {
             let dice_range = read_dice_range_from(fdt)?;
             info!("DICE range: {dice_range:#x?}");
@@ -178,7 +178,7 @@
     let request_context =
         RequestContext { dice_artifacts: bcc_handover.as_ref(), vendor_hashtree_root_digest };
 
-    let mut vsock_stream = VsockStream::new(socket_device, host_addr())?;
+    let mut vsock_stream = VsockStream::new(socket_device, host_addr(fdt)?)?;
     while let ServiceVmRequest::Process(req) = vsock_stream.read_request()? {
         info!("Received request: {}", req.name());
         let response = process_request(req, &request_context);
@@ -233,5 +233,6 @@
     }
 }
 
+generate_image_header!();
 main!(main);
 configure_heap!(SIZE_128KB * 2);
diff --git a/rialto/tests/test.rs b/guest/rialto/tests/test.rs
similarity index 92%
rename from rialto/tests/test.rs
rename to guest/rialto/tests/test.rs
index cf5630f..a90adea 100644
--- a/rialto/tests/test.rs
+++ b/guest/rialto/tests/test.rs
@@ -34,7 +34,7 @@
 use service_vm_fake_chain::client_vm::{
     fake_client_vm_dice_artifacts, fake_sub_components, SubComponent,
 };
-use service_vm_manager::ServiceVm;
+use service_vm_manager::{ServiceVm, VM_MEMORY_MB};
 use std::fs;
 use std::fs::File;
 use std::panic;
@@ -59,7 +59,7 @@
         // The test is skipped if the feature flag |dice_changes| is not enabled, because when
         // the flag is off, the DICE chain is truncated in the pvmfw, and the service VM cannot
         // verify the chain due to the missing entries in the chain.
-        check_processing_requests(VmType::ProtectedVm)
+        check_processing_requests(VmType::ProtectedVm, None)
     } else {
         warn!("pVMs are not supported on device, skipping test");
         Ok(())
@@ -68,11 +68,17 @@
 
 #[test]
 fn process_requests_in_non_protected_vm() -> Result<()> {
-    check_processing_requests(VmType::NonProtectedVm)
+    check_processing_requests(VmType::NonProtectedVm, None)
 }
 
-fn check_processing_requests(vm_type: VmType) -> Result<()> {
-    let mut vm = start_service_vm(vm_type)?;
+#[test]
+fn process_requests_in_non_protected_vm_with_extra_ram() -> Result<()> {
+    const MEMORY_MB: i32 = 300;
+    check_processing_requests(VmType::NonProtectedVm, Some(MEMORY_MB))
+}
+
+fn check_processing_requests(vm_type: VmType, vm_memory_mb: Option<i32>) -> Result<()> {
+    let mut vm = start_service_vm(vm_type, vm_memory_mb)?;
 
     check_processing_reverse_request(&mut vm)?;
     let key_pair = check_processing_generating_key_pair_request(&mut vm)?;
@@ -285,7 +291,7 @@
     Ok(())
 }
 
-fn start_service_vm(vm_type: VmType) -> Result<ServiceVm> {
+fn start_service_vm(vm_type: VmType, vm_memory_mb: Option<i32>) -> Result<ServiceVm> {
     android_logger::init_once(
         android_logger::Config::default()
             .with_tag("rialto")
@@ -297,19 +303,20 @@
     }));
     // We need to start the thread pool for Binder to work properly, especially link_to_death.
     ProcessState::start_thread_pool();
-    ServiceVm::start_vm(vm_instance(vm_type)?, vm_type)
+    ServiceVm::start_vm(vm_instance(vm_type, vm_memory_mb)?, vm_type)
 }
 
-fn vm_instance(vm_type: VmType) -> Result<VmInstance> {
+fn vm_instance(vm_type: VmType, vm_memory_mb: Option<i32>) -> Result<VmInstance> {
     match vm_type {
         VmType::ProtectedVm => {
+            assert!(vm_memory_mb.is_none());
             service_vm_manager::protected_vm_instance(PathBuf::from(INSTANCE_IMG_PATH))
         }
-        VmType::NonProtectedVm => nonprotected_vm_instance(),
+        VmType::NonProtectedVm => nonprotected_vm_instance(vm_memory_mb.unwrap_or(VM_MEMORY_MB)),
     }
 }
 
-fn nonprotected_vm_instance() -> Result<VmInstance> {
+fn nonprotected_vm_instance(memory_mib: i32) -> Result<VmInstance> {
     let rialto = File::open(UNSIGNED_RIALTO_PATH).context("Failed to open Rialto kernel binary")?;
     // Do not use `#allocateInstanceId` to generate the instance ID because the method
     // also adds an instance ID to the database it manages.
@@ -317,10 +324,10 @@
     let mut instance_id = [0u8; 64];
     rand_bytes(&mut instance_id).unwrap();
     let config = VirtualMachineConfig::RawConfig(VirtualMachineRawConfig {
-        name: String::from("Non protected rialto"),
-        bootloader: Some(ParcelFileDescriptor::new(rialto)),
+        name: format!("Non protected rialto ({memory_mib}MiB)"),
+        kernel: Some(ParcelFileDescriptor::new(rialto)),
         protectedVm: false,
-        memoryMib: 300,
+        memoryMib: memory_mib,
         platformVersion: "~1.0".to_string(),
         instanceId: instance_id,
         ..Default::default()
diff --git a/guest/vmbase_example/Android.bp b/guest/vmbase_example/Android.bp
new file mode 100644
index 0000000..ff7bd83
--- /dev/null
+++ b/guest/vmbase_example/Android.bp
@@ -0,0 +1,114 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+rust_ffi_static {
+    name: "libvmbase_example",
+    defaults: ["vmbase_ffi_defaults"],
+    crate_name: "vmbase_example",
+    srcs: ["src/main.rs"],
+    rustlibs: [
+        "libaarch64_paging",
+        "libcstr",
+        "libdiced_open_dice_nostd",
+        "libfdtpci",
+        "liblibfdt",
+        "liblog_rust_nostd",
+        "libvirtio_drivers",
+        "libvmbase",
+    ],
+}
+
+genrule {
+    name: "vmbase_image.ld.S.mm",
+    // Soong won't let us use cc_object to preprocess *.ld.S files because it
+    // can't resist feeding any and all *.S files to the assembler, which fails
+    // because linker scripts typically aren't valid assembly. Also, cc_object
+    // rejects inputs that don't end in one of .{s,S,c,cpp,cc,cxx,mm}. So keep
+    // the proper extension (.ld.S) for the file in VCS and use this convoluted
+    // extra step to please Soong by pretending that our linker script is in
+    // fact some Object C++ code, which fortunately it doesn't try to compile.
+    srcs: ["image.ld.S"],
+    out: ["image.ld.S.mm"],
+    cmd: "cp $(in) $(out)",
+    visibility: ["//visibility:private"],
+}
+
+cc_defaults {
+    name: "vmbase_example_ld_defaults",
+    defaults: ["vmbase_cc_defaults"],
+    cflags: [
+        "-E",
+        "-P",
+        "-xassembler-with-cpp", // allow C preprocessor directives
+    ],
+    srcs: [":vmbase_image.ld.S.mm"],
+    visibility: ["//visibility:private"],
+}
+
+cc_object {
+    name: "vmbase_example_bios.ld",
+    defaults: ["vmbase_example_ld_defaults"],
+    cflags: ["-DVMBASE_EXAMPLE_IS_BIOS"],
+}
+
+cc_object {
+    name: "vmbase_example_kernel.ld",
+    defaults: ["vmbase_example_ld_defaults"],
+    cflags: ["-DVMBASE_EXAMPLE_IS_KERNEL"],
+}
+
+cc_defaults {
+    name: "vmbase_example_elf_defaults",
+    defaults: ["vmbase_elf_defaults"],
+    srcs: [
+        "idmap.S",
+    ],
+    static_libs: [
+        "libvmbase_example",
+    ],
+}
+
+cc_binary {
+    name: "vmbase_example_bios",
+    defaults: ["vmbase_example_elf_defaults"],
+    asflags: ["-DVMBASE_EXAMPLE_IS_BIOS"],
+    linker_scripts: [
+        ":vmbase_example_bios.ld",
+        ":vmbase_sections",
+    ],
+}
+
+cc_binary {
+    name: "vmbase_example_kernel",
+    defaults: ["vmbase_example_elf_defaults"],
+    asflags: ["-DVMBASE_EXAMPLE_IS_KERNEL"],
+    linker_scripts: [
+        ":vmbase_example_kernel.ld",
+        ":vmbase_sections",
+    ],
+}
+
+raw_binary {
+    name: "vmbase_example_bios_bin",
+    stem: "vmbase_example_bios.bin",
+    src: ":vmbase_example_bios",
+    enabled: false,
+    target: {
+        android_arm64: {
+            enabled: true,
+        },
+    },
+}
+
+raw_binary {
+    name: "vmbase_example_kernel_bin",
+    stem: "vmbase_example_kernel.bin",
+    src: ":vmbase_example_kernel",
+    enabled: false,
+    target: {
+        android_arm64: {
+            enabled: true,
+        },
+    },
+}
diff --git a/vmbase/example/idmap.S b/guest/vmbase_example/idmap.S
similarity index 82%
rename from vmbase/example/idmap.S
rename to guest/vmbase_example/idmap.S
index 71a6ade..881850c 100644
--- a/vmbase/example/idmap.S
+++ b/guest/vmbase_example/idmap.S
@@ -43,8 +43,16 @@
 	.quad		.L_TT_TYPE_TABLE + 0f		// up to 1 GiB of DRAM
 	.fill		509, 8, 0x0			// 509 GiB of remaining VA space
 
-	/* level 2 */
-0:	.quad		.L_BLOCK_MEM | 0x80000000	// DT provided by VMM
+0:	/* level 2 */
+#if defined(VMBASE_EXAMPLE_IS_BIOS)
+	.quad		0				// 2 MiB not mapped (DT)
 	.quad		.L_BLOCK_MEM_XIP | 0x80200000	// 2 MiB of DRAM containing image
 	.quad		.L_BLOCK_MEM | 0x80400000	// 2 MiB of writable DRAM
 	.fill		509, 8, 0x0
+#elif defined(VMBASE_EXAMPLE_IS_KERNEL)
+	.quad		.L_BLOCK_MEM_XIP | 0x80000000	// 2 MiB of DRAM containing image
+	.quad		.L_BLOCK_MEM | 0x80200000	// 2 MiB of writable DRAM
+	.fill		510, 8, 0x0
+#else
+#error "Unexpected vmbase_example mode: failed to generate idmap"
+#endif
diff --git a/rialto/image.ld b/guest/vmbase_example/image.ld.S
similarity index 73%
copy from rialto/image.ld
copy to guest/vmbase_example/image.ld.S
index 368acbb..a5cd965 100644
--- a/rialto/image.ld
+++ b/guest/vmbase_example/image.ld.S
@@ -16,7 +16,13 @@
 
 MEMORY
 {
-	dtb_region	: ORIGIN = 0x80000000, LENGTH = 2M
+#if defined(VMBASE_EXAMPLE_IS_BIOS)
 	image		: ORIGIN = 0x80200000, LENGTH = 2M
 	writable_data	: ORIGIN = 0x80400000, LENGTH = 2M
+#elif defined(VMBASE_EXAMPLE_IS_KERNEL)
+	image		: ORIGIN = 0x80000000, LENGTH = 2M
+	writable_data	: ORIGIN = 0x80200000, LENGTH = 2M
+#else
+#error "Unexpected vmbase_example mode: failed to generate image layout"
+#endif
 }
diff --git a/vmbase/example/src/exceptions.rs b/guest/vmbase_example/src/exceptions.rs
similarity index 100%
rename from vmbase/example/src/exceptions.rs
rename to guest/vmbase_example/src/exceptions.rs
diff --git a/vmbase/example/src/layout.rs b/guest/vmbase_example/src/layout.rs
similarity index 90%
rename from vmbase/example/src/layout.rs
rename to guest/vmbase_example/src/layout.rs
index fc578bc..50ecb7e 100644
--- a/vmbase/example/src/layout.rs
+++ b/guest/vmbase_example/src/layout.rs
@@ -17,20 +17,17 @@
 use aarch64_paging::paging::{MemoryRegion, VirtualAddress};
 use core::ops::Range;
 use log::info;
-use vmbase::layout;
+use vmbase::{layout, memory::PAGE_SIZE};
 
 /// The first 1 GiB of memory are used for MMIO.
 pub const DEVICE_REGION: MemoryRegion = MemoryRegion::new(0, 0x40000000);
 
 /// Writable data region for the stack.
 pub fn boot_stack_range() -> Range<VirtualAddress> {
-    const PAGE_SIZE: usize = 4 << 10;
     layout::stack_range(40 * PAGE_SIZE)
 }
 
 pub fn print_addresses() {
-    let dtb = layout::dtb_range();
-    info!("dtb:        {}..{} ({} bytes)", dtb.start, dtb.end, dtb.end - dtb.start);
     let text = layout::text_range();
     info!("text:       {}..{} ({} bytes)", text.start, text.end, text.end - text.start);
     let rodata = layout::rodata_range();
diff --git a/vmbase/example/src/main.rs b/guest/vmbase_example/src/main.rs
similarity index 87%
rename from vmbase/example/src/main.rs
rename to guest/vmbase_example/src/main.rs
index da82b17..7a3f427 100644
--- a/vmbase/example/src/main.rs
+++ b/guest/vmbase_example/src/main.rs
@@ -25,38 +25,37 @@
 
 use crate::layout::{boot_stack_range, print_addresses, DEVICE_REGION};
 use crate::pci::{check_pci, get_bar_region};
-use aarch64_paging::paging::MemoryRegion;
+use aarch64_paging::paging::VirtualAddress;
 use aarch64_paging::MapError;
 use alloc::{vec, vec::Vec};
+use core::mem;
 use core::ptr::addr_of_mut;
 use cstr::cstr;
 use fdtpci::PciInfo;
 use libfdt::Fdt;
 use log::{debug, error, info, trace, warn, LevelFilter};
 use vmbase::{
-    bionic, configure_heap,
-    layout::{dtb_range, rodata_range, scratch_range, text_range},
+    bionic, configure_heap, generate_image_header,
+    layout::{crosvm::FDT_MAX_SIZE, rodata_range, scratch_range, text_range},
     linker, logger, main,
     memory::{PageTable, SIZE_64KB},
+    util::RangeExt as _,
 };
 
 static INITIALISED_DATA: [u32; 4] = [1, 2, 3, 4];
 static mut ZEROED_DATA: [u32; 10] = [0; 10];
 static mut MUTABLE_DATA: [u32; 4] = [1, 2, 3, 4];
 
+generate_image_header!();
 main!(main);
 configure_heap!(SIZE_64KB);
 
-fn init_page_table(pci_bar_range: &MemoryRegion) -> Result<(), MapError> {
-    let mut page_table = PageTable::default();
-
+fn init_page_table(page_table: &mut PageTable) -> Result<(), MapError> {
     page_table.map_device(&DEVICE_REGION)?;
     page_table.map_code(&text_range().into())?;
     page_table.map_rodata(&rodata_range().into())?;
     page_table.map_data(&scratch_range().into())?;
     page_table.map_data(&boot_stack_range().into())?;
-    page_table.map_rodata(&dtb_range().into())?;
-    page_table.map_device(pci_bar_range)?;
 
     info!("Activating IdMap...");
     // SAFETY: page_table duplicates the static mappings for everything that the Rust code is
@@ -76,15 +75,18 @@
     info!("Hello world");
     info!("x0={:#018x}, x1={:#018x}, x2={:#018x}, x3={:#018x}", arg0, arg1, arg2, arg3);
     print_addresses();
-    assert_eq!(arg0, dtb_range().start.0 as u64);
     check_data();
     check_stack_guard();
 
+    let mut page_table = PageTable::default();
+    init_page_table(&mut page_table).unwrap();
+
     info!("Checking FDT...");
-    let fdt = dtb_range();
-    let fdt_size = fdt.end.0 - fdt.start.0;
+    let fdt_addr = usize::try_from(arg0).unwrap();
     // SAFETY: The DTB range is valid, writable memory, and we don't construct any aliases to it.
-    let fdt = unsafe { core::slice::from_raw_parts_mut(fdt.start.0 as *mut u8, fdt_size) };
+    let fdt = unsafe { core::slice::from_raw_parts_mut(fdt_addr as *mut u8, FDT_MAX_SIZE) };
+    let fdt_region = (VirtualAddress(fdt_addr)..VirtualAddress(fdt_addr + fdt.len())).into();
+    page_table.map_data(&fdt_region).unwrap();
     let fdt = Fdt::from_mut_slice(fdt).unwrap();
     info!("FDT passed verification.");
     check_fdt(fdt);
@@ -96,7 +98,13 @@
 
     check_alloc();
 
-    init_page_table(&get_bar_region(&pci_info)).unwrap();
+    let bar_region = get_bar_region(&pci_info);
+    if bar_region.is_within(&DEVICE_REGION) {
+        // Avoid a MapError::BreakBeforeMakeViolation.
+        info!("BAR region is within already mapped device region: skipping page table ops.");
+    } else {
+        page_table.map_device(&bar_region).unwrap();
+    }
 
     check_data();
     check_dice();
@@ -106,6 +114,10 @@
     check_pci(&mut pci_root);
 
     emit_suppressed_log();
+
+    info!("De-activating IdMap...");
+    mem::drop(page_table); // Release PageTable and switch back to idmap.S
+    info!("De-activated.");
 }
 
 fn check_stack_guard() {
diff --git a/vmbase/example/src/pci.rs b/guest/vmbase_example/src/pci.rs
similarity index 100%
rename from vmbase/example/src/pci.rs
rename to guest/vmbase_example/src/pci.rs
diff --git a/zipfuse/.cargo/config.toml b/guest/zipfuse/.cargo/config.toml
similarity index 100%
rename from zipfuse/.cargo/config.toml
rename to guest/zipfuse/.cargo/config.toml
diff --git a/zipfuse/Android.bp b/guest/zipfuse/Android.bp
similarity index 100%
rename from zipfuse/Android.bp
rename to guest/zipfuse/Android.bp
diff --git a/zipfuse/AndroidTest.xml b/guest/zipfuse/AndroidTest.xml
similarity index 100%
rename from zipfuse/AndroidTest.xml
rename to guest/zipfuse/AndroidTest.xml
diff --git a/zipfuse/Cargo.toml b/guest/zipfuse/Cargo.toml
similarity index 100%
rename from zipfuse/Cargo.toml
rename to guest/zipfuse/Cargo.toml
diff --git a/zipfuse/TEST_MAPPING b/guest/zipfuse/TEST_MAPPING
similarity index 100%
rename from zipfuse/TEST_MAPPING
rename to guest/zipfuse/TEST_MAPPING
diff --git a/zipfuse/src/inode.rs b/guest/zipfuse/src/inode.rs
similarity index 100%
rename from zipfuse/src/inode.rs
rename to guest/zipfuse/src/inode.rs
diff --git a/zipfuse/src/main.rs b/guest/zipfuse/src/main.rs
similarity index 100%
rename from zipfuse/src/main.rs
rename to guest/zipfuse/src/main.rs
diff --git a/zipfuse/testdata/README b/guest/zipfuse/testdata/README
similarity index 100%
rename from zipfuse/testdata/README
rename to guest/zipfuse/testdata/README
diff --git a/zipfuse/testdata/dir/file1 b/guest/zipfuse/testdata/dir/file1
similarity index 100%
rename from zipfuse/testdata/dir/file1
rename to guest/zipfuse/testdata/dir/file1
diff --git a/zipfuse/testdata/dir/file2 b/guest/zipfuse/testdata/dir/file2
similarity index 100%
rename from zipfuse/testdata/dir/file2
rename to guest/zipfuse/testdata/dir/file2
diff --git a/zipfuse/testdata/test.zip b/guest/zipfuse/testdata/test.zip
similarity index 100%
rename from zipfuse/testdata/test.zip
rename to guest/zipfuse/testdata/test.zip
Binary files differ
diff --git a/libs/android_display_backend/Android.bp b/libs/android_display_backend/Android.bp
index 32587dd..f682627 100644
--- a/libs/android_display_backend/Android.bp
+++ b/libs/android_display_backend/Android.bp
@@ -46,6 +46,9 @@
         "android.system.virtualizationcommon-ndk",
         "android.system.virtualizationservice-ndk",
     ],
+    static_libs: [
+        "libbase",
+    ],
     shared_libs: [
         "libbinder_ndk",
         "libnativewindow",
diff --git a/libs/android_display_backend/aidl/android/crosvm/ICrosvmAndroidDisplayService.aidl b/libs/android_display_backend/aidl/android/crosvm/ICrosvmAndroidDisplayService.aidl
index e42cdd1..77e3a8c 100644
--- a/libs/android_display_backend/aidl/android/crosvm/ICrosvmAndroidDisplayService.aidl
+++ b/libs/android_display_backend/aidl/android/crosvm/ICrosvmAndroidDisplayService.aidl
@@ -27,4 +27,6 @@
     void setSurface(inout Surface surface, boolean forCursor);
     void setCursorStream(in ParcelFileDescriptor stream);
     void removeSurface(boolean forCursor);
+    void saveFrameForSurface(boolean forCursor);
+    void drawSavedFrameForSurface(boolean forCursor);
 }
diff --git a/libs/android_display_backend/crosvm_android_display_client.cpp b/libs/android_display_backend/crosvm_android_display_client.cpp
index 6e4a793..3802a69 100644
--- a/libs/android_display_backend/crosvm_android_display_client.cpp
+++ b/libs/android_display_backend/crosvm_android_display_client.cpp
@@ -16,6 +16,7 @@
 
 #include <aidl/android/crosvm/BnCrosvmAndroidDisplayService.h>
 #include <aidl/android/system/virtualizationservice_internal/IVirtualizationServiceInternal.h>
+#include <android-base/result.h>
 #include <android/binder_manager.h>
 #include <android/binder_process.h>
 #include <system/graphics.h> // for HAL_PIXEL_FORMAT_*
@@ -28,16 +29,16 @@
 using aidl::android::system::virtualizationservice_internal::IVirtualizationServiceInternal;
 using aidl::android::view::Surface;
 
+using android::base::Error;
+using android::base::Result;
+
 namespace {
 
 class SinkANativeWindow_Buffer {
 public:
-    SinkANativeWindow_Buffer() = default;
-    virtual ~SinkANativeWindow_Buffer() = default;
-
-    bool configure(uint32_t width, uint32_t height, int format) {
+    Result<void> configure(uint32_t width, uint32_t height, int format) {
         if (format != HAL_PIXEL_FORMAT_BGRA_8888) {
-            return false;
+            return Error() << "Pixel format " << format << " is not BGRA_8888.";
         }
 
         mBufferBits.resize(width * height * 4);
@@ -48,7 +49,7 @@
                 .format = format,
                 .bits = mBufferBits.data(),
         };
-        return true;
+        return {};
     }
 
     operator ANativeWindow_Buffer&() { return mBuffer; }
@@ -58,16 +59,28 @@
     std::vector<uint8_t> mBufferBits;
 };
 
-// Wrapper which contains the latest available Surface/ANativeWindow
-// from the DisplayService, if available. A Surface/ANativeWindow may
-// not always be available if, for example, the VmLauncherApp on the
-// other end of the DisplayService is not in the foreground / is paused.
+static Result<void> copyBuffer(ANativeWindow_Buffer& from, ANativeWindow_Buffer& to) {
+    if (from.width != to.width || from.height != to.height) {
+        return Error() << "dimension mismatch. from=(" << from.width << ", " << from.height << ") "
+                       << "to=(" << to.width << ", " << to.height << ")";
+    }
+    uint32_t* dst = reinterpret_cast<uint32_t*>(to.bits);
+    uint32_t* src = reinterpret_cast<uint32_t*>(from.bits);
+    size_t bytes_on_line = to.width * 4; // 4 bytes per pixel
+    for (int32_t h = 0; h < to.height; h++) {
+        memcpy(dst + (h * to.stride), src + (h * from.stride), bytes_on_line);
+    }
+    return {};
+}
+
+// Wrapper which contains the latest available Surface/ANativeWindow from the DisplayService, if
+// available. A Surface/ANativeWindow may not always be available if, for example, the VmLauncherApp
+// on the other end of the DisplayService is not in the foreground / is paused.
 class AndroidDisplaySurface {
 public:
-    AndroidDisplaySurface() = default;
-    virtual ~AndroidDisplaySurface() = default;
+    AndroidDisplaySurface(const std::string& name) : mName(name) {}
 
-    void setSurface(Surface* surface) {
+    void setNativeSurface(Surface* surface) {
         {
             std::lock_guard lk(mSurfaceMutex);
             mNativeSurface = std::make_unique<Surface>(surface->release());
@@ -90,7 +103,7 @@
         return mNativeSurface.get();
     }
 
-    void configure(uint32_t width, uint32_t height) {
+    Result<void> configure(uint32_t width, uint32_t height) {
         std::unique_lock lk(mSurfaceMutex);
 
         mRequestedSurfaceDimensions = Rect{
@@ -98,7 +111,13 @@
                 .height = height,
         };
 
-        mSinkBuffer.configure(width, height, kFormat);
+        if (auto ret = mSinkBuffer.configure(width, height, kFormat); !ret.ok()) {
+            return Error() << "Failed to configure sink buffer: " << ret.error();
+        }
+        if (auto ret = mSavedFrameBuffer.configure(width, height, kFormat); !ret.ok()) {
+            return Error() << "Failed to configure saved frame buffer: " << ret.error();
+        }
+        return {};
     }
 
     void waitForNativeSurface() {
@@ -106,7 +125,7 @@
         mNativeSurfaceReady.wait(lk, [this] { return mNativeSurface != nullptr; });
     }
 
-    int lock(ANativeWindow_Buffer* out_buffer) {
+    Result<void> lock(ANativeWindow_Buffer* out_buffer) {
         std::unique_lock lk(mSurfaceMutex);
 
         Surface* surface = mNativeSurface.get();
@@ -114,62 +133,137 @@
             // Surface not currently available but not necessarily an error
             // if, for example, the VmLauncherApp is not in the foreground.
             *out_buffer = mSinkBuffer;
-            return 0;
+            return {};
         }
 
         ANativeWindow* anw = surface->get();
         if (anw == nullptr) {
-            return -1;
+            return Error() << "Failed to get ANativeWindow";
         }
 
         if (mNativeSurfaceNeedsConfiguring) {
             if (!mRequestedSurfaceDimensions) {
-                return -1;
+                return Error() << "Surface dimension is not configured yet!";
             }
             const auto& dims = *mRequestedSurfaceDimensions;
 
             // Ensure locked buffers have our desired format.
             if (ANativeWindow_setBuffersGeometry(anw, dims.width, dims.height, kFormat) != 0) {
-                return -1;
+                return Error() << "Failed to set buffer geometry.";
             }
 
             mNativeSurfaceNeedsConfiguring = false;
         }
 
-        return ANativeWindow_lock(anw, out_buffer, nullptr);
+        if (ANativeWindow_lock(anw, out_buffer, nullptr) != 0) {
+            return Error() << "Failed to lock window";
+        }
+        mLastBuffer = *out_buffer;
+        return {};
     }
 
-    int unlockAndPost() {
+    Result<void> unlockAndPost() {
         std::unique_lock lk(mSurfaceMutex);
 
         Surface* surface = mNativeSurface.get();
         if (surface == nullptr) {
             // Surface not currently available but not necessarily an error
             // if, for example, the VmLauncherApp is not in the foreground.
-            return 0;
+            return {};
         }
 
         ANativeWindow* anw = surface->get();
         if (anw == nullptr) {
-            return -1;
+            return Error() << "Failed to get ANativeWindow";
         }
 
-        return ANativeWindow_unlockAndPost(anw);
+        if (ANativeWindow_unlockAndPost(anw) != 0) {
+            return Error() << "Failed to unlock and post window";
+        }
+        return {};
     }
 
+    // Saves the last frame drawn
+    Result<void> saveFrame() {
+        std::unique_lock lk(mSurfaceMutex);
+        if (auto ret = copyBuffer(mLastBuffer, mSavedFrameBuffer); !ret.ok()) {
+            return Error() << "Failed to copy frame: " << ret.error();
+        }
+        return {};
+    }
+
+    // Draws the saved frame
+    Result<void> drawSavedFrame() {
+        std::unique_lock lk(mSurfaceMutex);
+        Surface* surface = mNativeSurface.get();
+        if (surface == nullptr) {
+            return Error() << "Surface not ready";
+        }
+
+        ANativeWindow* anw = surface->get();
+        if (anw == nullptr) {
+            return Error() << "Failed to get ANativeWindow";
+        }
+
+        // TODO: dedup this and the one in lock(...)
+        if (mNativeSurfaceNeedsConfiguring) {
+            if (!mRequestedSurfaceDimensions) {
+                return Error() << "Surface dimension is not configured yet!";
+            }
+            const auto& dims = *mRequestedSurfaceDimensions;
+
+            // Ensure locked buffers have our desired format.
+            if (ANativeWindow_setBuffersGeometry(anw, dims.width, dims.height, kFormat) != 0) {
+                return Error() << "Failed to set buffer geometry.";
+            }
+
+            mNativeSurfaceNeedsConfiguring = false;
+        }
+
+        ANativeWindow_Buffer buf;
+        if (ANativeWindow_lock(anw, &buf, nullptr) != 0) {
+            return Error() << "Failed to lock window";
+        }
+
+        if (auto ret = copyBuffer(mSavedFrameBuffer, buf); !ret.ok()) {
+            return Error() << "Failed to copy frame: " << ret.error();
+        }
+
+        if (ANativeWindow_unlockAndPost(anw) != 0) {
+            return Error() << "Failed to unlock and post window";
+        }
+        return {};
+    }
+
+    const std::string& name() const { return mName; }
+
 private:
     // Note: crosvm always uses BGRA8888 or BGRX8888. See devices/src/virtio/gpu/mod.rs in
     // crosvm where the SetScanoutBlob command is handled. Let's use BGRA not BGRX with a hope
     // that we will need alpha blending for the cursor surface.
     static constexpr const int kFormat = HAL_PIXEL_FORMAT_BGRA_8888;
 
+    std::string mName;
+
     std::mutex mSurfaceMutex;
     std::unique_ptr<Surface> mNativeSurface;
     std::condition_variable mNativeSurfaceReady;
     bool mNativeSurfaceNeedsConfiguring = true;
 
+    // Buffer which crosvm uses when in background. This is just to not fail crosvm even when
+    // Android-side Surface doesn't exist. The content drawn here is never displayed on the physical
+    // screen.
     SinkANativeWindow_Buffer mSinkBuffer;
 
+    // Buffer which is currently allocated for crosvm to draw onto. This holds the last frame. This
+    // is what gets displayed on the physical screen.
+    ANativeWindow_Buffer mLastBuffer;
+
+    // Copy of mLastBuffer made by the call saveFrameForSurface. This holds the last good (i.e.
+    // non-blank) frame before the VM goes background. When the VM is brought up to foreground,
+    // this is drawn to the physical screen until the VM starts to emit actual frames.
+    SinkANativeWindow_Buffer mSavedFrameBuffer;
+
     struct Rect {
         uint32_t width = 0;
         uint32_t height = 0;
@@ -183,35 +277,50 @@
     virtual ~DisplayService() = default;
 
     ndk::ScopedAStatus setSurface(Surface* surface, bool forCursor) override {
-        if (forCursor) {
-            mCursor.setSurface(surface);
-        } else {
-            mScanout.setSurface(surface);
-        }
+        getSurface(forCursor).setNativeSurface(surface);
         return ::ndk::ScopedAStatus::ok();
     }
 
     ndk::ScopedAStatus removeSurface(bool forCursor) override {
-        if (forCursor) {
-            mCursor.removeSurface();
-        } else {
-            mScanout.removeSurface();
-        }
+        getSurface(forCursor).removeSurface();
         return ::ndk::ScopedAStatus::ok();
     }
 
-    AndroidDisplaySurface* getCursorSurface() { return &mCursor; }
-    AndroidDisplaySurface* getScanoutSurface() { return &mScanout; }
-
     ndk::ScopedFileDescriptor& getCursorStream() { return mCursorStream; }
     ndk::ScopedAStatus setCursorStream(const ndk::ScopedFileDescriptor& in_stream) {
         mCursorStream = ndk::ScopedFileDescriptor(dup(in_stream.get()));
         return ::ndk::ScopedAStatus::ok();
     }
 
+    ndk::ScopedAStatus saveFrameForSurface(bool forCursor) override {
+        if (auto ret = getSurface(forCursor).saveFrame(); !ret.ok()) {
+            std::string msg = std::format("Failed to save frame: {}", ret.error().message());
+            return ::ndk::ScopedAStatus(
+                    AStatus_fromServiceSpecificErrorWithMessage(-1, msg.c_str()));
+        }
+        return ::ndk::ScopedAStatus::ok();
+    }
+
+    ndk::ScopedAStatus drawSavedFrameForSurface(bool forCursor) override {
+        if (auto ret = getSurface(forCursor).drawSavedFrame(); !ret.ok()) {
+            std::string msg = std::format("Failed to draw saved frame: {}", ret.error().message());
+            return ::ndk::ScopedAStatus(
+                    AStatus_fromServiceSpecificErrorWithMessage(-1, msg.c_str()));
+        }
+        return ::ndk::ScopedAStatus::ok();
+    }
+
+    AndroidDisplaySurface& getSurface(bool forCursor) {
+        if (forCursor) {
+            return mCursor;
+        } else {
+            return mScanout;
+        }
+    }
+
 private:
-    AndroidDisplaySurface mScanout;
-    AndroidDisplaySurface mCursor;
+    AndroidDisplaySurface mScanout{"scanout"};
+    AndroidDisplaySurface mCursor{"cursor"};
     ndk::ScopedFileDescriptor mCursorStream;
 };
 
@@ -287,21 +396,18 @@
         return nullptr;
     }
 
-    AndroidDisplaySurface* displaySurface = forCursor ? ctx->disp_service->getCursorSurface()
-                                                      : ctx->disp_service->getScanoutSurface();
-    if (displaySurface == nullptr) {
-        ctx->errorf("AndroidDisplaySurface was not created");
-        return nullptr;
+    AndroidDisplaySurface& surface = ctx->disp_service->getSurface(forCursor);
+    if (auto ret = surface.configure(width, height); !ret.ok()) {
+        ctx->errorf("Failed to configure surface %s: %s", surface.name().c_str(),
+                    ret.error().message().c_str());
     }
 
-    displaySurface->configure(width, height);
-
-    displaySurface->waitForNativeSurface(); // this can block
+    surface.waitForNativeSurface(); // this can block
 
     // TODO(b/332785161): if we know that surface can get destroyed dynamically while VM is running,
     // consider calling ANativeWindow_acquire here and _release in destroy_android_surface, so that
     // crosvm doesn't hold a dangling pointer.
-    return displaySurface;
+    return &surface;
 }
 
 extern "C" void destroy_android_surface(struct AndroidDisplayContext*, ANativeWindow*) {
@@ -321,8 +427,10 @@
         return false;
     }
 
-    if (surface->lock(out_buffer) != 0) {
-        ctx->errorf("Failed to lock buffer");
+    auto ret = surface->lock(out_buffer);
+    if (!ret.ok()) {
+        ctx->errorf("Failed to lock surface %s: %s", surface->name().c_str(),
+                    ret.error().message().c_str());
         return false;
     }
 
@@ -351,8 +459,10 @@
         return;
     }
 
-    if (surface->unlockAndPost() != 0) {
-        ctx->errorf("Failed to unlock and post AndroidDisplaySurface.");
-        return;
+    auto ret = surface->unlockAndPost();
+    if (!ret.ok()) {
+        ctx->errorf("Failed to unlock and post for surface %s: %s", surface->name().c_str(),
+                    ret.error().message().c_str());
     }
+    return;
 }
diff --git a/authfs/aidl/Android.bp b/libs/authfs_aidl_interface/Android.bp
similarity index 100%
rename from authfs/aidl/Android.bp
rename to libs/authfs_aidl_interface/Android.bp
diff --git a/authfs/TEST_MAPPING b/libs/authfs_aidl_interface/TEST_MAPPING
similarity index 100%
copy from authfs/TEST_MAPPING
copy to libs/authfs_aidl_interface/TEST_MAPPING
diff --git a/authfs/aidl/com/android/virt/fs/AuthFsConfig.aidl b/libs/authfs_aidl_interface/com/android/virt/fs/AuthFsConfig.aidl
similarity index 100%
rename from authfs/aidl/com/android/virt/fs/AuthFsConfig.aidl
rename to libs/authfs_aidl_interface/com/android/virt/fs/AuthFsConfig.aidl
diff --git a/authfs/aidl/com/android/virt/fs/IAuthFs.aidl b/libs/authfs_aidl_interface/com/android/virt/fs/IAuthFs.aidl
similarity index 100%
rename from authfs/aidl/com/android/virt/fs/IAuthFs.aidl
rename to libs/authfs_aidl_interface/com/android/virt/fs/IAuthFs.aidl
diff --git a/authfs/aidl/com/android/virt/fs/IAuthFsService.aidl b/libs/authfs_aidl_interface/com/android/virt/fs/IAuthFsService.aidl
similarity index 100%
rename from authfs/aidl/com/android/virt/fs/IAuthFsService.aidl
rename to libs/authfs_aidl_interface/com/android/virt/fs/IAuthFsService.aidl
diff --git a/authfs/aidl/com/android/virt/fs/IVirtFdService.aidl b/libs/authfs_aidl_interface/com/android/virt/fs/IVirtFdService.aidl
similarity index 100%
rename from authfs/aidl/com/android/virt/fs/IVirtFdService.aidl
rename to libs/authfs_aidl_interface/com/android/virt/fs/IVirtFdService.aidl
diff --git a/compos/aidl/Android.bp b/libs/compos_aidl_interface/Android.bp
similarity index 100%
rename from compos/aidl/Android.bp
rename to libs/compos_aidl_interface/Android.bp
diff --git a/compos/aidl/com/android/compos/ICompOsService.aidl b/libs/compos_aidl_interface/com/android/compos/ICompOsService.aidl
similarity index 100%
rename from compos/aidl/com/android/compos/ICompOsService.aidl
rename to libs/compos_aidl_interface/com/android/compos/ICompOsService.aidl
diff --git a/libs/dice/open_dice/Android.bp b/libs/dice/open_dice/Android.bp
index 62504d1..efe350f 100644
--- a/libs/dice/open_dice/Android.bp
+++ b/libs/dice/open_dice/Android.bp
@@ -92,6 +92,9 @@
         "--ctypes-prefix=core::ffi",
         "--raw-line=#![no_std]",
     ],
+    dylib: {
+        enabled: false,
+    },
     no_stdlibs: true,
     prefer_rlib: true,
     stdlibs: [
diff --git a/libs/fdtpci/src/lib.rs b/libs/fdtpci/src/lib.rs
index 602f736..bdd904f 100644
--- a/libs/fdtpci/src/lib.rs
+++ b/libs/fdtpci/src/lib.rs
@@ -206,7 +206,7 @@
 impl PciMemoryFlags {
     /// Returns whether this PCI range is prefetchable
     pub fn prefetchable(self) -> bool {
-        self.0 & 0x80000000 != 0
+        self.0 & 0x40000000 != 0
     }
 
     /// Returns the type of this PCI range
diff --git a/java/framework/Android.bp b/libs/framework-virtualization/Android.bp
similarity index 96%
rename from java/framework/Android.bp
rename to libs/framework-virtualization/Android.bp
index 7c0240d..d3a2b54 100644
--- a/java/framework/Android.bp
+++ b/libs/framework-virtualization/Android.bp
@@ -47,6 +47,7 @@
         "avf_aconfig_flags",
     ],
     lint: {
+        baseline_filename: "lint-baseline.xml",
         warning_checks: [
             "FlaggedApi",
         ],
diff --git a/java/framework/README.md b/libs/framework-virtualization/README.md
similarity index 95%
rename from java/framework/README.md
rename to libs/framework-virtualization/README.md
index 61ba096..0dd7e64 100644
--- a/java/framework/README.md
+++ b/libs/framework-virtualization/README.md
@@ -1,12 +1,12 @@
 # Android Virtualization Framework API
 
 These Java APIs allow an app to configure and run a Virtual Machine running
-[Microdroid](../microdroid/README.md) and execute native code from the app (the
+[Microdroid](../build/microdroid/README.md) and execute native code from the app (the
 payload) within it.
 
 There is more information on AVF [here](../README.md). To see how to package the
 payload code that is to run inside a VM, and the native API available to it, see
-the [VM Payload API](../vm_payload/README.md)
+the [VM Payload API](../libs/libvm_payload/README.md)
 
 The API classes are all in the
 [`android.system.virtualmachine`](src/android/system/virtualmachine) package.
@@ -137,10 +137,10 @@
 - `onPayloadStarted()`: The VM payload is about to be run.
 - `onPayloadReady()`: The VM payload is running and ready to accept
   connections. (This notification is triggered by the payload code, using the
-  [`AVmPayload_notifyPayloadReady()`](../vm_payload/include/vm_payload.h)
+  [`AVmPayload_notifyPayloadReady()`](../libs/libvm_payload/include/vm_payload.h)
   function.)
 - `onPayloadFinished()`: The VM payload has exited normally. The exit code of
-  the VM (the value returned by [`AVmPayload_main()`](../vm_payload/README.md))
+  the VM (the value returned by [`AVmPayload_main()`](../libs/libvm_payload/README.md))
   is supplied as a parameter.
 - `onError()`: The VM failed; something went wrong. An error code and
   human-readable message are provided which may help diagnosing the problem.
@@ -195,7 +195,7 @@
 The payload code is not given direct access to the VM secret, but an API is
 provided to allow deterministically deriving further secrets from it,
 e.g. encryption or signing keys. See
-[`AVmPayload_getVmInstanceSecret()`](../vm_payload/include/vm_payload.h).
+[`AVmPayload_getVmInstanceSecret()`](../libs/libvm_payload/include/vm_payload.h).
 
 Some VM configuration changes are allowed that don’t affect the identity -
 e.g. changing the number of CPUs or the amount of memory allocated to the
@@ -304,7 +304,7 @@
 the `onStopped()` callback).
 
 Then you can arrange for your VM payload code to exit when it has finished its
-task (by returning from [`AVmPayload_main()`](../vm_payload/README.md), or
+task (by returning from [`AVmPayload_main()`](../libs/libvm_payload/README.md), or
 calling `exit()`). Alternatively you could exit when you receive a request to do
 so from the app, e.g. via binder.
 
@@ -330,7 +330,7 @@
 builder.
 
 Inside the VM this storage is mounted at a path that can be retrieved via the
-[`AVmPayload_getEncryptedStoragePath()`](../vm_payload/include/vm_payload.h)
+[`AVmPayload_getEncryptedStoragePath()`](../libs/libvm_payload/include/vm_payload.h)
 function. The VM can create sub-directories and read and write files here. Any
 data written is persisted and should be available next time the VM is run. (An
 automatic sync is done when the payload exits normally.)
@@ -343,7 +343,7 @@
 powerful attacker could delete it, wholly or partially roll it back to an
 earlier version, or modify it, corrupting the data.
 
-For more info see [README](https://cs.android.com/android/platform/superproject/main/+/main:packages/modules/Virtualization/java/framework/README.md)
+For more info see [README](https://cs.android.com/android/platform/superproject/main/+/main:packages/modules/Virtualization/libs/framework-virtualization/README.md)
 
 # Transferring a VM
 
diff --git a/java/framework/api/current.txt b/libs/framework-virtualization/api/current.txt
similarity index 100%
rename from java/framework/api/current.txt
rename to libs/framework-virtualization/api/current.txt
diff --git a/java/framework/api/module-lib-current.txt b/libs/framework-virtualization/api/module-lib-current.txt
similarity index 100%
rename from java/framework/api/module-lib-current.txt
rename to libs/framework-virtualization/api/module-lib-current.txt
diff --git a/java/framework/api/module-lib-removed.txt b/libs/framework-virtualization/api/module-lib-removed.txt
similarity index 100%
rename from java/framework/api/module-lib-removed.txt
rename to libs/framework-virtualization/api/module-lib-removed.txt
diff --git a/java/framework/api/removed.txt b/libs/framework-virtualization/api/removed.txt
similarity index 100%
rename from java/framework/api/removed.txt
rename to libs/framework-virtualization/api/removed.txt
diff --git a/java/framework/api/system-current.txt b/libs/framework-virtualization/api/system-current.txt
similarity index 100%
rename from java/framework/api/system-current.txt
rename to libs/framework-virtualization/api/system-current.txt
diff --git a/java/framework/api/system-removed.txt b/libs/framework-virtualization/api/system-removed.txt
similarity index 100%
rename from java/framework/api/system-removed.txt
rename to libs/framework-virtualization/api/system-removed.txt
diff --git a/java/framework/api/test-current.txt b/libs/framework-virtualization/api/test-current.txt
similarity index 100%
rename from java/framework/api/test-current.txt
rename to libs/framework-virtualization/api/test-current.txt
diff --git a/java/framework/api/test-removed.txt b/libs/framework-virtualization/api/test-removed.txt
similarity index 100%
rename from java/framework/api/test-removed.txt
rename to libs/framework-virtualization/api/test-removed.txt
diff --git a/java/framework/jarjar-rules.txt b/libs/framework-virtualization/jarjar-rules.txt
similarity index 100%
rename from java/framework/jarjar-rules.txt
rename to libs/framework-virtualization/jarjar-rules.txt
diff --git a/libs/framework-virtualization/lint-baseline.xml b/libs/framework-virtualization/lint-baseline.xml
new file mode 100644
index 0000000..a77a80b
--- /dev/null
+++ b/libs/framework-virtualization/lint-baseline.xml
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="6" by="lint 8.4.0-alpha08" type="baseline" client="" dependencies="true" name="" variant="all" version="8.4.0-alpha08">
+
+    <issue
+        id="FlaggedApi"
+        message="Method `getExtraApks()` is a flagged API and should be inside an `if (Flags.avfVTestApis())` check (or annotate the surrounding method `createVirtualMachineConfigForAppFrom` with `@FlaggedApi(Flags.FLAG_AVF_V_TEST_APIS) to transfer requirement to caller`)"
+        errorLine1="        if (!vmConfig.getExtraApks().isEmpty()) {"
+        errorLine2="             ~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="packages/modules/Virtualization/libs/framework-virtualization/src/android/system/virtualmachine/VirtualMachine.java"
+            line="1105"
+            column="14"/>
+    </issue>
+
+    <issue
+        id="FlaggedApi"
+        message="Method `getExtraApks()` is a flagged API and should be inside an `if (Flags.avfVTestApis())` check (or annotate the surrounding method `setupExtraApks` with `@FlaggedApi(Flags.FLAG_AVF_V_TEST_APIS) to transfer requirement to caller`)"
+        errorLine1="        List&lt;String> extraApks = config.getExtraApks();"
+        errorLine2="                                 ~~~~~~~~~~~~~~~~~~~~~">
+        <location
+		file="packages/modules/Virtualization/libs/framework-virtualization/src/android/system/virtualmachine/VirtualMachine.java"
+            line="1730"
+            column="34"/>
+    </issue>
+
+    <issue
+        id="FlaggedApi"
+        message="Method `setVendorDiskImage()` is a flagged API and should be inside an `if (Flags.avfVTestApis())` check (or annotate the surrounding method `fromPersistableBundle` with `@FlaggedApi(Flags.FLAG_AVF_V_TEST_APIS) to transfer requirement to caller`)"
+        errorLine1="            builder.setVendorDiskImage(new File(vendorDiskImagePath));"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+		file="packages/modules/Virtualization/libs/framework-virtualization/src/android/system/virtualmachine/VirtualMachineConfig.java"
+            line="367"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="FlaggedApi"
+        message="Method `setOs()` is a flagged API and should be inside an `if (Flags.avfVTestApis())` check (or annotate the surrounding method `fromPersistableBundle` with `@FlaggedApi(Flags.FLAG_AVF_V_TEST_APIS) to transfer requirement to caller`)"
+        errorLine1="        builder.setOs(b.getString(KEY_OS));"
+        errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+		file="packages/modules/Virtualization/libs/framework-virtualization/src/android/system/virtualmachine/VirtualMachineConfig.java"
+            line="370"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="FlaggedApi"
+        message="Method `addExtraApk()` is a flagged API and should be inside an `if (Flags.avfVTestApis())` check (or annotate the surrounding method `fromPersistableBundle` with `@FlaggedApi(Flags.FLAG_AVF_V_TEST_APIS) to transfer requirement to caller`)"
+        errorLine1="                builder.addExtraApk(extraApk);"
+        errorLine2="                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+		file="packages/modules/Virtualization/libs/framework-virtualization/src/android/system/virtualmachine/VirtualMachineConfig.java"
+            line="375"
+            column="17"/>
+    </issue>
+
+    <issue
+        id="FlaggedApi"
+        message="Field `MICRODROID` is a flagged API and should be inside an `if (Flags.avfVTestApis())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.FLAG_AVF_V_TEST_APIS) to transfer requirement to caller`)"
+        errorLine1="        @OsName private final String DEFAULT_OS = MICRODROID;"
+        errorLine2="                                                  ~~~~~~~~~~">
+        <location
+		file="packages/modules/Virtualization/libs/framework-virtualization/src/android/system/virtualmachine/VirtualMachineConfig.java"
+            line="855"
+            column="51"/>
+    </issue>
+
+</issues>
diff --git a/java/framework/src/android/system/virtualmachine/VirtualMachine.java b/libs/framework-virtualization/src/android/system/virtualmachine/VirtualMachine.java
similarity index 96%
rename from java/framework/src/android/system/virtualmachine/VirtualMachine.java
rename to libs/framework-virtualization/src/android/system/virtualmachine/VirtualMachine.java
index b6de9bb..3b16a8a 100644
--- a/java/framework/src/android/system/virtualmachine/VirtualMachine.java
+++ b/libs/framework-virtualization/src/android/system/virtualmachine/VirtualMachine.java
@@ -132,7 +132,7 @@
  * #connectToVsockServer} or {@link #connectVsock}.
  *
  * <p>The payload code running inside the VM has access to a set of native APIs; see the <a
- * href="https://cs.android.com/android/platform/superproject/+/master:packages/modules/Virtualization/vm_payload/README.md">README
+ * href="https://cs.android.com/android/platform/superproject/main/+/main:packages/modules/Virtualization/libs/libvm_payload/README.md">README
  * file</a> for details.
  *
  * <p>Each VM has a unique secret, computed from the APK that contains the code running in it, the
@@ -193,11 +193,9 @@
      * @hide
      */
     @Retention(RetentionPolicy.SOURCE)
-    @IntDef(prefix = "STATUS_", value = {
-            STATUS_STOPPED,
-            STATUS_RUNNING,
-            STATUS_DELETED
-    })
+    @IntDef(
+            prefix = "STATUS_",
+            value = {STATUS_STOPPED, STATUS_RUNNING, STATUS_DELETED})
     public @interface Status {}
 
     /** The virtual machine has just been created, or {@link #stop} was called on it. */
@@ -245,9 +243,7 @@
     /** Name of this VM within the package. The name should be unique in the package. */
     @NonNull private final String mName;
 
-    /**
-     * Path to the directory containing all the files related to this VM.
-     */
+    /** Path to the directory containing all the files related to this VM. */
     @NonNull private final File mVmRootPath;
 
     /**
@@ -691,8 +687,7 @@
             try {
                 byte[] instanceId = Files.readAllBytes(mInstanceIdPath.toPath());
                 service.claimVmInstance(instanceId);
-            }
-            catch (IOException e) {
+            } catch (IOException e) {
                 throw new VirtualMachineException("failed to read instance_id", e);
             } catch (RemoteException e) {
                 throw e.rethrowAsRuntimeException();
@@ -998,7 +993,8 @@
     /** @hide */
     public boolean sendMouseEvent(MotionEvent event) {
         try {
-            mInputEventQueue.add(Pair.create(InputEventType.MOUSE, event));
+            mInputEventQueue.add(
+                    Pair.create(InputEventType.MOUSE, MotionEvent.obtainNoHistory(event)));
             return true;
         } catch (Exception e) {
             Log.e(TAG, e.toString());
@@ -1097,7 +1093,8 @@
     /** @hide */
     public boolean sendMultiTouchEvent(MotionEvent event) {
         try {
-            mInputEventQueue.add(Pair.create(InputEventType.TOUCH, event));
+            mInputEventQueue.add(
+                    Pair.create(InputEventType.TOUCH, MotionEvent.obtainNoHistory(event)));
             return true;
         } catch (Exception e) {
             Log.e(TAG, e.toString());
@@ -1192,9 +1189,29 @@
     }
 
     /** @hide */
+    public boolean sendTabletModeEvent(boolean tabletMode) {
+        if (mSwitchesSock == null) {
+            Log.d(TAG, "mSwitcheSock == null");
+            return false;
+        }
+
+        // from include/uapi/linux/input-event-codes.h in the kernel.
+        short EV_SYN = 0x00;
+        short EV_SW = 0x05;
+        short SW_TABLET_MODE = 0x01;
+        short SYN_REPORT = 0x00;
+        return writeEventsToSock(
+                mSwitchesSock,
+                Arrays.asList(
+                        new InputEvent(EV_SW, SW_TABLET_MODE, tabletMode ? 1 : 0),
+                        new InputEvent(EV_SYN, SYN_REPORT, 0)));
+    }
+
+    /** @hide */
     public boolean sendTrackpadEvent(MotionEvent event) {
         try {
-            mInputEventQueue.add(Pair.create(InputEventType.TRACKPAD, event));
+            mInputEventQueue.add(
+                    Pair.create(InputEventType.TRACKPAD, MotionEvent.obtainNoHistory(event)));
             return true;
         } catch (Exception e) {
             Log.e(TAG, e.toString());
@@ -1541,6 +1558,7 @@
                                             sendMouseEventInternal(event.second);
                                             break;
                                     }
+                                    event.second.recycle();
                                 } catch (Exception e) {
                                     Log.e(TAG, e.toString());
                                 }
@@ -1750,7 +1768,6 @@
         }
     }
 
-
     /**
      * Returns the stream object representing the log output from the virtual machine. The log
      * output is only available if the VirtualMachineConfig specifies that it should be {@linkplain
@@ -1884,22 +1901,26 @@
     private static void deleteRecursively(File dir) throws IOException {
         // Note: This doesn't follow symlinks, which is important. Instead they are just deleted
         // (and Files.delete deletes the link not the target).
-        Files.walkFileTree(dir.toPath(), new SimpleFileVisitor<>() {
-            @Override
-            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
-                    throws IOException {
-                Files.delete(file);
-                return FileVisitResult.CONTINUE;
-            }
+        Files.walkFileTree(
+                dir.toPath(),
+                new SimpleFileVisitor<>() {
+                    @Override
+                    public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
+                            throws IOException {
+                        Files.delete(file);
+                        return FileVisitResult.CONTINUE;
+                    }
 
-            @Override
-            public FileVisitResult postVisitDirectory(Path dir, IOException e) throws IOException {
-                // Directory is deleted after we've visited (deleted) all its contents, so it
-                // should be empty by now.
-                Files.delete(dir);
-                return FileVisitResult.CONTINUE;
-            }
-        });
+                    @Override
+                    public FileVisitResult postVisitDirectory(Path dir, IOException e)
+                            throws IOException {
+                        // Directory is deleted after we've visited (deleted) all its contents, so
+                        // it
+                        // should be empty by now.
+                        Files.delete(dir);
+                        return FileVisitResult.CONTINUE;
+                    }
+                });
     }
 
     /**
@@ -2087,21 +2108,14 @@
         String payloadBinaryName = config.getPayloadBinaryName();
 
         StringBuilder result = new StringBuilder();
-        result.append("VirtualMachine(")
-                .append("name:")
-                .append(getName())
-                .append(", ");
+        result.append("VirtualMachine(").append("name:").append(getName()).append(", ");
         if (payloadBinaryName != null) {
             result.append("payload:").append(payloadBinaryName).append(", ");
         }
         if (payloadConfigPath != null) {
-            result.append("config:")
-                    .append(payloadConfigPath)
-                    .append(", ");
+            result.append("config:").append(payloadConfigPath).append(", ");
         }
-        result.append("package: ")
-                .append(mPackageName)
-                .append(")");
+        result.append("package: ").append(mPackageName).append(")");
         return result.toString();
     }
 
@@ -2148,7 +2162,7 @@
     private static List<String> parseExtraApkListFromPayloadConfig(JsonReader reader)
             throws VirtualMachineException {
         /*
-         * JSON schema from packages/modules/Virtualization/microdroid/payload/config/src/lib.rs:
+         * JSON schema from packages/modules/Virtualization/microdroid/libs/libmicrodroid_payload_metadata/config/src/lib.rs:
          *
          * <p>{ "extra_apks": [ { "path": "/system/app/foo.apk", }, ... ], ... }
          */
@@ -2209,7 +2223,7 @@
             throws VirtualMachineException {
         try (FileChannel idOutput = new FileOutputStream(mInstanceIdPath).getChannel();
                 FileChannel idInput = new AutoCloseInputStream(instanceIdFd).getChannel()) {
-            idOutput.transferFrom(idInput, /*position=*/ 0, idInput.size());
+            idOutput.transferFrom(idInput, /* position= */ 0, idInput.size());
         } catch (IOException e) {
             throw new VirtualMachineException("failed to copy instance_id", e);
         }
@@ -2219,7 +2233,7 @@
             throws VirtualMachineException {
         try (FileChannel instance = new FileOutputStream(mInstanceFilePath).getChannel();
                 FileChannel instanceInput = new AutoCloseInputStream(instanceFd).getChannel()) {
-            instance.transferFrom(instanceInput, /*position=*/ 0, instanceInput.size());
+            instance.transferFrom(instanceInput, /* position= */ 0, instanceInput.size());
         } catch (IOException e) {
             throw new VirtualMachineException("failed to transfer instance image", e);
         }
@@ -2229,7 +2243,7 @@
             throws VirtualMachineException {
         try (FileChannel storeOutput = new FileOutputStream(mEncryptedStoreFilePath).getChannel();
                 FileChannel storeInput = new AutoCloseInputStream(encryptedStoreFd).getChannel()) {
-            storeOutput.transferFrom(storeInput, /*position=*/ 0, storeInput.size());
+            storeOutput.transferFrom(storeInput, /* position= */ 0, storeInput.size());
         } catch (IOException e) {
             throw new VirtualMachineException("failed to transfer encryptedstore image", e);
         }
diff --git a/java/framework/src/android/system/virtualmachine/VirtualMachineCallback.java b/libs/framework-virtualization/src/android/system/virtualmachine/VirtualMachineCallback.java
similarity index 95%
rename from java/framework/src/android/system/virtualmachine/VirtualMachineCallback.java
rename to libs/framework-virtualization/src/android/system/virtualmachine/VirtualMachineCallback.java
index d72ba14..b077826 100644
--- a/java/framework/src/android/system/virtualmachine/VirtualMachineCallback.java
+++ b/libs/framework-virtualization/src/android/system/virtualmachine/VirtualMachineCallback.java
@@ -35,23 +35,28 @@
 public interface VirtualMachineCallback {
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
-    @IntDef(prefix = "ERROR_", value = {
-        ERROR_UNKNOWN,
-        ERROR_PAYLOAD_VERIFICATION_FAILED,
-        ERROR_PAYLOAD_CHANGED,
-        ERROR_PAYLOAD_INVALID_CONFIG
-    })
+    @IntDef(
+            prefix = "ERROR_",
+            value = {
+                ERROR_UNKNOWN,
+                ERROR_PAYLOAD_VERIFICATION_FAILED,
+                ERROR_PAYLOAD_CHANGED,
+                ERROR_PAYLOAD_INVALID_CONFIG
+            })
     @interface ErrorCode {}
 
     /** Error code for all other errors not listed below. */
     int ERROR_UNKNOWN = 0;
+
     /**
      * Error code indicating that the payload can't be verified due to various reasons (e.g invalid
      * merkle tree, invalid formats, etc).
      */
     int ERROR_PAYLOAD_VERIFICATION_FAILED = 1;
+
     /** Error code indicating that the payload is verified, but has changed since the last boot. */
     int ERROR_PAYLOAD_CHANGED = 2;
+
     /** Error code indicating that the payload config is invalid. */
     int ERROR_PAYLOAD_INVALID_CONFIG = 3;
 
diff --git a/java/framework/src/android/system/virtualmachine/VirtualMachineConfig.java b/libs/framework-virtualization/src/android/system/virtualmachine/VirtualMachineConfig.java
similarity index 98%
rename from java/framework/src/android/system/virtualmachine/VirtualMachineConfig.java
rename to libs/framework-virtualization/src/android/system/virtualmachine/VirtualMachineConfig.java
index a2d8ee6..7ae4a55 100644
--- a/java/framework/src/android/system/virtualmachine/VirtualMachineConfig.java
+++ b/libs/framework-virtualization/src/android/system/virtualmachine/VirtualMachineConfig.java
@@ -105,10 +105,9 @@
 
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
-    @IntDef(prefix = "DEBUG_LEVEL_", value = {
-            DEBUG_LEVEL_NONE,
-            DEBUG_LEVEL_FULL
-    })
+    @IntDef(
+            prefix = "DEBUG_LEVEL_",
+            value = {DEBUG_LEVEL_NONE, DEBUG_LEVEL_FULL})
     public @interface DebugLevel {}
 
     /**
@@ -164,9 +163,7 @@
 
     @DebugLevel private final int mDebugLevel;
 
-    /**
-     * Whether to run the VM in protected mode, so the host can't access its memory.
-     */
+    /** Whether to run the VM in protected mode, so the host can't access its memory. */
     private final boolean mProtectedVm;
 
     /**
@@ -181,9 +178,7 @@
     /** The serial device for VM console input. */
     @Nullable private final String mConsoleInputDevice;
 
-    /**
-     * Path within the APK to the payload config file that defines software aspects of the VM.
-     */
+    /** Path within the APK to the payload config file that defines software aspects of the VM. */
     @Nullable private final String mPayloadConfigPath;
 
     /** Name of the payload binary file within the APK that will be executed within the VM. */
@@ -754,11 +749,9 @@
             VirtualMachinePayloadConfig payloadConfig = new VirtualMachinePayloadConfig();
             payloadConfig.payloadBinaryName = mPayloadBinaryName;
             payloadConfig.extraApks = Collections.emptyList();
-            vsConfig.payload =
-                    VirtualMachineAppConfig.Payload.payloadConfig(payloadConfig);
+            vsConfig.payload = VirtualMachineAppConfig.Payload.payloadConfig(payloadConfig);
         } else {
-            vsConfig.payload =
-                    VirtualMachineAppConfig.Payload.configPath(mPayloadConfigPath);
+            vsConfig.payload = VirtualMachineAppConfig.Payload.configPath(mPayloadConfigPath);
         }
         vsConfig.osName = mOs;
         switch (mDebugLevel) {
@@ -1013,7 +1006,8 @@
         /**
          * Sets the path within the APK to the payload config file that defines software aspects of
          * the VM. The file is a JSON file; see
-         * packages/modules/Virtualization/microdroid/payload/config/src/lib.rs for the format.
+         * packages/modules/Virtualization/libs/libmicrodroid_payload_metadata/config/src/lib.rs for
+         * the format.
          *
          * @hide
          */
diff --git a/java/framework/src/android/system/virtualmachine/VirtualMachineCustomImageConfig.java b/libs/framework-virtualization/src/android/system/virtualmachine/VirtualMachineCustomImageConfig.java
similarity index 99%
rename from java/framework/src/android/system/virtualmachine/VirtualMachineCustomImageConfig.java
rename to libs/framework-virtualization/src/android/system/virtualmachine/VirtualMachineCustomImageConfig.java
index 2da83a0..37dc8fa 100644
--- a/java/framework/src/android/system/virtualmachine/VirtualMachineCustomImageConfig.java
+++ b/libs/framework-virtualization/src/android/system/virtualmachine/VirtualMachineCustomImageConfig.java
@@ -211,8 +211,6 @@
         return builder.build();
     }
 
-
-
     PersistableBundle toPersistableBundle() {
         PersistableBundle pb = new PersistableBundle();
         pb.putString(KEY_NAME, this.name);
diff --git a/java/framework/src/android/system/virtualmachine/VirtualMachineDescriptor.java b/libs/framework-virtualization/src/android/system/virtualmachine/VirtualMachineDescriptor.java
similarity index 100%
rename from java/framework/src/android/system/virtualmachine/VirtualMachineDescriptor.java
rename to libs/framework-virtualization/src/android/system/virtualmachine/VirtualMachineDescriptor.java
diff --git a/java/framework/src/android/system/virtualmachine/VirtualMachineException.java b/libs/framework-virtualization/src/android/system/virtualmachine/VirtualMachineException.java
similarity index 100%
rename from java/framework/src/android/system/virtualmachine/VirtualMachineException.java
rename to libs/framework-virtualization/src/android/system/virtualmachine/VirtualMachineException.java
diff --git a/java/framework/src/android/system/virtualmachine/VirtualMachineManager.java b/libs/framework-virtualization/src/android/system/virtualmachine/VirtualMachineManager.java
similarity index 98%
rename from java/framework/src/android/system/virtualmachine/VirtualMachineManager.java
rename to libs/framework-virtualization/src/android/system/virtualmachine/VirtualMachineManager.java
index abb2c81..242dc91 100644
--- a/java/framework/src/android/system/virtualmachine/VirtualMachineManager.java
+++ b/libs/framework-virtualization/src/android/system/virtualmachine/VirtualMachineManager.java
@@ -89,10 +89,10 @@
      * @hide
      */
     @Retention(RetentionPolicy.SOURCE)
-    @IntDef(prefix = "CAPABILITY_", flag = true, value = {
-            CAPABILITY_PROTECTED_VM,
-            CAPABILITY_NON_PROTECTED_VM
-    })
+    @IntDef(
+            prefix = "CAPABILITY_",
+            flag = true,
+            value = {CAPABILITY_PROTECTED_VM, CAPABILITY_NON_PROTECTED_VM})
     public @interface Capability {}
 
     /**
diff --git a/java/framework/src/android/system/virtualmachine/VirtualizationFrameworkInitializer.java b/libs/framework-virtualization/src/android/system/virtualmachine/VirtualizationFrameworkInitializer.java
similarity index 100%
rename from java/framework/src/android/system/virtualmachine/VirtualizationFrameworkInitializer.java
rename to libs/framework-virtualization/src/android/system/virtualmachine/VirtualizationFrameworkInitializer.java
diff --git a/java/framework/src/android/system/virtualmachine/VirtualizationService.java b/libs/framework-virtualization/src/android/system/virtualmachine/VirtualizationService.java
similarity index 100%
rename from java/framework/src/android/system/virtualmachine/VirtualizationService.java
rename to libs/framework-virtualization/src/android/system/virtualmachine/VirtualizationService.java
diff --git a/flags/cpp/Android.bp b/libs/libavf_cc_flags/Android.bp
similarity index 100%
rename from flags/cpp/Android.bp
rename to libs/libavf_cc_flags/Android.bp
diff --git a/flags/cpp/include/android/avf_cc_flags.h b/libs/libavf_cc_flags/include/android/avf_cc_flags.h
similarity index 100%
rename from flags/cpp/include/android/avf_cc_flags.h
rename to libs/libavf_cc_flags/include/android/avf_cc_flags.h
diff --git a/service_vm/client_vm_csr/Android.bp b/libs/libclient_vm_csr/Android.bp
similarity index 100%
rename from service_vm/client_vm_csr/Android.bp
rename to libs/libclient_vm_csr/Android.bp
diff --git a/service_vm/client_vm_csr/TEST_MAPPING b/libs/libclient_vm_csr/TEST_MAPPING
similarity index 100%
rename from service_vm/client_vm_csr/TEST_MAPPING
rename to libs/libclient_vm_csr/TEST_MAPPING
diff --git a/service_vm/client_vm_csr/src/lib.rs b/libs/libclient_vm_csr/src/lib.rs
similarity index 98%
rename from service_vm/client_vm_csr/src/lib.rs
rename to libs/libclient_vm_csr/src/lib.rs
index 70152cb..77cfea9 100644
--- a/service_vm/client_vm_csr/src/lib.rs
+++ b/libs/libclient_vm_csr/src/lib.rs
@@ -36,7 +36,7 @@
 
 /// Key parameters for the attestation key.
 ///
-/// See service_vm/comm/client_vm_csr.cddl for more information about the attestation key.
+/// See libs/libservice_vm_comm/client_vm_csr.cddl for more information about the attestation key.
 const ATTESTATION_KEY_NID: Nid = Nid::X9_62_PRIME256V1; // NIST P-256 curve
 const ATTESTATION_KEY_ALGO: iana::Algorithm = iana::Algorithm::ES256;
 const ATTESTATION_KEY_CURVE: iana::EllipticCurve = iana::EllipticCurve::P_256;
diff --git a/compos/common/Android.bp b/libs/libcompos_common/Android.bp
similarity index 100%
rename from compos/common/Android.bp
rename to libs/libcompos_common/Android.bp
diff --git a/compos/common/binder.rs b/libs/libcompos_common/binder.rs
similarity index 100%
rename from compos/common/binder.rs
rename to libs/libcompos_common/binder.rs
diff --git a/compos/common/compos_client.rs b/libs/libcompos_common/compos_client.rs
similarity index 100%
rename from compos/common/compos_client.rs
rename to libs/libcompos_common/compos_client.rs
diff --git a/compos/common/lib.rs b/libs/libcompos_common/lib.rs
similarity index 100%
rename from compos/common/lib.rs
rename to libs/libcompos_common/lib.rs
diff --git a/compos/common/odrefresh.rs b/libs/libcompos_common/odrefresh.rs
similarity index 100%
rename from compos/common/odrefresh.rs
rename to libs/libcompos_common/odrefresh.rs
diff --git a/compos/common/timeouts.rs b/libs/libcompos_common/timeouts.rs
similarity index 100%
rename from compos/common/timeouts.rs
rename to libs/libcompos_common/timeouts.rs
diff --git a/libs/libfdt/Android.bp b/libs/libfdt/Android.bp
index 7dc9e64..b2e7b2b 100644
--- a/libs/libfdt/Android.bp
+++ b/libs/libfdt/Android.bp
@@ -16,6 +16,9 @@
         "--raw-line=#![no_std]",
         "--ctypes-prefix=core::ffi",
     ],
+    dylib: {
+        enabled: false,
+    },
     static_libs: [
         "libfdt",
     ],
diff --git a/microdroid/payload/Android.bp b/libs/libmicrodroid_payload_metadata/Android.bp
similarity index 100%
rename from microdroid/payload/Android.bp
rename to libs/libmicrodroid_payload_metadata/Android.bp
diff --git a/microdroid/payload/README.md b/libs/libmicrodroid_payload_metadata/README.md
similarity index 92%
rename from microdroid/payload/README.md
rename to libs/libmicrodroid_payload_metadata/README.md
index b1eb63f..98ace61 100644
--- a/microdroid/payload/README.md
+++ b/libs/libmicrodroid_payload_metadata/README.md
@@ -3,7 +3,7 @@
 Payload disk is a composite disk image referencing host APEXes and an APK so that microdroid
 mounts/activates APK/APEXes and executes a binary within the APK.
 
-Payload disk is created by [VirtualizationService](../../virtualizationservice) Service when
+Payload disk is created by [VirtualizationService](../../android/virtualizationservice) Service when
 starting a VM.
 
 ## Partitions
diff --git a/microdroid/payload/config/Android.bp b/libs/libmicrodroid_payload_metadata/config/Android.bp
similarity index 100%
rename from microdroid/payload/config/Android.bp
rename to libs/libmicrodroid_payload_metadata/config/Android.bp
diff --git a/microdroid/payload/config/src/lib.rs b/libs/libmicrodroid_payload_metadata/config/src/lib.rs
similarity index 100%
rename from microdroid/payload/config/src/lib.rs
rename to libs/libmicrodroid_payload_metadata/config/src/lib.rs
diff --git a/microdroid/payload/include/microdroid/metadata.h b/libs/libmicrodroid_payload_metadata/include/microdroid/metadata.h
similarity index 100%
rename from microdroid/payload/include/microdroid/metadata.h
rename to libs/libmicrodroid_payload_metadata/include/microdroid/metadata.h
diff --git a/microdroid/payload/metadata.cc b/libs/libmicrodroid_payload_metadata/metadata.cc
similarity index 100%
rename from microdroid/payload/metadata.cc
rename to libs/libmicrodroid_payload_metadata/metadata.cc
diff --git a/microdroid/payload/metadata.proto b/libs/libmicrodroid_payload_metadata/metadata.proto
similarity index 100%
rename from microdroid/payload/metadata.proto
rename to libs/libmicrodroid_payload_metadata/metadata.proto
diff --git a/microdroid/payload/metadata/Android.bp b/libs/libmicrodroid_payload_metadata/metadata/Android.bp
similarity index 100%
rename from microdroid/payload/metadata/Android.bp
rename to libs/libmicrodroid_payload_metadata/metadata/Android.bp
diff --git a/microdroid/payload/metadata/src/lib.rs b/libs/libmicrodroid_payload_metadata/metadata/src/lib.rs
similarity index 100%
rename from microdroid/payload/metadata/src/lib.rs
rename to libs/libmicrodroid_payload_metadata/metadata/src/lib.rs
diff --git a/microdroid/payload/src/com/android/virt/PayloadMetadata.java b/libs/libmicrodroid_payload_metadata/src/com/android/virt/PayloadMetadata.java
similarity index 100%
rename from microdroid/payload/src/com/android/virt/PayloadMetadata.java
rename to libs/libmicrodroid_payload_metadata/src/com/android/virt/PayloadMetadata.java
diff --git a/service_vm/comm/Android.bp b/libs/libservice_vm_comm/Android.bp
similarity index 100%
rename from service_vm/comm/Android.bp
rename to libs/libservice_vm_comm/Android.bp
diff --git a/service_vm/comm/TEST_MAPPING b/libs/libservice_vm_comm/TEST_MAPPING
similarity index 100%
rename from service_vm/comm/TEST_MAPPING
rename to libs/libservice_vm_comm/TEST_MAPPING
diff --git a/service_vm/comm/src/client_vm_csr.cddl b/libs/libservice_vm_comm/src/client_vm_csr.cddl
similarity index 100%
rename from service_vm/comm/src/client_vm_csr.cddl
rename to libs/libservice_vm_comm/src/client_vm_csr.cddl
diff --git a/service_vm/comm/src/csr.rs b/libs/libservice_vm_comm/src/csr.rs
similarity index 100%
rename from service_vm/comm/src/csr.rs
rename to libs/libservice_vm_comm/src/csr.rs
diff --git a/service_vm/comm/src/lib.rs b/libs/libservice_vm_comm/src/lib.rs
similarity index 100%
rename from service_vm/comm/src/lib.rs
rename to libs/libservice_vm_comm/src/lib.rs
diff --git a/service_vm/comm/src/message.rs b/libs/libservice_vm_comm/src/message.rs
similarity index 100%
rename from service_vm/comm/src/message.rs
rename to libs/libservice_vm_comm/src/message.rs
diff --git a/service_vm/comm/src/vsock.rs b/libs/libservice_vm_comm/src/vsock.rs
similarity index 100%
rename from service_vm/comm/src/vsock.rs
rename to libs/libservice_vm_comm/src/vsock.rs
diff --git a/service_vm/comm/tests/api_test.rs b/libs/libservice_vm_comm/tests/api_test.rs
similarity index 100%
rename from service_vm/comm/tests/api_test.rs
rename to libs/libservice_vm_comm/tests/api_test.rs
diff --git a/service_vm/fake_chain/Android.bp b/libs/libservice_vm_fake_chain/Android.bp
similarity index 94%
rename from service_vm/fake_chain/Android.bp
rename to libs/libservice_vm_fake_chain/Android.bp
index 2bc7b4e..39f36eb 100644
--- a/service_vm/fake_chain/Android.bp
+++ b/libs/libservice_vm_fake_chain/Android.bp
@@ -22,7 +22,7 @@
     defaults: ["avf_build_flags_rust"],
     srcs: ["src/lib.rs"],
     visibility: [
-        "//packages/modules/Virtualization/rialto:__subpackages__",
+        "//packages/modules/Virtualization/guest/rialto:__subpackages__",
     ],
     prefer_rlib: true,
     rustlibs: [
diff --git a/service_vm/fake_chain/src/client_vm.rs b/libs/libservice_vm_fake_chain/src/client_vm.rs
similarity index 100%
rename from service_vm/fake_chain/src/client_vm.rs
rename to libs/libservice_vm_fake_chain/src/client_vm.rs
diff --git a/service_vm/fake_chain/src/lib.rs b/libs/libservice_vm_fake_chain/src/lib.rs
similarity index 100%
rename from service_vm/fake_chain/src/lib.rs
rename to libs/libservice_vm_fake_chain/src/lib.rs
diff --git a/service_vm/fake_chain/src/service_vm.rs b/libs/libservice_vm_fake_chain/src/service_vm.rs
similarity index 100%
rename from service_vm/fake_chain/src/service_vm.rs
rename to libs/libservice_vm_fake_chain/src/service_vm.rs
diff --git a/service_vm/manager/Android.bp b/libs/libservice_vm_manager/Android.bp
similarity index 100%
rename from service_vm/manager/Android.bp
rename to libs/libservice_vm_manager/Android.bp
diff --git a/service_vm/manager/src/lib.rs b/libs/libservice_vm_manager/src/lib.rs
similarity index 98%
rename from service_vm/manager/src/lib.rs
rename to libs/libservice_vm_manager/src/lib.rs
index 78ed85b..8564c51 100644
--- a/service_vm/manager/src/lib.rs
+++ b/libs/libservice_vm_manager/src/lib.rs
@@ -37,12 +37,14 @@
 use vmclient::{DeathReason, VmInstance};
 use vsock::{VsockListener, VsockStream, VMADDR_CID_HOST};
 
+/// Size of virtual memory allocated to the Service VM.
+pub const VM_MEMORY_MB: i32 = 8;
+
 const VIRT_DATA_DIR: &str = "/data/misc/apexdata/com.android.virt";
 const RIALTO_PATH: &str = "/apex/com.android.virt/etc/rialto.bin";
 const INSTANCE_IMG_NAME: &str = "service_vm_instance.img";
 const INSTANCE_ID_FILENAME: &str = "service_vm_instance_id";
 const INSTANCE_IMG_SIZE_BYTES: i64 = 1 << 20; // 1MB
-const MEMORY_MB: i32 = 300;
 const WRITE_BUFFER_CAPACITY: usize = 512;
 const READ_TIMEOUT: Duration = Duration::from_secs(10);
 const WRITE_TIMEOUT: Duration = Duration::from_secs(10);
@@ -227,11 +229,11 @@
     let instance_id = get_or_allocate_instance_id(service.as_ref(), instance_id_file)?;
     let config = VirtualMachineConfig::RawConfig(VirtualMachineRawConfig {
         name: String::from("Service VM"),
-        bootloader: Some(ParcelFileDescriptor::new(rialto)),
+        kernel: Some(ParcelFileDescriptor::new(rialto)),
         disks: vec![DiskImage { image: None, partitions: writable_partitions, writable: true }],
         instanceId: instance_id,
         protectedVm: true,
-        memoryMib: MEMORY_MB,
+        memoryMib: VM_MEMORY_MB,
         cpuTopology: CpuTopology::ONE_CPU,
         platformVersion: "~1.0".to_string(),
         gdbPort: 0, // No gdb
diff --git a/service_vm/requests/Android.bp b/libs/libservice_vm_requests/Android.bp
similarity index 100%
rename from service_vm/requests/Android.bp
rename to libs/libservice_vm_requests/Android.bp
diff --git a/service_vm/requests/TEST_MAPPING b/libs/libservice_vm_requests/TEST_MAPPING
similarity index 100%
rename from service_vm/requests/TEST_MAPPING
rename to libs/libservice_vm_requests/TEST_MAPPING
diff --git a/service_vm/requests/src/api.rs b/libs/libservice_vm_requests/src/api.rs
similarity index 100%
rename from service_vm/requests/src/api.rs
rename to libs/libservice_vm_requests/src/api.rs
diff --git a/service_vm/requests/src/cert.rs b/libs/libservice_vm_requests/src/cert.rs
similarity index 100%
rename from service_vm/requests/src/cert.rs
rename to libs/libservice_vm_requests/src/cert.rs
diff --git a/service_vm/requests/src/client_vm.rs b/libs/libservice_vm_requests/src/client_vm.rs
similarity index 98%
rename from service_vm/requests/src/client_vm.rs
rename to libs/libservice_vm_requests/src/client_vm.rs
index 2aa7113..4e54510 100644
--- a/service_vm/requests/src/client_vm.rs
+++ b/libs/libservice_vm_requests/src/client_vm.rs
@@ -55,7 +55,7 @@
         vendor_hashtree_root_digest_from_dt,
     )?;
 
-    // AAD is empty as defined in service_vm/comm/client_vm_csr.cddl.
+    // AAD is empty as defined in libs/libservice_vm_comm/client_vm_csr.cddl.
     let aad = &[];
 
     // Verifies the first signature with the leaf private key in the DICE chain.
diff --git a/service_vm/requests/src/dice.rs b/libs/libservice_vm_requests/src/dice.rs
similarity index 100%
rename from service_vm/requests/src/dice.rs
rename to libs/libservice_vm_requests/src/dice.rs
diff --git a/service_vm/requests/src/keyblob.rs b/libs/libservice_vm_requests/src/keyblob.rs
similarity index 100%
rename from service_vm/requests/src/keyblob.rs
rename to libs/libservice_vm_requests/src/keyblob.rs
diff --git a/service_vm/requests/src/lib.rs b/libs/libservice_vm_requests/src/lib.rs
similarity index 100%
rename from service_vm/requests/src/lib.rs
rename to libs/libservice_vm_requests/src/lib.rs
diff --git a/service_vm/requests/src/pub_key.rs b/libs/libservice_vm_requests/src/pub_key.rs
similarity index 100%
rename from service_vm/requests/src/pub_key.rs
rename to libs/libservice_vm_requests/src/pub_key.rs
diff --git a/service_vm/requests/src/rkp.rs b/libs/libservice_vm_requests/src/rkp.rs
similarity index 100%
rename from service_vm/requests/src/rkp.rs
rename to libs/libservice_vm_requests/src/rkp.rs
diff --git a/java/jni/Android.bp b/libs/libvirtualization_jni/Android.bp
similarity index 100%
rename from java/jni/Android.bp
rename to libs/libvirtualization_jni/Android.bp
diff --git a/java/jni/android_system_virtualmachine_VirtualMachine.cpp b/libs/libvirtualization_jni/android_system_virtualmachine_VirtualMachine.cpp
similarity index 96%
rename from java/jni/android_system_virtualmachine_VirtualMachine.cpp
rename to libs/libvirtualization_jni/android_system_virtualmachine_VirtualMachine.cpp
index ed102bf..3f1d8a0 100644
--- a/java/jni/android_system_virtualmachine_VirtualMachine.cpp
+++ b/libs/libvirtualization_jni/android_system_virtualmachine_VirtualMachine.cpp
@@ -49,7 +49,7 @@
 
 extern "C" JNIEXPORT jobject JNICALL
 Java_android_system_virtualmachine_VirtualMachine_nativeConnectToVsockServer(
-        JNIEnv* env, [[maybe_unused]] jclass clazz, jobject vmBinder, jint port) {
+        JNIEnv *env, [[maybe_unused]] jclass clazz, jobject vmBinder, jint port) {
     using aidl::android::system::virtualizationservice::IVirtualMachine;
     using ndk::ScopedFileDescriptor;
     using ndk::SpAIBinder;
@@ -59,8 +59,8 @@
     std::tuple args{env, vm.get(), port};
     using Args = decltype(args);
 
-    auto requestFunc = [](void* param) {
-        auto [env, vm, port] = *static_cast<Args*>(param);
+    auto requestFunc = [](void *param) {
+        auto [env, vm, port] = *static_cast<Args *>(param);
 
         ScopedFileDescriptor fd;
         if (auto status = vm->connectVsock(port, &fd); !status.isOk()) {
diff --git a/java/jni/android_system_virtualmachine_VirtualizationService.cpp b/libs/libvirtualization_jni/android_system_virtualmachine_VirtualizationService.cpp
similarity index 100%
rename from java/jni/android_system_virtualmachine_VirtualizationService.cpp
rename to libs/libvirtualization_jni/android_system_virtualmachine_VirtualizationService.cpp
diff --git a/java/jni/common.h b/libs/libvirtualization_jni/common.h
similarity index 100%
rename from java/jni/common.h
rename to libs/libvirtualization_jni/common.h
diff --git a/vm_payload/Android.bp b/libs/libvm_payload/Android.bp
similarity index 100%
rename from vm_payload/Android.bp
rename to libs/libvm_payload/Android.bp
diff --git a/vm_payload/README.md b/libs/libvm_payload/README.md
similarity index 93%
rename from vm_payload/README.md
rename to libs/libvm_payload/README.md
index 66fd532..8ef1bac 100644
--- a/vm_payload/README.md
+++ b/libs/libvm_payload/README.md
@@ -2,14 +2,14 @@
 
 This directory contains the definition of the VM Payload API. This is a native
 API, exposed as a set of C functions, available to payload code running inside a
-[Microdroid](https://android.googlesource.com/platform/packages/modules/Virtualization/+/refs/heads/main/microdroid/README.md)
+[Microdroid](https://android.googlesource.com/platform/packages/modules/Virtualization/+/refs/heads/main/build/microdroid/README.md)
 VM.
 
 Note that only native code is supported in Microdroid, so no Java APIs are
 available in the VM, and only 64 bit code is supported.
 
 To create a VM and run the payload from Android see the [AVF Java
-APIs](../java/framework/README.md).
+APIs](../libs/framework-virtualization/README.md).
 
 ## Entry point
 
@@ -17,7 +17,7 @@
 under the `lib/<ABI>` directory, like other JNI code.
 
 The primary .so, which is specified as part of the VM configuration via
-[VirtualMachineConfig.Builder#setPayloadBinaryPath](https://android.googlesource.com/platform/packages/modules/Virtualization/+/refs/heads/main/java/framework/src/android/system/virtualmachine/VirtualMachineConfig.java),
+[VirtualMachineConfig.Builder#setPayloadBinaryPath](https://android.googlesource.com/platform/packages/modules/Virtualization/+/refs/heads/main/libs/framework-virtualization/src/android/system/virtualmachine/VirtualMachineConfig.java),
 must define the entry point for the payload.
 
 This entry point is a C function called `AVmPayload_main()`, as declared in
diff --git a/vm_payload/include-restricted/vm_payload_restricted.h b/libs/libvm_payload/include-restricted/vm_payload_restricted.h
similarity index 100%
rename from vm_payload/include-restricted/vm_payload_restricted.h
rename to libs/libvm_payload/include-restricted/vm_payload_restricted.h
diff --git a/vm_payload/include/vm_main.h b/libs/libvm_payload/include/vm_main.h
similarity index 100%
rename from vm_payload/include/vm_main.h
rename to libs/libvm_payload/include/vm_main.h
diff --git a/vm_payload/include/vm_payload.h b/libs/libvm_payload/include/vm_payload.h
similarity index 100%
rename from vm_payload/include/vm_payload.h
rename to libs/libvm_payload/include/vm_payload.h
diff --git a/vm_payload/libvm_payload.map.txt b/libs/libvm_payload/libvm_payload.map.txt
similarity index 100%
rename from vm_payload/libvm_payload.map.txt
rename to libs/libvm_payload/libvm_payload.map.txt
diff --git a/vm_payload/src/lib.rs b/libs/libvm_payload/src/lib.rs
similarity index 100%
rename from vm_payload/src/lib.rs
rename to libs/libvm_payload/src/lib.rs
diff --git a/vm_payload/stub/readme.txt b/libs/libvm_payload/stub/readme.txt
similarity index 85%
rename from vm_payload/stub/readme.txt
rename to libs/libvm_payload/stub/readme.txt
index 47679b5..15b8743 100644
--- a/vm_payload/stub/readme.txt
+++ b/libs/libvm_payload/stub/readme.txt
@@ -14,4 +14,4 @@
 lunch aosp_arm64-eng
 m MicrodroidTestNativeLib
 
-The generated stub file can then be found at out/soong/.intermediates/packages/modules/Virtualization/vm_payload/libvm_payload/android_arm64_armv8-a_shared_current/gen/stub.c
+The generated stub file can then be found at out/soong/.intermediates/packages/modules/Virtualization/libs/libvm_payload/libvm_payload/android_arm64_armv8-a_shared_current/gen/stub.c
diff --git a/vm_payload/stub/stub.c b/libs/libvm_payload/stub/stub.c
similarity index 100%
rename from vm_payload/stub/stub.c
rename to libs/libvm_payload/stub/stub.c
diff --git a/vm_payload/wrapper/attestation.rs b/libs/libvm_payload/wrapper/attestation.rs
similarity index 100%
rename from vm_payload/wrapper/attestation.rs
rename to libs/libvm_payload/wrapper/attestation.rs
diff --git a/vm_payload/wrapper/lib.rs b/libs/libvm_payload/wrapper/lib.rs
similarity index 98%
rename from vm_payload/wrapper/lib.rs
rename to libs/libvm_payload/wrapper/lib.rs
index d3f03d7..b9ce6c8 100644
--- a/vm_payload/wrapper/lib.rs
+++ b/libs/libvm_payload/wrapper/lib.rs
@@ -17,7 +17,7 @@
 //! Rust wrapper for the VM Payload API, allowing virtual machine payload code to be written in
 //! Rust. This wraps the raw C API, accessed via bindgen, into a more idiomatic Rust interface.
 //!
-//! See `https://cs.android.com/android/platform/superproject/main/+/main:packages/modules/Virtualization/vm_payload/README.md`
+//! See `https://cs.android.com/android/platform/superproject/main/+/main:packages/modules/Virtualization/libs/libvm_payload/README.md`
 //! for more information on the VM Payload API.
 
 mod attestation;
diff --git a/vmbase/Android.bp b/libs/libvmbase/Android.bp
similarity index 100%
rename from vmbase/Android.bp
rename to libs/libvmbase/Android.bp
diff --git a/vmbase/README.md b/libs/libvmbase/README.md
similarity index 100%
rename from vmbase/README.md
rename to libs/libvmbase/README.md
diff --git a/vmbase/TEST_MAPPING b/libs/libvmbase/TEST_MAPPING
similarity index 100%
rename from vmbase/TEST_MAPPING
rename to libs/libvmbase/TEST_MAPPING
diff --git a/vmbase/common.h b/libs/libvmbase/common.h
similarity index 100%
rename from vmbase/common.h
rename to libs/libvmbase/common.h
diff --git a/vmbase/entry.S b/libs/libvmbase/entry.S
similarity index 100%
rename from vmbase/entry.S
rename to libs/libvmbase/entry.S
diff --git a/vmbase/exceptions.S b/libs/libvmbase/exceptions.S
similarity index 100%
rename from vmbase/exceptions.S
rename to libs/libvmbase/exceptions.S
diff --git a/vmbase/exceptions_panic.S b/libs/libvmbase/exceptions_panic.S
similarity index 100%
rename from vmbase/exceptions_panic.S
rename to libs/libvmbase/exceptions_panic.S
diff --git a/vmbase/sections.ld b/libs/libvmbase/sections.ld
similarity index 96%
rename from vmbase/sections.ld
rename to libs/libvmbase/sections.ld
index c7ef0ec..7d464bc 100644
--- a/vmbase/sections.ld
+++ b/libs/libvmbase/sections.ld
@@ -29,17 +29,13 @@
 
 SECTIONS
 {
-	.dtb (NOLOAD) : {
-		dtb_begin = .;
-		. += LENGTH(dtb_region);
-		dtb_end = .;
-	} >dtb_region
-
 	/*
 	 * Collect together the code. This is page aligned so it can be mapped
 	 * as executable-only.
 	 */
 	.text : ALIGN(4096) {
+		KEEP(*(.init.head));
+		*(.init.head)
 		text_begin = .;
 		*(.init.entry)
 		*(.init.*)
diff --git a/vmbase/src/arch.rs b/libs/libvmbase/src/arch.rs
similarity index 100%
rename from vmbase/src/arch.rs
rename to libs/libvmbase/src/arch.rs
diff --git a/vmbase/src/bionic.rs b/libs/libvmbase/src/bionic.rs
similarity index 100%
rename from vmbase/src/bionic.rs
rename to libs/libvmbase/src/bionic.rs
diff --git a/vmbase/src/console.rs b/libs/libvmbase/src/console.rs
similarity index 78%
rename from vmbase/src/console.rs
rename to libs/libvmbase/src/console.rs
index bbbcb07..7b01bb6 100644
--- a/vmbase/src/console.rs
+++ b/libs/libvmbase/src/console.rs
@@ -15,25 +15,18 @@
 //! Console driver for 8250 UART.
 
 use crate::uart::Uart;
-use core::{
-    cell::OnceCell,
-    fmt::{write, Arguments, Write},
-};
-use spin::mutex::SpinMutex;
+use core::fmt::{write, Arguments, Write};
+use spin::{mutex::SpinMutex, Once};
 
 // Arbitrary limit on the number of consoles that can be registered.
 //
 // Matches the UART count in crosvm.
 const MAX_CONSOLES: usize = 4;
 
-static CONSOLES: [SpinMutex<Option<Uart>>; MAX_CONSOLES] =
-    [SpinMutex::new(None), SpinMutex::new(None), SpinMutex::new(None), SpinMutex::new(None)];
-static ADDRESSES: [SpinMutex<OnceCell<usize>>; MAX_CONSOLES] = [
-    SpinMutex::new(OnceCell::new()),
-    SpinMutex::new(OnceCell::new()),
-    SpinMutex::new(OnceCell::new()),
-    SpinMutex::new(OnceCell::new()),
-];
+static CONSOLES: [Once<SpinMutex<Uart>>; MAX_CONSOLES] =
+    [Once::new(), Once::new(), Once::new(), Once::new()];
+static ADDRESSES: [Once<usize>; MAX_CONSOLES] =
+    [Once::new(), Once::new(), Once::new(), Once::new()];
 
 /// Index of the console used by default for logging.
 pub const DEFAULT_CONSOLE_INDEX: usize = 0;
@@ -52,13 +45,13 @@
 pub unsafe fn init(base_addresses: &[usize]) {
     for (i, &base_address) in base_addresses.iter().enumerate() {
         // Remember the valid address, for emergency console accesses.
-        ADDRESSES[i].lock().set(base_address).expect("console::init() called more than once");
+        ADDRESSES[i].call_once(|| base_address);
 
         // Initialize the console driver, for normal console accesses.
-        let mut console = CONSOLES[i].lock();
-        assert!(console.is_none(), "console::init() called more than once");
-        // SAFETY: base_address must be the base of a mapped UART.
-        console.replace(unsafe { Uart::new(base_address) });
+        assert!(!CONSOLES[i].is_completed(), "console::init() called more than once");
+        // SAFETY: The caller promised that base_address is the base of a mapped UART with no
+        // aliases.
+        CONSOLES[i].call_once(|| SpinMutex::new(unsafe { Uart::new(base_address) }));
     }
 }
 
@@ -66,8 +59,7 @@
 ///
 /// Panics if the n-th console was not initialized by calling [`init`] first.
 pub fn writeln(n: usize, format_args: Arguments) {
-    let mut guard = CONSOLES[n].lock();
-    let uart = guard.as_mut().unwrap();
+    let uart = &mut *CONSOLES[n].get().unwrap().lock();
 
     write(uart, format_args).unwrap();
     let _ = uart.write_str("\n");
@@ -78,8 +70,7 @@
 /// This is intended for use in situations where the UART may be in an unknown state or the global
 /// instance may be locked, such as in an exception handler or panic handler.
 pub fn ewriteln(n: usize, format_args: Arguments) {
-    let Some(cell) = ADDRESSES[n].try_lock() else { return };
-    let Some(addr) = cell.get() else { return };
+    let Some(addr) = ADDRESSES[n].get() else { return };
 
     // SAFETY: addr contains the base of a mapped UART, passed in init().
     let mut uart = unsafe { Uart::new(*addr) };
diff --git a/vmbase/src/entry.rs b/libs/libvmbase/src/entry.rs
similarity index 76%
rename from vmbase/src/entry.rs
rename to libs/libvmbase/src/entry.rs
index ad633ed..99f28fc 100644
--- a/vmbase/src/entry.rs
+++ b/libs/libvmbase/src/entry.rs
@@ -18,7 +18,7 @@
     bionic, console, heap, hyp,
     layout::{UART_ADDRESSES, UART_PAGE_ADDR},
     logger,
-    memory::{SIZE_16KB, SIZE_4KB},
+    memory::{PAGE_SIZE, SIZE_16KB, SIZE_4KB},
     power::{reboot, shutdown},
     rand,
 };
@@ -129,3 +129,37 @@
         }
     };
 }
+
+/// Prepends a Linux kernel header to the generated binary image.
+///
+/// See https://docs.kernel.org/arch/arm64/booting.html
+/// ```
+#[macro_export]
+macro_rules! generate_image_header {
+    () => {
+        #[cfg(not(target_endian = "little"))]
+        compile_error!("Image header uses wrong endianness: bootloaders expect LE!");
+
+        core::arch::global_asm!(
+            // This section gets linked at the start of the image.
+            ".section .init.head, \"ax\"",
+            // This prevents the macro from being called more than once.
+            ".global image_header",
+            "image_header:",
+            // Linux uses a special NOP to be ELF-compatible; we're not.
+            "nop",                          // code0
+            "b entry",                      // code1
+            ".quad 0",                      // text_offset
+            ".quad bin_end - image_header", // image_size
+            ".quad (1 << 1)",               // flags (PAGE_SIZE=4KiB)
+            ".quad 0",                      // res2
+            ".quad 0",                      // res3
+            ".quad 0",                      // res4
+            ".ascii \"ARM\x64\"",           // magic
+            ".long 0",                      // res5
+        );
+    };
+}
+
+// If this fails, the image header flags are out-of-sync with PAGE_SIZE!
+static_assertions::const_assert_eq!(PAGE_SIZE, SIZE_4KB);
diff --git a/vmbase/src/exceptions.rs b/libs/libvmbase/src/exceptions.rs
similarity index 100%
rename from vmbase/src/exceptions.rs
rename to libs/libvmbase/src/exceptions.rs
diff --git a/vmbase/src/fdt.rs b/libs/libvmbase/src/fdt.rs
similarity index 100%
rename from vmbase/src/fdt.rs
rename to libs/libvmbase/src/fdt.rs
diff --git a/vmbase/src/heap.rs b/libs/libvmbase/src/heap.rs
similarity index 100%
rename from vmbase/src/heap.rs
rename to libs/libvmbase/src/heap.rs
diff --git a/vmbase/src/hvc.rs b/libs/libvmbase/src/hvc.rs
similarity index 100%
rename from vmbase/src/hvc.rs
rename to libs/libvmbase/src/hvc.rs
diff --git a/vmbase/src/hvc/trng.rs b/libs/libvmbase/src/hvc/trng.rs
similarity index 100%
rename from vmbase/src/hvc/trng.rs
rename to libs/libvmbase/src/hvc/trng.rs
diff --git a/vmbase/src/hyp.rs b/libs/libvmbase/src/hyp.rs
similarity index 100%
rename from vmbase/src/hyp.rs
rename to libs/libvmbase/src/hyp.rs
diff --git a/vmbase/src/hyp/error.rs b/libs/libvmbase/src/hyp/error.rs
similarity index 100%
rename from vmbase/src/hyp/error.rs
rename to libs/libvmbase/src/hyp/error.rs
diff --git a/vmbase/src/hyp/hypervisor.rs b/libs/libvmbase/src/hyp/hypervisor.rs
similarity index 100%
rename from vmbase/src/hyp/hypervisor.rs
rename to libs/libvmbase/src/hyp/hypervisor.rs
diff --git a/vmbase/src/hyp/hypervisor/common.rs b/libs/libvmbase/src/hyp/hypervisor/common.rs
similarity index 100%
rename from vmbase/src/hyp/hypervisor/common.rs
rename to libs/libvmbase/src/hyp/hypervisor/common.rs
diff --git a/vmbase/src/hyp/hypervisor/geniezone.rs b/libs/libvmbase/src/hyp/hypervisor/geniezone.rs
similarity index 100%
rename from vmbase/src/hyp/hypervisor/geniezone.rs
rename to libs/libvmbase/src/hyp/hypervisor/geniezone.rs
diff --git a/vmbase/src/hyp/hypervisor/gunyah.rs b/libs/libvmbase/src/hyp/hypervisor/gunyah.rs
similarity index 100%
rename from vmbase/src/hyp/hypervisor/gunyah.rs
rename to libs/libvmbase/src/hyp/hypervisor/gunyah.rs
diff --git a/vmbase/src/hyp/hypervisor/kvm.rs b/libs/libvmbase/src/hyp/hypervisor/kvm.rs
similarity index 98%
rename from vmbase/src/hyp/hypervisor/kvm.rs
rename to libs/libvmbase/src/hyp/hypervisor/kvm.rs
index 8450bed..e496f09 100644
--- a/vmbase/src/hyp/hypervisor/kvm.rs
+++ b/libs/libvmbase/src/hyp/hypervisor/kvm.rs
@@ -75,7 +75,7 @@
 const VENDOR_HYP_KVM_MMIO_GUARD_UNMAP_FUNC_ID: u32 = 0xc6000008;
 
 const VENDOR_HYP_KVM_DEV_REQ_MMIO_FUNC_ID: u32 = 0xc6000012;
-const VENDOR_HYP_KVM_DEV_REQ_DMA_FUNC_ID: u32 = 0xc6000013;
+const VENDOR_HYP_KVM_DEV_REQ_DMA_FUNC_ID: u32 = 0xc600001b;
 
 pub(super) struct RegularKvmHypervisor;
 
diff --git a/vmbase/src/layout.rs b/libs/libvmbase/src/layout.rs
similarity index 96%
rename from vmbase/src/layout.rs
rename to libs/libvmbase/src/layout.rs
index 5ac435f..adcb2fa 100644
--- a/vmbase/src/layout.rs
+++ b/libs/libvmbase/src/layout.rs
@@ -60,11 +60,6 @@
     }};
 }
 
-/// Memory reserved for the DTB.
-pub fn dtb_range() -> Range<VirtualAddress> {
-    linker_region!(dtb_begin, dtb_end)
-}
-
 /// Executable code.
 pub fn text_range() -> Range<VirtualAddress> {
     linker_region!(text_begin, text_end)
diff --git a/vmbase/src/layout/crosvm.rs b/libs/libvmbase/src/layout/crosvm.rs
similarity index 100%
rename from vmbase/src/layout/crosvm.rs
rename to libs/libvmbase/src/layout/crosvm.rs
diff --git a/vmbase/src/lib.rs b/libs/libvmbase/src/lib.rs
similarity index 100%
rename from vmbase/src/lib.rs
rename to libs/libvmbase/src/lib.rs
diff --git a/vmbase/src/linker.rs b/libs/libvmbase/src/linker.rs
similarity index 100%
rename from vmbase/src/linker.rs
rename to libs/libvmbase/src/linker.rs
diff --git a/vmbase/src/logger.rs b/libs/libvmbase/src/logger.rs
similarity index 100%
rename from vmbase/src/logger.rs
rename to libs/libvmbase/src/logger.rs
diff --git a/vmbase/src/memory.rs b/libs/libvmbase/src/memory.rs
similarity index 100%
rename from vmbase/src/memory.rs
rename to libs/libvmbase/src/memory.rs
diff --git a/vmbase/src/memory/dbm.rs b/libs/libvmbase/src/memory/dbm.rs
similarity index 100%
rename from vmbase/src/memory/dbm.rs
rename to libs/libvmbase/src/memory/dbm.rs
diff --git a/vmbase/src/memory/error.rs b/libs/libvmbase/src/memory/error.rs
similarity index 100%
rename from vmbase/src/memory/error.rs
rename to libs/libvmbase/src/memory/error.rs
diff --git a/vmbase/src/memory/page_table.rs b/libs/libvmbase/src/memory/page_table.rs
similarity index 87%
rename from vmbase/src/memory/page_table.rs
rename to libs/libvmbase/src/memory/page_table.rs
index dc346e7..62b52ae 100644
--- a/vmbase/src/memory/page_table.rs
+++ b/libs/libvmbase/src/memory/page_table.rs
@@ -16,23 +16,29 @@
 
 use crate::read_sysreg;
 use aarch64_paging::idmap::IdMap;
-use aarch64_paging::paging::{Attributes, Constraints, Descriptor, MemoryRegion};
+use aarch64_paging::paging::{
+    Attributes, Constraints, Descriptor, MemoryRegion, TranslationRegime,
+};
 use aarch64_paging::MapError;
 use core::result;
 
 /// Software bit used to indicate a device that should be lazily mapped.
 pub(super) const MMIO_LAZY_MAP_FLAG: Attributes = Attributes::SWFLAG_0;
 
-// We assume that:
-// - MAIR_EL1.Attr0 = "Device-nGnRE memory" (0b0000_0100)
-// - MAIR_EL1.Attr1 = "Normal memory, Outer & Inner WB Non-transient, R/W-Allocate" (0b1111_1111)
+/// We assume that MAIR_EL1.Attr0 = "Device-nGnRE memory" (0b0000_0100)
+const DEVICE_NGNRE: Attributes = Attributes::ATTRIBUTE_INDEX_0;
+
+/// We assume that MAIR_EL1.Attr1 = "Normal memory, Outer & Inner WB Non-transient, R/W-Allocate"
+/// (0b1111_1111)
+const NORMAL: Attributes = Attributes::ATTRIBUTE_INDEX_1.union(Attributes::INNER_SHAREABLE);
+
 const MEMORY: Attributes =
-    Attributes::VALID.union(Attributes::NORMAL).union(Attributes::NON_GLOBAL);
+    Attributes::VALID.union(NORMAL).union(Attributes::NON_GLOBAL).union(Attributes::ACCESSED);
 const DEVICE_LAZY: Attributes =
-    MMIO_LAZY_MAP_FLAG.union(Attributes::DEVICE_NGNRE).union(Attributes::EXECUTE_NEVER);
+    MMIO_LAZY_MAP_FLAG.union(DEVICE_NGNRE).union(Attributes::UXN).union(Attributes::ACCESSED);
 const DEVICE: Attributes = DEVICE_LAZY.union(Attributes::VALID);
 const CODE: Attributes = MEMORY.union(Attributes::READ_ONLY);
-const DATA: Attributes = MEMORY.union(Attributes::EXECUTE_NEVER);
+const DATA: Attributes = MEMORY.union(Attributes::UXN);
 const RODATA: Attributes = DATA.union(Attributes::READ_ONLY);
 const DATA_DBM: Attributes = RODATA.union(Attributes::DBM);
 
@@ -64,7 +70,7 @@
         assert_eq!((tcr_el1 >> TCR_EL1_TG0_SHIFT) & TCR_EL1_TG0_MASK, TCR_EL1_TG0_SIZE_4KB);
         assert_eq!((tcr_el1 >> TCR_EL1_T0SZ_SHIFT) & TCR_EL1_T0SZ_MASK, TCR_EL1_T0SZ_39_VA_BITS);
 
-        IdMap::new(Self::ASID, Self::ROOT_LEVEL).into()
+        IdMap::new(Self::ASID, Self::ROOT_LEVEL, TranslationRegime::El1And0).into()
     }
 }
 
diff --git a/vmbase/src/memory/shared.rs b/libs/libvmbase/src/memory/shared.rs
similarity index 100%
rename from vmbase/src/memory/shared.rs
rename to libs/libvmbase/src/memory/shared.rs
diff --git a/vmbase/src/memory/util.rs b/libs/libvmbase/src/memory/util.rs
similarity index 100%
rename from vmbase/src/memory/util.rs
rename to libs/libvmbase/src/memory/util.rs
diff --git a/vmbase/src/power.rs b/libs/libvmbase/src/power.rs
similarity index 100%
rename from vmbase/src/power.rs
rename to libs/libvmbase/src/power.rs
diff --git a/vmbase/src/rand.rs b/libs/libvmbase/src/rand.rs
similarity index 100%
rename from vmbase/src/rand.rs
rename to libs/libvmbase/src/rand.rs
diff --git a/vmbase/src/uart.rs b/libs/libvmbase/src/uart.rs
similarity index 100%
rename from vmbase/src/uart.rs
rename to libs/libvmbase/src/uart.rs
diff --git a/vmbase/src/util.rs b/libs/libvmbase/src/util.rs
similarity index 90%
rename from vmbase/src/util.rs
rename to libs/libvmbase/src/util.rs
index 8c230a1..e52ac8e 100644
--- a/vmbase/src/util.rs
+++ b/libs/libvmbase/src/util.rs
@@ -14,6 +14,7 @@
 
 //! Utility functions.
 
+use aarch64_paging::paging::MemoryRegion;
 use core::ops::Range;
 
 /// Flatten [[T; N]] into &[T]
@@ -91,3 +92,13 @@
         self.start < other.end && other.start < self.end
     }
 }
+
+impl RangeExt for MemoryRegion {
+    fn is_within(&self, other: &Self) -> bool {
+        self.start() >= other.start() && self.end() <= other.end()
+    }
+
+    fn overlaps(&self, other: &Self) -> bool {
+        self.start() < other.end() && other.start() < self.end()
+    }
+}
diff --git a/vmbase/src/virtio.rs b/libs/libvmbase/src/virtio.rs
similarity index 100%
rename from vmbase/src/virtio.rs
rename to libs/libvmbase/src/virtio.rs
diff --git a/vmbase/src/virtio/hal.rs b/libs/libvmbase/src/virtio/hal.rs
similarity index 100%
rename from vmbase/src/virtio/hal.rs
rename to libs/libvmbase/src/virtio/hal.rs
diff --git a/vmbase/src/virtio/pci.rs b/libs/libvmbase/src/virtio/pci.rs
similarity index 100%
rename from vmbase/src/virtio/pci.rs
rename to libs/libvmbase/src/virtio/pci.rs
diff --git a/vmclient/Android.bp b/libs/libvmclient/Android.bp
similarity index 100%
rename from vmclient/Android.bp
rename to libs/libvmclient/Android.bp
diff --git a/vmclient/src/death_reason.rs b/libs/libvmclient/src/death_reason.rs
similarity index 100%
rename from vmclient/src/death_reason.rs
rename to libs/libvmclient/src/death_reason.rs
diff --git a/vmclient/src/error_code.rs b/libs/libvmclient/src/error_code.rs
similarity index 100%
rename from vmclient/src/error_code.rs
rename to libs/libvmclient/src/error_code.rs
diff --git a/vmclient/src/errors.rs b/libs/libvmclient/src/errors.rs
similarity index 100%
rename from vmclient/src/errors.rs
rename to libs/libvmclient/src/errors.rs
diff --git a/vmclient/src/lib.rs b/libs/libvmclient/src/lib.rs
similarity index 100%
rename from vmclient/src/lib.rs
rename to libs/libvmclient/src/lib.rs
diff --git a/vmclient/src/sync.rs b/libs/libvmclient/src/sync.rs
similarity index 100%
rename from vmclient/src/sync.rs
rename to libs/libvmclient/src/sync.rs
diff --git a/compos/service/Android.bp b/libs/service-compos/Android.bp
similarity index 100%
rename from compos/service/Android.bp
rename to libs/service-compos/Android.bp
diff --git a/compos/service/java/com/android/server/compos/IsolatedCompilationJobService.java b/libs/service-compos/java/com/android/server/compos/IsolatedCompilationJobService.java
similarity index 89%
rename from compos/service/java/com/android/server/compos/IsolatedCompilationJobService.java
rename to libs/service-compos/java/com/android/server/compos/IsolatedCompilationJobService.java
index 933ac7a..adc0300 100644
--- a/compos/service/java/com/android/server/compos/IsolatedCompilationJobService.java
+++ b/libs/service-compos/java/com/android/server/compos/IsolatedCompilationJobService.java
@@ -52,14 +52,16 @@
         ComponentName serviceName =
                 new ComponentName("android", IsolatedCompilationJobService.class.getName());
 
-        int result = scheduler.schedule(new JobInfo.Builder(STAGED_APEX_JOB_ID, serviceName)
-                // Wait in case more APEXes are staged
-                .setMinimumLatency(TimeUnit.MINUTES.toMillis(60))
-                // We consume CPU, power, and storage
-                .setRequiresDeviceIdle(true)
-                .setRequiresCharging(true)
-                .setRequiresStorageNotLow(true)
-                .build());
+        int result =
+                scheduler.schedule(
+                        new JobInfo.Builder(STAGED_APEX_JOB_ID, serviceName)
+                                // Wait in case more APEXes are staged
+                                .setMinimumLatency(TimeUnit.MINUTES.toMillis(60))
+                                // We consume CPU, power, and storage
+                                .setRequiresDeviceIdle(true)
+                                .setRequiresCharging(true)
+                                .setRequiresStorageNotLow(true)
+                                .build());
         if (result == JobScheduler.RESULT_SUCCESS) {
             IsolatedCompilationMetrics.onCompilationScheduled(
                     IsolatedCompilationMetrics.SCHEDULING_SUCCESS);
@@ -86,13 +88,14 @@
         if (oldJob != null) {
             // We're already running a job, give up on this one
             Log.w(TAG, "Another job is in progress, skipping");
-            return false;  // Already finished
+            return false; // Already finished
         }
 
         IsolatedCompilationMetrics metrics = new IsolatedCompilationMetrics();
 
-        CompilationJob newJob = new CompilationJob(IsolatedCompilationJobService.this::onCompletion,
-                params, metrics);
+        CompilationJob newJob =
+                new CompilationJob(
+                        IsolatedCompilationJobService.this::onCompletion, params, metrics);
         mCurrentJob.set(newJob);
 
         // This can take some time - we need to start up a VM - so we do it on a separate
@@ -108,7 +111,7 @@
                     metrics.onCompilationEnded(IsolatedCompilationMetrics.RESULT_FAILED_TO_START);
                     mCurrentJob.set(null);
                     newJob.stop(); // Just in case it managed to start before failure
-                    jobFinished(params, /*wantReschedule=*/ false);
+                    jobFinished(params, /* wantReschedule= */ false);
                 }
             }
         }.start();
@@ -137,7 +140,7 @@
         // On success we don't need to reschedule.
         // On failure we could reschedule, but that could just use a lot of resources and still
         // fail; instead we just let odsign do compilation on reboot if necessary.
-        jobFinished(params, /*wantReschedule=*/ false);
+        jobFinished(params, /* wantReschedule= */ false);
     }
 
     interface CompilationCallback {
@@ -152,7 +155,9 @@
         private final JobParameters mParams;
         private volatile boolean mStopRequested = false;
 
-        CompilationJob(CompilationCallback callback, JobParameters params,
+        CompilationJob(
+                CompilationCallback callback,
+                JobParameters params,
                 IsolatedCompilationMetrics metrics) {
             mCallback = requireNonNull(callback);
             mParams = params;
diff --git a/compos/service/java/com/android/server/compos/IsolatedCompilationMetrics.java b/libs/service-compos/java/com/android/server/compos/IsolatedCompilationMetrics.java
similarity index 77%
rename from compos/service/java/com/android/server/compos/IsolatedCompilationMetrics.java
rename to libs/service-compos/java/com/android/server/compos/IsolatedCompilationMetrics.java
index f7799a4..0419a55 100644
--- a/compos/service/java/com/android/server/compos/IsolatedCompilationMetrics.java
+++ b/libs/service-compos/java/com/android/server/compos/IsolatedCompilationMetrics.java
@@ -61,10 +61,11 @@
             ArtStatsLog.ISOLATED_COMPILATION_ENDED__COMPILATION_RESULT__RESULT_FAILED_TO_START;
     public static final int RESULT_JOB_CANCELED =
             ArtStatsLog.ISOLATED_COMPILATION_ENDED__COMPILATION_RESULT__RESULT_JOB_CANCELED;
-    public static final int RESULT_COMPILATION_FAILED = ArtStatsLog
-            .ISOLATED_COMPILATION_ENDED__COMPILATION_RESULT__RESULT_COMPILATION_FAILED;
-    public static final int RESULT_UNEXPECTED_COMPILATION_RESULT = ArtStatsLog
-            .ISOLATED_COMPILATION_ENDED__COMPILATION_RESULT__RESULT_UNEXPECTED_COMPILATION_RESULT;
+    public static final int RESULT_COMPILATION_FAILED =
+            ArtStatsLog.ISOLATED_COMPILATION_ENDED__COMPILATION_RESULT__RESULT_COMPILATION_FAILED;
+    public static final int RESULT_UNEXPECTED_COMPILATION_RESULT =
+            ArtStatsLog
+                    .ISOLATED_COMPILATION_ENDED__COMPILATION_RESULT__RESULT_UNEXPECTED_COMPILATION_RESULT;
     public static final int RESULT_COMPOSD_DIED =
             ArtStatsLog.ISOLATED_COMPILATION_ENDED__COMPILATION_RESULT__RESULT_COMPOSD_DIED;
     public static final int RESULT_FAILED_TO_ENABLE_FSVERITY =
@@ -78,8 +79,9 @@
     // Keep this in sync with Result enum in IsolatedCompilationScheduled in
     // frameworks/proto_logging/stats/atoms.proto
 
-    public static final int SCHEDULING_RESULT_UNKNOWN = ArtStatsLog
-            .ISOLATED_COMPILATION_SCHEDULED__SCHEDULING_RESULT__SCHEDULING_RESULT_UNKNOWN;
+    public static final int SCHEDULING_RESULT_UNKNOWN =
+            ArtStatsLog
+                    .ISOLATED_COMPILATION_SCHEDULED__SCHEDULING_RESULT__SCHEDULING_RESULT_UNKNOWN;
     public static final int SCHEDULING_FAILURE =
             ArtStatsLog.ISOLATED_COMPILATION_SCHEDULED__SCHEDULING_RESULT__SCHEDULING_FAILURE;
     public static final int SCHEDULING_SUCCESS =
@@ -104,16 +106,24 @@
         statsLogPostCompilation(result, JobParameters.STOP_REASON_UNDEFINED);
     }
 
-    private void statsLogPostCompilation(@CompilationResult int result,
-                @JobParameters.StopReason int jobStopReason) {
+    private void statsLogPostCompilation(
+            @CompilationResult int result, @JobParameters.StopReason int jobStopReason) {
 
-        long compilationTime = mCompilationStartTimeMs == 0 ? -1
-                : SystemClock.elapsedRealtime() - mCompilationStartTimeMs;
+        long compilationTime =
+                mCompilationStartTimeMs == 0
+                        ? -1
+                        : SystemClock.elapsedRealtime() - mCompilationStartTimeMs;
         mCompilationStartTimeMs = 0;
 
-        ArtStatsLog.write(ArtStatsLog.ISOLATED_COMPILATION_ENDED, compilationTime,
-                result, jobStopReason);
-        Log.i(TAG, "ISOLATED_COMPILATION_ENDED: " + result + ", " + compilationTime
-                + ", " + jobStopReason);
+        ArtStatsLog.write(
+                ArtStatsLog.ISOLATED_COMPILATION_ENDED, compilationTime, result, jobStopReason);
+        Log.i(
+                TAG,
+                "ISOLATED_COMPILATION_ENDED: "
+                        + result
+                        + ", "
+                        + compilationTime
+                        + ", "
+                        + jobStopReason);
     }
 }
diff --git a/compos/service/java/com/android/server/compos/IsolatedCompilationService.java b/libs/service-compos/java/com/android/server/compos/IsolatedCompilationService.java
similarity index 94%
rename from compos/service/java/com/android/server/compos/IsolatedCompilationService.java
rename to libs/service-compos/java/com/android/server/compos/IsolatedCompilationService.java
index b2fcbe0..95e365d 100644
--- a/compos/service/java/com/android/server/compos/IsolatedCompilationService.java
+++ b/libs/service-compos/java/com/android/server/compos/IsolatedCompilationService.java
@@ -60,7 +60,6 @@
             return;
         }
 
-
         JobScheduler scheduler = getContext().getSystemService(JobScheduler.class);
         if (scheduler == null) {
             Log.e(TAG, "No scheduler");
@@ -92,8 +91,9 @@
         private final IPackageManagerNative mPackageNative;
 
         static void registerForStagedApexUpdates(JobScheduler scheduler) {
-            final IPackageManagerNative packageNative = IPackageManagerNative.Stub.asInterface(
-                    ServiceManager.getService("package_native"));
+            final IPackageManagerNative packageNative =
+                    IPackageManagerNative.Stub.asInterface(
+                            ServiceManager.getService("package_native"));
             if (packageNative == null) {
                 Log.e(TAG, "No IPackageManagerNative");
                 return;
@@ -110,8 +110,7 @@
             }
         }
 
-        private StagedApexObserver(JobScheduler scheduler,
-                IPackageManagerNative packageNative) {
+        private StagedApexObserver(JobScheduler scheduler, IPackageManagerNative packageNative) {
             mScheduler = scheduler;
             mPackageNative = packageNative;
         }
diff --git a/java/service/Android.bp b/libs/service-virtualization/Android.bp
similarity index 100%
rename from java/service/Android.bp
rename to libs/service-virtualization/Android.bp
diff --git a/java/service/src/com/android/system/virtualmachine/SecretkeeperJobService.java b/libs/service-virtualization/src/com/android/system/virtualmachine/SecretkeeperJobService.java
similarity index 98%
rename from java/service/src/com/android/system/virtualmachine/SecretkeeperJobService.java
rename to libs/service-virtualization/src/com/android/system/virtualmachine/SecretkeeperJobService.java
index 473fbfb..4899dcb 100644
--- a/java/service/src/com/android/system/virtualmachine/SecretkeeperJobService.java
+++ b/libs/service-virtualization/src/com/android/system/virtualmachine/SecretkeeperJobService.java
@@ -95,7 +95,7 @@
                 }
                 sJob.set(null);
                 // We don't reschedule on error, we will try again the next day anyway.
-                jobFinished(params, /*wantReschedule=*/ false);
+                jobFinished(params, /* wantReschedule= */ false);
             }
         }.start();
 
diff --git a/java/service/src/com/android/system/virtualmachine/VirtualizationSystemService.java b/libs/service-virtualization/src/com/android/system/virtualmachine/VirtualizationSystemService.java
similarity index 100%
rename from java/service/src/com/android/system/virtualmachine/VirtualizationSystemService.java
rename to libs/service-virtualization/src/com/android/system/virtualmachine/VirtualizationSystemService.java
diff --git a/libs/vm_launcher_lib/Android.bp b/libs/vm_launcher_lib/Android.bp
new file mode 100644
index 0000000..8591c8d
--- /dev/null
+++ b/libs/vm_launcher_lib/Android.bp
@@ -0,0 +1,13 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+java_library {
+    name: "vm_launcher_lib",
+    srcs: ["java/**/*.java"],
+    apex_available: [
+        "//apex_available:platform",
+        "com.android.virt",
+    ],
+    sdk_version: "system_current",
+}
diff --git a/libs/vm_launcher_lib/java/com/android/virtualization/vmlauncher/VmLauncherServices.java b/libs/vm_launcher_lib/java/com/android/virtualization/vmlauncher/VmLauncherServices.java
new file mode 100644
index 0000000..c5bc5fb
--- /dev/null
+++ b/libs/vm_launcher_lib/java/com/android/virtualization/vmlauncher/VmLauncherServices.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.virtualization.vmlauncher;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Parcel;
+import android.os.ResultReceiver;
+import android.util.Log;
+
+import java.util.List;
+
+public class VmLauncherServices {
+    private static final String TAG = "VmLauncherServices";
+
+    private static final String ACTION_START_VM_LAUNCHER_SERVICE =
+            "android.virtualization.START_VM_LAUNCHER_SERVICE";
+
+    private static final int RESULT_START = 0;
+    private static final int RESULT_STOP = 1;
+    private static final int RESULT_ERROR = 2;
+    private static final int RESULT_IPADDR = 3;
+    private static final String KEY_VM_IP_ADDR = "ip_addr";
+
+    private static Intent buildVmLauncherServiceIntent(Context context) {
+        Intent i = new Intent();
+        i.setAction(ACTION_START_VM_LAUNCHER_SERVICE);
+
+        Intent intent = new Intent(ACTION_START_VM_LAUNCHER_SERVICE);
+        PackageManager pm = context.getPackageManager();
+        List<ResolveInfo> resolveInfos =
+                pm.queryIntentServices(intent, PackageManager.MATCH_DEFAULT_ONLY);
+        if (resolveInfos == null || resolveInfos.size() != 1) {
+            Log.e(TAG, "cannot find a service to handle ACTION_START_VM_LAUNCHER_SERVICE");
+            return null;
+        }
+        String packageName = resolveInfos.get(0).serviceInfo.packageName;
+
+        i.setPackage(packageName);
+        return i;
+    }
+
+    public static void startVmLauncherService(Context context, VmLauncherServiceCallback callback) {
+        Intent i = buildVmLauncherServiceIntent(context);
+        if (i == null) {
+            return;
+        }
+        ResultReceiver resultReceiver =
+                new ResultReceiver(new Handler(Looper.myLooper())) {
+                    @Override
+                    protected void onReceiveResult(int resultCode, Bundle resultData) {
+                        if (callback == null) {
+                            return;
+                        }
+                        switch (resultCode) {
+                            case RESULT_START:
+                                callback.onVmStart();
+                                return;
+                            case RESULT_STOP:
+                                callback.onVmStop();
+                                return;
+                            case RESULT_ERROR:
+                                callback.onVmError();
+                                return;
+                            case RESULT_IPADDR:
+                                callback.onIpAddrAvailable(resultData.getString(KEY_VM_IP_ADDR));
+                                return;
+                        }
+                    }
+                };
+        i.putExtra(Intent.EXTRA_RESULT_RECEIVER, getResultReceiverForIntent(resultReceiver));
+        context.startForegroundService(i);
+    }
+
+    public interface VmLauncherServiceCallback {
+        void onVmStart();
+
+        void onVmStop();
+
+        void onVmError();
+
+        void onIpAddrAvailable(String ipAddr);
+    }
+
+    private static ResultReceiver getResultReceiverForIntent(ResultReceiver r) {
+        Parcel parcel = Parcel.obtain();
+        r.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        r = ResultReceiver.CREATOR.createFromParcel(parcel);
+        parcel.recycle();
+        return r;
+    }
+}
diff --git a/microdroid/kernel/Android.bp b/microdroid/kernel/Android.bp
deleted file mode 100644
index 7a8af90..0000000
--- a/microdroid/kernel/Android.bp
+++ /dev/null
@@ -1,91 +0,0 @@
-// Copyright (C) 2024 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package {
-    default_applicable_licenses: ["microdroid_kernel_prebuilts_license"],
-}
-
-// See: http://go/android-license-faq
-license {
-    name: "microdroid_kernel_prebuilts_license",
-    visibility: [":__subpackages__"],
-    license_kinds: [
-        "SPDX-license-identifier-GPL-2.0-only",
-    ],
-    // large-scale-change unable to identify any license_text files
-}
-
-soong_config_module_type {
-    name: "avf_microdroid_kernel_prebuilt",
-    module_type: "filegroup",
-    config_namespace: "ANDROID",
-    variables: [
-        "release_avf_microdroid_kernel_version",
-    ],
-    properties: [
-        "srcs",
-    ],
-}
-
-soong_config_string_variable {
-    name: "release_avf_microdroid_kernel_version",
-    values: [
-        "android14_61",
-        "android15_66",
-    ],
-}
-
-avf_microdroid_kernel_prebuilt {
-    name: "microdroid_kernel_prebuilt-arm64",
-    // Below are properties that are conditionally set depending on value of build flags.
-    soong_config_variables: {
-        release_avf_microdroid_kernel_version: {
-            android14_61: {
-                srcs: ["android14-6.1/arm64/kernel-6.1"],
-            },
-            android15_66: {
-                srcs: ["android15-6.6/arm64/kernel-6.6"],
-            },
-            // In case release configuration doesn't specify value of the
-            // RELEASE_AVF_MICRODROID_KERNEL_VERSION fallback to the kernel we
-            // already released.
-            // TODO(b/298011555): remove this once we set the flag in all release configs.
-            conditions_default: {
-                srcs: ["android14-6.1/arm64/kernel-6.1"],
-            },
-        },
-    },
-}
-
-avf_microdroid_kernel_prebuilt {
-    name: "microdroid_kernel_prebuilt-x86_64",
-    // Below are properties that are conditionally set depending on value of build flags.
-    soong_config_variables: {
-        release_avf_microdroid_kernel_version: {
-            android14_61: {
-                srcs: ["android14-6.1/x86_64/kernel-6.1"],
-            },
-            android15_66: {
-                srcs: ["android15-6.6/x86_64/kernel-6.6"],
-            },
-            // In case release configuration doesn't specify value of the
-            // RELEASE_AVF_MICRODROID_KERNEL_VERSION fallback to the kernel we
-            // already released.
-            // TODO(b/298011555): remove this once we set the flag in all release configs.
-            conditions_default: {
-                srcs: ["android14-6.1/x86_64/kernel-6.1"],
-            },
-        },
-    },
-}
diff --git a/compos/benchmark/Android.bp b/tests/ComposBenchmarkApp/Android.bp
similarity index 100%
rename from compos/benchmark/Android.bp
rename to tests/ComposBenchmarkApp/Android.bp
diff --git a/compos/benchmark/AndroidManifest.xml b/tests/ComposBenchmarkApp/AndroidManifest.xml
similarity index 100%
rename from compos/benchmark/AndroidManifest.xml
rename to tests/ComposBenchmarkApp/AndroidManifest.xml
diff --git a/compos/benchmark/AndroidTest.xml b/tests/ComposBenchmarkApp/AndroidTest.xml
similarity index 100%
rename from compos/benchmark/AndroidTest.xml
rename to tests/ComposBenchmarkApp/AndroidTest.xml
diff --git a/compos/benchmark/src/java/com/android/compos/benchmark/ComposBenchmark.java b/tests/ComposBenchmarkApp/src/java/com/android/compos/benchmark/ComposBenchmark.java
similarity index 100%
rename from compos/benchmark/src/java/com/android/compos/benchmark/ComposBenchmark.java
rename to tests/ComposBenchmarkApp/src/java/com/android/compos/benchmark/ComposBenchmark.java
diff --git a/compos/tests/Android.bp b/tests/ComposHostTestCases/Android.bp
similarity index 100%
rename from compos/tests/Android.bp
rename to tests/ComposHostTestCases/Android.bp
diff --git a/compos/tests/AndroidTest.xml b/tests/ComposHostTestCases/AndroidTest.xml
similarity index 100%
rename from compos/tests/AndroidTest.xml
rename to tests/ComposHostTestCases/AndroidTest.xml
diff --git a/compos/tests/java/android/compos/test/ComposTestCase.java b/tests/ComposHostTestCases/java/android/compos/test/ComposTestCase.java
similarity index 90%
rename from compos/tests/java/android/compos/test/ComposTestCase.java
rename to tests/ComposHostTestCases/java/android/compos/test/ComposTestCase.java
index b31f4f3..7a35829 100644
--- a/compos/tests/java/android/compos/test/ComposTestCase.java
+++ b/tests/ComposHostTestCases/java/android/compos/test/ComposTestCase.java
@@ -54,8 +54,7 @@
     // Binaries used in test. (These paths are valid both in host and Microdroid.)
     private static final String ODREFRESH_BIN = "/apex/com.android.art/bin/odrefresh";
     private static final String COMPOSD_CMD_BIN = "/apex/com.android.compos/bin/composd_cmd";
-    private static final String COMPOS_VERIFY_BIN =
-            "/apex/com.android.compos/bin/compos_verify";
+    private static final String COMPOS_VERIFY_BIN = "/apex/com.android.compos/bin/compos_verify";
 
     private static final String COMPOS_APEXDATA_DIR = "/data/misc/apexdata/com.android.compos";
 
@@ -111,10 +110,13 @@
         android.tryRun("rm", "-rf", ODREFRESH_OUTPUT_DIR);
 
         if (mBackupSystemServerCompilerFilter != null) {
-            CLog.d("Restore dalvik.vm.systemservercompilerfilter to "
-                    + mBackupSystemServerCompilerFilter);
-            getDevice().setProperty(SYSTEM_SERVER_COMPILER_FILTER_PROP_NAME,
-                    mBackupSystemServerCompilerFilter);
+            CLog.d(
+                    "Restore dalvik.vm.systemservercompilerfilter to "
+                            + mBackupSystemServerCompilerFilter);
+            getDevice()
+                    .setProperty(
+                            SYSTEM_SERVER_COMPILER_FILTER_PROP_NAME,
+                            mBackupSystemServerCompilerFilter);
         }
     }
 
@@ -143,8 +145,8 @@
         }
 
         // Save the expected checksum for the output directory.
-        String expectedChecksumSnapshot = checksumDirectoryContentPartial(android,
-                ODREFRESH_OUTPUT_DIR);
+        String expectedChecksumSnapshot =
+                checksumDirectoryContentPartial(android, ODREFRESH_OUTPUT_DIR);
 
         // --check may delete the output.
         CommandResult result = runOdrefresh(android, "--check");
@@ -166,8 +168,8 @@
         assertVmBccIsValid();
 
         // Save the actual checksum for the output directory.
-        String actualChecksumSnapshot = checksumDirectoryContentPartial(android,
-                ODREFRESH_OUTPUT_DIR);
+        String actualChecksumSnapshot =
+                checksumDirectoryContentPartial(android, ODREFRESH_OUTPUT_DIR);
 
         // Expect the output of Comp OS to be the same as compiled on Android.
         assertThat(actualChecksumSnapshot).isEqualTo(expectedChecksumSnapshot);
@@ -185,11 +187,12 @@
         assertThat(bcc_file).isNotNull();
 
         // Add the BCC to test artifacts, in case it is ill-formed or otherwise interesting.
-        mTestLogs.addTestLog(bcc_file.getPath(), LogDataType.UNKNOWN,
-                new FileInputStreamSource(bcc_file));
+        mTestLogs.addTestLog(
+                bcc_file.getPath(), LogDataType.UNKNOWN, new FileInputStreamSource(bcc_file));
 
         // Find the validator binary - note that it's specified as a dependency in our Android.bp.
-        File validator = getTestInformation().getDependencyFile("hwtrust", /*targetFirst=*/ false);
+        File validator =
+                getTestInformation().getDependencyFile("hwtrust", /* targetFirst= */ false);
 
         CommandResult result =
                 new RunUtil()
diff --git a/authfs/Android.bp b/tests/authfs/Android.bp
similarity index 60%
rename from authfs/Android.bp
rename to tests/authfs/Android.bp
index e04d5e1..aa814eb 100644
--- a/authfs/Android.bp
+++ b/tests/authfs/Android.bp
@@ -1,51 +1,3 @@
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-rust_defaults {
-    name: "authfs_defaults",
-    crate_name: "authfs",
-    srcs: [
-        "src/main.rs",
-    ],
-    edition: "2021",
-    rustlibs: [
-        "authfs_aidl_interface-rust",
-        "libandroid_logger",
-        "libanyhow",
-        "libauthfs_fsverity_metadata",
-        "libbinder_rs",
-        "libcfg_if",
-        "libclap",
-        "libfsverity_digests_proto_rust",
-        "libfuse_rust",
-        "libhex",
-        "liblibc",
-        "liblog_rust",
-        "libnix",
-        "libopenssl",
-        "libprotobuf",
-        "librpcbinder_rs",
-        "libthiserror",
-    ],
-    prefer_rlib: true,
-    target: {
-        darwin: {
-            enabled: false,
-        },
-    },
-    defaults: [
-        "crosvm_defaults",
-        "avf_build_flags_rust",
-    ],
-}
-
-rust_binary {
-    name: "authfs",
-    defaults: ["authfs_defaults"],
-    apex_available: ["com.android.virt"],
-}
-
 rust_test {
     name: "authfs_device_test_src_lib",
     defaults: ["authfs_defaults"],
diff --git a/authfs/TEST_MAPPING b/tests/authfs/TEST_MAPPING
similarity index 100%
copy from authfs/TEST_MAPPING
copy to tests/authfs/TEST_MAPPING
diff --git a/authfs/tests/benchmarks/Android.bp b/tests/authfs/benchmarks/Android.bp
similarity index 100%
rename from authfs/tests/benchmarks/Android.bp
rename to tests/authfs/benchmarks/Android.bp
diff --git a/authfs/tests/benchmarks/AndroidTest.xml b/tests/authfs/benchmarks/AndroidTest.xml
similarity index 100%
rename from authfs/tests/benchmarks/AndroidTest.xml
rename to tests/authfs/benchmarks/AndroidTest.xml
diff --git a/authfs/tests/benchmarks/src/java/com/android/fs/benchmarks/AuthFsBenchmarks.java b/tests/authfs/benchmarks/src/java/com/android/fs/benchmarks/AuthFsBenchmarks.java
similarity index 99%
rename from authfs/tests/benchmarks/src/java/com/android/fs/benchmarks/AuthFsBenchmarks.java
rename to tests/authfs/benchmarks/src/java/com/android/fs/benchmarks/AuthFsBenchmarks.java
index 085d06e..abda3e3 100644
--- a/authfs/tests/benchmarks/src/java/com/android/fs/benchmarks/AuthFsBenchmarks.java
+++ b/tests/authfs/benchmarks/src/java/com/android/fs/benchmarks/AuthFsBenchmarks.java
@@ -18,7 +18,6 @@
 
 import static com.android.tradefed.testtype.DeviceJUnit4ClassRunner.TestMetrics;
 
-
 import static org.junit.Assume.assumeFalse;
 import static org.junit.Assume.assumeTrue;
 
diff --git a/authfs/tests/benchmarks/src/measure_io.cpp b/tests/authfs/benchmarks/src/measure_io.cpp
similarity index 100%
rename from authfs/tests/benchmarks/src/measure_io.cpp
rename to tests/authfs/benchmarks/src/measure_io.cpp
diff --git a/authfs/tests/common/Android.bp b/tests/authfs/common/Android.bp
similarity index 100%
rename from authfs/tests/common/Android.bp
rename to tests/authfs/common/Android.bp
diff --git a/authfs/tests/common/src/java/com/android/fs/common/AuthFsTestRule.java b/tests/authfs/common/src/java/com/android/fs/common/AuthFsTestRule.java
similarity index 100%
rename from authfs/tests/common/src/java/com/android/fs/common/AuthFsTestRule.java
rename to tests/authfs/common/src/java/com/android/fs/common/AuthFsTestRule.java
diff --git a/authfs/tests/common/src/open_then_run.rs b/tests/authfs/common/src/open_then_run.rs
similarity index 100%
rename from authfs/tests/common/src/open_then_run.rs
rename to tests/authfs/common/src/open_then_run.rs
diff --git a/authfs/tests/hosttests/Android.bp b/tests/authfs/hosttests/Android.bp
similarity index 100%
rename from authfs/tests/hosttests/Android.bp
rename to tests/authfs/hosttests/Android.bp
diff --git a/authfs/tests/hosttests/AndroidTest.xml b/tests/authfs/hosttests/AndroidTest.xml
similarity index 100%
rename from authfs/tests/hosttests/AndroidTest.xml
rename to tests/authfs/hosttests/AndroidTest.xml
diff --git a/authfs/tests/hosttests/java/src/com/android/fs/AuthFsHostTest.java b/tests/authfs/hosttests/java/src/com/android/fs/AuthFsHostTest.java
similarity index 87%
rename from authfs/tests/hosttests/java/src/com/android/fs/AuthFsHostTest.java
rename to tests/authfs/hosttests/java/src/com/android/fs/AuthFsHostTest.java
index d0a7c66..0a27506 100644
--- a/authfs/tests/hosttests/java/src/com/android/fs/AuthFsHostTest.java
+++ b/tests/authfs/hosttests/java/src/com/android/fs/AuthFsHostTest.java
@@ -75,8 +75,8 @@
     @BeforeClassWithInfo
     public static void beforeClassWithDevice(TestInformation testInfo) throws Exception {
         AuthFsTestRule.setUpAndroid(testInfo);
-        assumeTrue(AuthFsTestRule.getDevice().supportsMicrodroid(/*protectedVm=*/ true));
-        AuthFsTestRule.startMicrodroid(/*protectedVm=*/ true);
+        assumeTrue(AuthFsTestRule.getDevice().supportsMicrodroid(/* protectedVm= */ true));
+        AuthFsTestRule.startMicrodroid(/* protectedVm= */ true);
         sAndroid = AuthFsTestRule.getAndroid();
         sMicrodroid = AuthFsTestRule.getMicrodroid();
     }
@@ -114,7 +114,7 @@
         // Setup
         runFdServerOnAndroid(
                 "--open-ro 3:input.4k --open-ro 4:input.4k.fsv_meta --open-ro"
-                    + " 6:input.4k1 --open-ro 7:input.4k1.fsv_meta",
+                        + " 6:input.4k1 --open-ro 7:input.4k1.fsv_meta",
                 "--ro-fds 3:4 --ro-fds 6:7");
         runAuthFsOnMicrodroid(
                 "--remote-ro-file 3:" + DIGEST_4K + " --remote-ro-file 6:" + DIGEST_4K1);
@@ -135,8 +135,7 @@
     public void testReadWithFsverityVerification_TamperedMerkleTree() throws Exception {
         // Setup
         runFdServerOnAndroid(
-                "--open-ro 3:input.4m --open-ro 4:input.4m.fsv_meta.bad_merkle",
-                "--ro-fds 3:4");
+                "--open-ro 3:input.4m --open-ro 4:input.4m.fsv_meta.bad_merkle", "--ro-fds 3:4");
         runAuthFsOnMicrodroid("--remote-ro-file 3:" + DIGEST_4M);
 
         // Verify
@@ -190,8 +189,12 @@
         // Action
         // Tampering with the first 2 4K-blocks of the backing file.
         assertThat(
-                writeZerosAtFileOffset(sAndroid, backendPath,
-                        /* offset */ 0, /* number */ 8192, /* writeThrough */ false))
+                        writeZerosAtFileOffset(
+                                sAndroid,
+                                backendPath,
+                                /* offset */ 0, /* number */
+                                8192, /* writeThrough */
+                                false))
                 .isSuccess();
 
         // Verify
@@ -199,21 +202,33 @@
         // when the content is inconsistent to the known hash. Use direct I/O to avoid simply
         // writing to the filesystem cache.
         assertThat(
-                writeZerosAtFileOffset(sMicrodroid, destPath,
-                        /* offset */ 0, /* number */ 1024, /* writeThrough */ true))
+                        writeZerosAtFileOffset(
+                                sMicrodroid,
+                                destPath,
+                                /* offset */ 0, /* number */
+                                1024, /* writeThrough */
+                                true))
                 .isFailed();
 
         // A full 4K write does not require to read back, so write can succeed even if the backing
         // block has already been tampered.
         assertThat(
-                writeZerosAtFileOffset(sMicrodroid, destPath,
-                        /* offset */ 4096, /* number */ 4096, /* writeThrough */ false))
+                        writeZerosAtFileOffset(
+                                sMicrodroid,
+                                destPath,
+                                /* offset */ 4096, /* number */
+                                4096, /* writeThrough */
+                                false))
                 .isSuccess();
 
         // Otherwise, a partial write with correct backing file should still succeed.
         assertThat(
-                writeZerosAtFileOffset(sMicrodroid, destPath,
-                        /* offset */ 8192, /* number */ 1024, /* writeThrough */ false))
+                        writeZerosAtFileOffset(
+                                sMicrodroid,
+                                destPath,
+                                /* offset */ 8192, /* number */
+                                1024, /* writeThrough */
+                                false))
                 .isSuccess();
     }
 
@@ -231,8 +246,12 @@
         // Action
         // Tampering with the first 4K-block of the backing file.
         assertThat(
-                writeZerosAtFileOffset(sAndroid, backendPath,
-                        /* offset */ 0, /* number */ 4096, /* writeThrough */ false))
+                        writeZerosAtFileOffset(
+                                sAndroid,
+                                backendPath,
+                                /* offset */ 0, /* number */
+                                4096, /* writeThrough */
+                                false))
                 .isSuccess();
 
         // Verify
@@ -258,8 +277,12 @@
         // Action
         // Tampering with the last 4K-block of the backing file.
         assertThat(
-                writeZerosAtFileOffset(sAndroid, backendPath,
-                        /* offset */ 4096, /* number */ 1, /* writeThrough */ false))
+                        writeZerosAtFileOffset(
+                                sAndroid,
+                                backendPath,
+                                /* offset */ 4096, /* number */
+                                1, /* writeThrough */
+                                false))
                 .isSuccess();
 
         // Verify
@@ -470,15 +493,22 @@
         sAndroid.run("test -f " + androidOutputPath);
 
         // Action
-        String output = sMicrodroid.run(
-                // Open the file for append and read
-                "exec 4>>" + outputPath + " 5<" + outputPath + "; "
-                // Delete the file from the directory
-                + "rm " + outputPath + "; "
-                // Append more data to the file descriptor
-                + "echo -n 456 >&4; "
-                // Print the whole file from the file descriptor
-                + "cat <&5");
+        String output =
+                sMicrodroid.run(
+                        // Open the file for append and read
+                        "exec 4>>"
+                                + outputPath
+                                + " 5<"
+                                + outputPath
+                                + "; "
+                                // Delete the file from the directory
+                                + "rm "
+                                + outputPath
+                                + "; "
+                                // Append more data to the file descriptor
+                                + "echo -n 456 >&4; "
+                                // Print the whole file from the file descriptor
+                                + "cat <&5");
 
         // Verify
         // Output contains all written data, while the files are deleted.
@@ -531,27 +561,33 @@
 
         // Verify
         String[] actual = sMicrodroid.run("cd " + authfsOutputDir + "; find |sort").split("\n");
-        String[] expected = new String[] {
-                ".",
-                "./dir",
-                "./dir/dir2",
-                "./dir/dir2/dir3",
-                "./dir/dir2/dir3/file1",
-                "./dir/dir2/dir3/file2",
-                "./dir/dir2/dir3/file3",
-                "./file"};
+        String[] expected =
+                new String[] {
+                    ".",
+                    "./dir",
+                    "./dir/dir2",
+                    "./dir/dir2/dir3",
+                    "./dir/dir2/dir3/file1",
+                    "./dir/dir2/dir3/file2",
+                    "./dir/dir2/dir3/file3",
+                    "./file"
+                };
         assertEquals(expected, actual);
 
         // Add more entries.
         sMicrodroid.run("mkdir -p " + authfsOutputDir + "/dir2");
         sMicrodroid.run("touch " + authfsOutputDir + "/file2");
         // Check new entries. Also check that the types are correct.
-        actual = sMicrodroid.run(
-                "cd " + authfsOutputDir + "; find -maxdepth 1 -type f |sort").split("\n");
+        actual =
+                sMicrodroid
+                        .run("cd " + authfsOutputDir + "; find -maxdepth 1 -type f |sort")
+                        .split("\n");
         expected = new String[] {"./file", "./file2"};
         assertEquals(expected, actual);
-        actual = sMicrodroid.run(
-                "cd " + authfsOutputDir + "; find -maxdepth 1 -type d |sort").split("\n");
+        actual =
+                sMicrodroid
+                        .run("cd " + authfsOutputDir + "; find -maxdepth 1 -type d |sort")
+                        .split("\n");
         expected = new String[] {".", "./dir", "./dir2"};
         assertEquals(expected, actual);
     }
@@ -687,8 +723,9 @@
                 "yes $'\\x01' | tr -d '\\n' | dd bs=1 count=" + numberOfOnes + " of=" + filePath);
     }
 
-    private static CommandResult checkReadAt(CommandRunner runner, String filePath, long offset,
-            long size) throws DeviceNotAvailableException {
+    private static CommandResult checkReadAt(
+            CommandRunner runner, String filePath, long offset, long size)
+            throws DeviceNotAvailableException {
         String cmd = "dd if=" + filePath + " of=/dev/null bs=1 count=" + size;
         if (offset > 0) {
             cmd += " skip=" + offset;
@@ -696,10 +733,15 @@
         return runner.runForResult(cmd);
     }
 
-    private CommandResult writeZerosAtFileOffset(CommandRunner runner, String filePath, long offset,
-            long numberOfZeros, boolean writeThrough) throws DeviceNotAvailableException {
-        String cmd = "dd if=/dev/zero of=" + filePath + " bs=1 count=" + numberOfZeros
-                + " conv=notrunc";
+    private CommandResult writeZerosAtFileOffset(
+            CommandRunner runner,
+            String filePath,
+            long offset,
+            long numberOfZeros,
+            boolean writeThrough)
+            throws DeviceNotAvailableException {
+        String cmd =
+                "dd if=/dev/zero of=" + filePath + " bs=1 count=" + numberOfZeros + " conv=notrunc";
         if (offset > 0) {
             cmd += " seek=" + offset;
         }
diff --git a/authfs/testdata/README.md b/tests/authfs/testdata/README.md
similarity index 100%
rename from authfs/testdata/README.md
rename to tests/authfs/testdata/README.md
diff --git a/authfs/testdata/cert.der b/tests/authfs/testdata/cert.der
similarity index 100%
rename from authfs/testdata/cert.der
rename to tests/authfs/testdata/cert.der
Binary files differ
diff --git a/authfs/testdata/cert.pem b/tests/authfs/testdata/cert.pem
similarity index 100%
rename from authfs/testdata/cert.pem
rename to tests/authfs/testdata/cert.pem
diff --git a/authfs/testdata/input.4k b/tests/authfs/testdata/input.4k
similarity index 100%
rename from authfs/testdata/input.4k
rename to tests/authfs/testdata/input.4k
Binary files differ
diff --git a/authfs/testdata/input.4k.fsv_meta b/tests/authfs/testdata/input.4k.fsv_meta
similarity index 100%
rename from authfs/testdata/input.4k.fsv_meta
rename to tests/authfs/testdata/input.4k.fsv_meta
Binary files differ
diff --git a/authfs/testdata/input.4k1 b/tests/authfs/testdata/input.4k1
similarity index 100%
rename from authfs/testdata/input.4k1
rename to tests/authfs/testdata/input.4k1
Binary files differ
diff --git a/authfs/testdata/input.4k1.fsv_meta b/tests/authfs/testdata/input.4k1.fsv_meta
similarity index 100%
rename from authfs/testdata/input.4k1.fsv_meta
rename to tests/authfs/testdata/input.4k1.fsv_meta
Binary files differ
diff --git a/authfs/testdata/input.4m b/tests/authfs/testdata/input.4m
similarity index 100%
rename from authfs/testdata/input.4m
rename to tests/authfs/testdata/input.4m
Binary files differ
diff --git a/authfs/testdata/input.4m.fsv_meta b/tests/authfs/testdata/input.4m.fsv_meta
similarity index 100%
rename from authfs/testdata/input.4m.fsv_meta
rename to tests/authfs/testdata/input.4m.fsv_meta
Binary files differ
diff --git a/authfs/testdata/input.4m.fsv_meta.bad_merkle b/tests/authfs/testdata/input.4m.fsv_meta.bad_merkle
similarity index 100%
rename from authfs/testdata/input.4m.fsv_meta.bad_merkle
rename to tests/authfs/testdata/input.4m.fsv_meta.bad_merkle
Binary files differ
diff --git a/authfs/testdata/key.pem b/tests/authfs/testdata/key.pem
similarity index 100%
rename from authfs/testdata/key.pem
rename to tests/authfs/testdata/key.pem
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 ec1a553..0e59a01 100644
--- a/tests/benchmark/src/java/com/android/microdroid/benchmark/MicrodroidBenchmarks.java
+++ b/tests/benchmark/src/java/com/android/microdroid/benchmark/MicrodroidBenchmarks.java
@@ -268,6 +268,7 @@
                 /* fullDebug */ false,
                 (builder) -> builder.setCpuTopology(CPU_TOPOLOGY_ONE_CPU));
     }
+
     @Test
     public void testMicrodroidHostCpuTopologyBootTime()
             throws VirtualMachineException, InterruptedException, IOException {
@@ -280,10 +281,7 @@
     @Test
     public void testMicrodroidDebugBootTime()
             throws VirtualMachineException, InterruptedException, IOException {
-        runBootTimeTest(
-                "test_vm_boot_time_debug",
-                /* fullDebug */ true,
-                (builder) -> builder);
+        runBootTimeTest("test_vm_boot_time_debug", /* fullDebug */ true, (builder) -> builder);
     }
 
     private void testMicrodroidDebugBootTime_withVendorBase(File vendorDiskImage) throws Exception {
@@ -366,12 +364,12 @@
 
     @Test
     public void testVirtioBlkSeqReadRate() throws Exception {
-        testVirtioBlkReadRate(/*isRand=*/ false);
+        testVirtioBlkReadRate(/* isRand= */ false);
     }
 
     @Test
     public void testVirtioBlkRandReadRate() throws Exception {
-        testVirtioBlkReadRate(/*isRand=*/ true);
+        testVirtioBlkReadRate(/* isRand= */ true);
     }
 
     private void testVirtioBlkReadRate(boolean isRand) throws Exception {
diff --git a/tests/benchmark_hostside/java/android/avf/test/AVFHostTestCase.java b/tests/benchmark_hostside/java/android/avf/test/AVFHostTestCase.java
index 4a61016..e2956f2 100644
--- a/tests/benchmark_hostside/java/android/avf/test/AVFHostTestCase.java
+++ b/tests/benchmark_hostside/java/android/avf/test/AVFHostTestCase.java
@@ -69,6 +69,7 @@
 
     /** Boot time test related variables */
     private static final int REINSTALL_APEX_RETRY_INTERVAL_MS = 5 * 1000;
+
     private static final int REINSTALL_APEX_TIMEOUT_SEC = 15;
     private static final int COMPILE_STAGED_APEX_RETRY_INTERVAL_MS = 10 * 1000;
     private static final int COMPILE_STAGED_APEX_TIMEOUT_SEC = 540;
@@ -122,17 +123,18 @@
 
     @Test
     public void testNoLongHypSections() throws Exception {
-        String[] hypEvents = {
-            "hyp_enter", "hyp_exit"
-        };
+        String[] hypEvents = {"hyp_enter", "hyp_exit"};
 
-        assumeTrue("Skip without hypervisor tracing",
-            KvmHypTracer.isSupported(getDevice(), hypEvents));
+        assumeTrue(
+                "Skip without hypervisor tracing",
+                KvmHypTracer.isSupported(getDevice(), hypEvents));
 
         KvmHypTracer tracer = new KvmHypTracer(getDevice(), hypEvents);
         String result = tracer.run(COMPOSD_CMD_BIN + " test-compile");
         assertWithMessage("Failed to test compilation VM.")
-                .that(result).ignoringCase().contains("all ok");
+                .that(result)
+                .ignoringCase()
+                .contains("all ok");
 
         SimpleStats stats = tracer.getDurationStats();
         reportMetric(stats.getData(), "hyp_sections", "s");
@@ -141,32 +143,37 @@
 
     @Test
     public void testPsciMemProtect() throws Exception {
-        String[] hypEvents = {
-            "psci_mem_protect"
-        };
+        String[] hypEvents = {"psci_mem_protect"};
 
-        assumeTrue("Skip without hypervisor tracing",
-            KvmHypTracer.isSupported(getDevice(), hypEvents));
+        assumeTrue(
+                "Skip without hypervisor tracing",
+                KvmHypTracer.isSupported(getDevice(), hypEvents));
         KvmHypTracer tracer = new KvmHypTracer(getDevice(), hypEvents);
 
         /* We need to wait for crosvm to die so all the VM pages are reclaimed */
         String result = tracer.run(COMPOSD_CMD_BIN + " test-compile && killall -w crosvm || true");
         assertWithMessage("Failed to test compilation VM.")
-                .that(result).ignoringCase().contains("all ok");
+                .that(result)
+                .ignoringCase()
+                .contains("all ok");
 
         List<Integer> values = tracer.getPsciMemProtect();
 
         assertWithMessage("PSCI MEM_PROTECT events not recorded")
-            .that(values.size()).isGreaterThan(2);
+                .that(values.size())
+                .isGreaterThan(2);
 
         assertWithMessage("PSCI MEM_PROTECT counter not starting from 0")
-            .that(values.get(0)).isEqualTo(0);
+                .that(values.get(0))
+                .isEqualTo(0);
 
         assertWithMessage("PSCI MEM_PROTECT counter not ending with 0")
-            .that(values.get(values.size() - 1)).isEqualTo(0);
+                .that(values.get(values.size() - 1))
+                .isEqualTo(0);
 
         assertWithMessage("PSCI MEM_PROTECT counter didn't increment")
-            .that(Collections.max(values)).isGreaterThan(0);
+                .that(Collections.max(values))
+                .isGreaterThan(0);
     }
 
     @Test
@@ -182,9 +189,7 @@
 
     @Test
     public void testSettingsAppStartupTime() throws Exception {
-        String[] launchIntentPackages = {
-            "com.android.settings"
-        };
+        String[] launchIntentPackages = {"com.android.settings"};
         String launchIntentPackage = findSupportedPackage(launchIntentPackages);
         assume().withMessage("No supported settings package").that(launchIntentPackage).isNotNull();
         appStartupHelper(launchIntentPackage);
@@ -193,28 +198,34 @@
     private void appStartupHelper(String launchIntentPackage) throws Exception {
         assumeTrue(
                 "Skip on non-protected VMs",
-                ((TestDevice) getDevice()).supportsMicrodroid(/*protectedVm=*/ true));
+                ((TestDevice) getDevice()).supportsMicrodroid(/* protectedVm= */ true));
 
         StartupTimeMetricCollection mCollection =
                 new StartupTimeMetricCollection(getPackageName(launchIntentPackage), ROUND_COUNT);
         getAppStartupTime(launchIntentPackage, mCollection);
 
-        reportMetric(mCollection.mAppBeforeVmRunTotalTime,
+        reportMetric(
+                mCollection.mAppBeforeVmRunTotalTime,
                 "app_startup/" + mCollection.getPkgName() + "/total_time/before_vm",
                 "ms");
-        reportMetric(mCollection.mAppBeforeVmRunWaitTime,
+        reportMetric(
+                mCollection.mAppBeforeVmRunWaitTime,
                 "app_startup/" + mCollection.getPkgName() + "/wait_time/before_vm",
                 "ms");
-        reportMetric(mCollection.mAppDuringVmRunTotalTime,
+        reportMetric(
+                mCollection.mAppDuringVmRunTotalTime,
                 "app_startup/" + mCollection.getPkgName() + "/total_time/during_vm",
                 "ms");
-        reportMetric(mCollection.mAppDuringVmRunWaitTime,
+        reportMetric(
+                mCollection.mAppDuringVmRunWaitTime,
                 "app_startup/" + mCollection.getPkgName() + "/wait_time/during_vm",
                 "ms");
-        reportMetric(mCollection.mAppAfterVmRunTotalTime,
+        reportMetric(
+                mCollection.mAppAfterVmRunTotalTime,
                 "app_startup/" + mCollection.getPkgName() + "/total_time/after_vm",
                 "ms");
-        reportMetric(mCollection.mAppAfterVmRunWaitTime,
+        reportMetric(
+                mCollection.mAppAfterVmRunWaitTime,
                 "app_startup/" + mCollection.getPkgName() + "/wait_time/after_vm",
                 "ms");
     }
@@ -234,8 +245,9 @@
 
         for (String pkgName : pkgNameList) {
             String appPkg = getPackageName(pkgName);
-            String hasPackage = android.run("pm list package | grep -w " + appPkg + " 1> /dev/null"
-                    + "; echo $?");
+            String hasPackage =
+                    android.run(
+                            "pm list package | grep -w " + appPkg + " 1> /dev/null" + "; echo $?");
             assertNotNull(hasPackage);
 
             if (hasPackage.equals("0")) {
@@ -390,8 +402,8 @@
         }
     }
 
-    private int getFreeMemoryInfoMb(CommandRunner android) throws DeviceNotAvailableException,
-            IllegalArgumentException {
+    private int getFreeMemoryInfoMb(CommandRunner android)
+            throws DeviceNotAvailableException, IllegalArgumentException {
         int freeMemory = 0;
         String content = android.runForResult("cat /proc/meminfo").getStdout().trim();
         String[] lines = content.split("[\r\n]+");
@@ -410,8 +422,8 @@
             throws DeviceNotAvailableException, InterruptedException {
         android.run("input keyevent", "KEYCODE_WAKEUP");
         Thread.sleep(500);
-        final String ret = android.runForResult("dumpsys nfc | grep 'mScreenState='")
-                .getStdout().trim();
+        final String ret =
+                android.runForResult("dumpsys nfc | grep 'mScreenState='").getStdout().trim();
         if (ret != null && ret.contains("ON_LOCKED")) {
             android.run("input keyevent", "KEYCODE_MENU");
         }
@@ -429,8 +441,9 @@
                 String[] bootKeyVal = bootLoaderPhase.split(":");
                 String key = String.format("%s%s", BOOTLOADER_PREFIX, bootKeyVal[0]);
 
-                bootloaderTime.computeIfAbsent(key,
-                        k -> new ArrayList<>()).add(Double.parseDouble(bootKeyVal[1]));
+                bootloaderTime
+                        .computeIfAbsent(key, k -> new ArrayList<>())
+                        .add(Double.parseDouble(bootKeyVal[1]));
                 // SW is the time spent on the warning screen. So ignore it in
                 // final boot time calculation.
                 if (BOOTLOADER_PHASE_SW.equalsIgnoreCase(bootKeyVal[0])) {
@@ -438,8 +451,9 @@
                 }
                 bootLoaderTotalTime += Double.parseDouble(bootKeyVal[1]);
             }
-            bootloaderTime.computeIfAbsent(BOOTLOADER_TIME,
-                    k -> new ArrayList<>()).add(bootLoaderTotalTime);
+            bootloaderTime
+                    .computeIfAbsent(BOOTLOADER_TIME, k -> new ArrayList<>())
+                    .add(bootLoaderTotalTime);
         }
     }
 
@@ -518,7 +532,9 @@
                         android.runWithTimeout(
                                 3 * 60 * 1000, COMPOSD_CMD_BIN + " staged-apex-compile");
                 assertWithMessage("Failed to compile staged APEX. Reason: " + result)
-                    .that(result).ignoringCase().contains("all ok");
+                        .that(result)
+                        .ignoringCase()
+                        .contains("all ok");
 
                 CLog.i("Success to compile staged APEX. Result: " + result);
 
@@ -546,22 +562,23 @@
             try {
                 CommandRunner android = new CommandRunner(getDevice());
 
-                String packagesOutput =
-                        android.run("pm list packages -f --apex-only");
+                String packagesOutput = android.run("pm list packages -f --apex-only");
 
-                Pattern p = Pattern.compile(
-                        "package:(.*)=(com(?:\\.google)?\\.android\\.art)$", Pattern.MULTILINE);
+                Pattern p =
+                        Pattern.compile(
+                                "package:(.*)=(com(?:\\.google)?\\.android\\.art)$",
+                                Pattern.MULTILINE);
                 Matcher m = p.matcher(packagesOutput);
                 assertWithMessage("ART module not found. Packages are:\n" + packagesOutput)
-                    .that(m.find())
-                    .isTrue();
+                        .that(m.find())
+                        .isTrue();
 
                 String artApexPath = m.group(1);
 
-                CommandResult result = android.runForResult(
-                        "pm install --apex " + artApexPath);
+                CommandResult result = android.runForResult("pm install --apex " + artApexPath);
                 assertWithMessage("Failed to install APEX. Reason: " + result)
-                    .that(result.getExitCode()).isEqualTo(0);
+                        .that(result.getExitCode())
+                        .isEqualTo(0);
 
                 CLog.i("Success to install APEX. Result: " + result);
 
diff --git a/tests/ferrochrome/Android.bp b/tests/ferrochrome/Android.bp
index f165b8f..f1b7f27 100644
--- a/tests/ferrochrome/Android.bp
+++ b/tests/ferrochrome/Android.bp
@@ -19,8 +19,7 @@
     name: "ferrochrome-tests.sh",
     srcs: ["ferrochrome.sh"],
     out: ["ferrochrome-tests"],
-    // This breaks shebang, but test will execute the script with bash
-    cmd: "echo \"set -x\" > $(out); cat $(in) >> $(out)",
+    cmd: "sed '2 i set -x' $(in) > $(out)",
 }
 
 sh_binary_host {
diff --git a/tests/ferrochrome/AndroidTest.xml b/tests/ferrochrome/AndroidTest.xml
index 8053674..6c975be 100644
--- a/tests/ferrochrome/AndroidTest.xml
+++ b/tests/ferrochrome/AndroidTest.xml
@@ -34,14 +34,20 @@
         <option name="throw-if-cmd-fail" value="false" />
         <option name="run-command" value="mkdir /data/local/tmp" />
         <option name="teardown-command" value="pkill vmlauncher" />
-        <option name="teardown-command" value="rm /data/local/tmp/chromiumos_test_image.bin" />
+        <option name="teardown-command" value="rm /data/local/tmp/chromiumos_base_image.bin" />
+        <option name="teardown-command" value="rm -rf /data/local/tmp/ferrochrome_screenshots" />
     </target_preparer>
 
-    <test class="com.android.tradefed.testtype.binary.ExecutableHostTest" >
+    <test class="com.android.tradefed.testtype.binary.ExecutableHostTest">
         <option name="binary" value="ferrochrome-tests" />
         <option name="relative-path-execution" value="true" />
         <option name="runtime-hint" value="10m" />
         <option name="per-binary-timeout" value="20m" />
     </test>
+
+    <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
+        <option name="directory-keys" value="/data/local/tmp/ferrochrome_screenshots" />
+        <option name="collect-on-run-ended-only" value="true" />
+    </metrics_collector>
 </configuration>
 
diff --git a/tests/ferrochrome/assets/vm_config.json b/tests/ferrochrome/assets/vm_config.json
index 358df95..53e3b72 100644
--- a/tests/ferrochrome/assets/vm_config.json
+++ b/tests/ferrochrome/assets/vm_config.json
@@ -2,19 +2,37 @@
     "name": "cros",
     "disks": [
         {
-            "image": "/data/local/tmp/chromiumos_test_image.bin",
+            "image": "/data/local/tmp/chromiumos_base_image.bin",
             "partitions": [],
             "writable": true
         }
     ],
+    "protected": false,
+    "cpu_topology": "match_host",
+    "platform_version": "~1.0",
+    "memory_mib": 8096,
+    "debuggable": true,
+    "console_out": true,
+    "connect_console": true,
+    "console_input_device": "hvc0",
+    "network": true,
+    "input": {
+        "touchscreen": true,
+        "keyboard": true,
+        "mouse": true,
+        "trackpad": true,
+        "switches": true
+    },
+    "audio": {
+        "speaker": true,
+        "microphone": true
+    },
     "gpu": {
         "backend": "virglrenderer",
         "context_types": ["virgl2"]
     },
-    "params": "root=/dev/vda3 rootwait noinitrd ro enforcing=0 cros_debug cros_secure",
-    "protected": false,
-    "cpu_topology": "match_host",
-    "platform_version": "~1.0",
-    "memory_mib" : 8096,
-    "console_input_device": "ttyS0"
+    "display": {
+        "scale": "0.77",
+        "refresh_rate": "30"
+    }
 }
diff --git a/tests/ferrochrome/ferrochrome.sh b/tests/ferrochrome/ferrochrome.sh
index 210548a..c68e8a9 100755
--- a/tests/ferrochrome/ferrochrome.sh
+++ b/tests/ferrochrome/ferrochrome.sh
@@ -20,18 +20,29 @@
 set -e
 
 FECR_GS_URL="https://storage.googleapis.com/chromiumos-image-archive/ferrochrome-public"
-FECR_DEFAULT_VERSION="R127-15916.0.0"
+FECR_DEFAULT_VERSION="R128-15958.0.0"
+FECR_DEFAULT_SCREENSHOT_DIR="/data/local/tmp/ferrochrome_screenshots"  # Hardcoded at AndroidTest.xml
+FECR_TEST_IMAGE="chromiumos_test_image"
+FECR_BASE_IMAGE="chromiumos_base_image"
 FECR_DEVICE_DIR="/data/local/tmp"
+FECR_IMAGE_VM_CONFIG_JSON="chromiumos_base_image.bin"  # hardcoded at vm_config.json
 FECR_CONFIG_PATH="/data/local/tmp/vm_config.json"  # hardcoded at VmLauncherApp
-FECR_CONSOLE_LOG_PATH="/data/data/\${pkg_name}/files/console.log"
-FECR_BOOT_COMPLETED_LOG="Have fun and send patches!"
+FECR_CONSOLE_LOG_PATH="files/cros.log" # log file name is ${vm_name}.log
+FECR_TEST_IMAGE_BOOT_COMPLETED_LOG="Have fun and send patches!"
+FECR_BASE_IMAGE_BOOT_COMPLETED_LOG="Chrome started, our work is done, exiting"
 FECR_BOOT_TIMEOUT="300" # 5 minutes (300 seconds)
 ACTION_NAME="android.virtualization.VM_LAUNCHER"
-TRY_UNLOCK_MAX=10
+
+# Match this with AndroidTest.xml and assets/vm_config.json
+FECR_DEFAULT_IMAGE="${FECR_BASE_IMAGE}"
+FECR_DEFAULT_BOOT_COMPLETED_LOG="${FECR_BASE_IMAGE_BOOT_COMPLETED_LOG}"
 
 fecr_clean_up() {
   trap - INT
 
+  # Reset screen always on
+  adb shell svc power stayon false
+
   if [[ -d ${fecr_dir} && -z ${fecr_keep} ]]; then
     rm -rf ${fecr_dir}
   fi
@@ -40,7 +51,7 @@
 print_usage() {
   echo "ferochrome: Launches ferrochrome image"
   echo ""
-  echo "By default, this downloads ferrochrome image with version ${FECR_DEFAULT_VERSION},"
+  echo "By default, this downloads ${FECR_DEFAULT_VERSION} with version ${FECR_DEFAULT_VERSION},"
   echo "launches, and waits for boot completed."
   echo "When done, removes downloaded image on host while keeping pushed image on device."
   echo ""
@@ -53,14 +64,18 @@
   echo "  --skip: Skipping downloading and/or pushing images"
   echo "  --version \${version}: ferrochrome version to be downloaded"
   echo "  --keep: Keep downloaded ferrochrome image"
+  echo "  --test: Download test image instead"
 }
 
-fecr_version=""
+fecr_version="${FECR_DEFAULT_VERSION}"
 fecr_dir=""
 fecr_keep=""
 fecr_skip=""
 fecr_script_path=$(dirname ${0})
 fecr_verbose=""
+fecr_image="${FECR_DEFAULT_IMAGE}"
+fecr_boot_completed_log="${FECR_DEFAULT_BOOT_COMPLETED_LOG}"
+fecr_screenshot_dir="${FECR_DEFAULT_SCREENSHOT_DIR}"
 
 # Parse parameters
 while (( "${#}" )); do
@@ -83,6 +98,10 @@
     --skip)
       fecr_skip="true"
       ;;
+    --test)
+      fecr_image="${FECR_TEST_IMAGE}"
+      fecr_boot_completed_log="${FECR_TEST_IMAGE_BOOT_COMPLETED_LOG}"
+      ;;
     -h|--help)
       print_usage
       exit 0
@@ -123,58 +142,34 @@
     fecr_dir=$(mktemp -d)
 
     echo "Downloading & extracting ferrochrome image to ${fecr_dir}"
-    fecr_version=${fecr_version:-${FECR_DEFAULT_VERSION}}
-    curl ${FECR_GS_URL}/${fecr_version}/chromiumos_test_image.tar.xz | tar xfJ - -C ${fecr_dir}
+    curl ${FECR_GS_URL}/${fecr_version}/${fecr_image}.tar.xz | tar xfJ - -C ${fecr_dir}
   fi
 
   echo "Pushing ferrochrome image to ${FECR_DEVICE_DIR}"
   adb shell mkdir -p ${FECR_DEVICE_DIR} > /dev/null || true
-  adb push ${fecr_dir}/chromiumos_test_image.bin ${FECR_DEVICE_DIR}
+  adb push ${fecr_dir}/${fecr_image}.bin ${FECR_DEVICE_DIR}/${FECR_IMAGE_VM_CONFIG_JSON}
   adb push ${fecr_script_path}/assets/vm_config.json ${FECR_CONFIG_PATH}
 fi
 
 echo "Ensure screen unlocked"
+adb shell svc power stayon true
+adb shell wm dismiss-keyguard
 
-try_unlock=0
-while [[ "${try_unlock}" -le "${TRY_UNLOCK_MAX}" ]]; do
-  screen_state=$(adb shell dumpsys nfc | sed -n 's/^mScreenState=\(.*\)$/\1/p')
-  case "${screen_state}" in
-    "ON_UNLOCKED")
-      break
-      ;;
-    "ON_LOCKED")
-      # Disclaimer: This can unlock phone only if unlock method is swipe (default after FDR)
-      adb shell input keyevent KEYCODE_MENU
-      ;;
-    "OFF_LOCKED"|"OFF_UNLOCKED")
-      adb shell input keyevent KEYCODE_WAKEUP
-      ;;
-    *)
-      echo "Unknown screen state. Continue to boot, but may fail"
-      break
-      ;;
-  esac
-  sleep 1
-  try_unlock=$((try_unlock+1))
-done
-if [[ "${try_unlock}" -gt "${TRY_UNLOCK_MAX}" ]]; then
-  >&2 echo "Failed to unlock screen. Try again after manual unlock"
-  exit 1
-fi
+echo "Granting runtime permissions to ensure VmLauncher is focused"
+adb shell pm grant ${pkg_name} android.permission.RECORD_AUDIO
 
 echo "Starting ferrochrome"
 adb shell am start-activity -a ${ACTION_NAME} > /dev/null
 
-if [[ $(adb shell getprop ro.fw.mu.headless_system_user) == "true" ]]; then
-  current_user=$(adb shell am get-current-user)
-  log_path="/data/user/${current_user}/${pkg_name}/files/console.log"
-else
-  log_path="/data/data/${pkg_name}/files/console.log"
-fi
+# HSUM aware log path
+current_user=$(adb shell am get-current-user)
+log_path="/data/user/${current_user}/${pkg_name}/${FECR_CONSOLE_LOG_PATH}"
 fecr_start_time=${EPOCHSECONDS}
 
+adb shell mkdir -p "${fecr_screenshot_dir}"
 while [[ $((EPOCHSECONDS - fecr_start_time)) -lt ${FECR_BOOT_TIMEOUT} ]]; do
-  adb shell grep -sF \""${FECR_BOOT_COMPLETED_LOG}"\" "${log_path}" && exit 0
+  adb shell screencap -p "${fecr_screenshot_dir}/screenshot-${EPOCHSECONDS}.png"
+  adb shell grep -soF \""${fecr_boot_completed_log}"\" "${log_path}" && exit 0 || true
   sleep 10
 done
 
@@ -182,3 +177,4 @@
 >&2 adb shell cat ${log_path}
 
 exit 1
+
diff --git a/tests/helper/src/java/com/android/microdroid/test/common/DeviceProperties.java b/tests/helper/src/java/com/android/microdroid/test/common/DeviceProperties.java
index 69527be..af68eb3 100644
--- a/tests/helper/src/java/com/android/microdroid/test/common/DeviceProperties.java
+++ b/tests/helper/src/java/com/android/microdroid/test/common/DeviceProperties.java
@@ -33,6 +33,8 @@
 
     private static final String CUTTLEFISH_DEVICE_PREFIX = "vsoc_";
     private static final String CUTTLEFISH_ARM64_DEVICE_PREFIX = "vsoc_arm64";
+    private static final String GOLDFISH_DEVICE_PREFIX = "emu64";
+    private static final String GOLDFISH_ARM64_DEVICE_PREFIX = "emu64a";
     private static final String USER_BUILD_TYPE = "user";
     private static final String HWASAN_SUFFIX = "_hwasan";
 
@@ -65,6 +67,23 @@
     }
 
     /**
+     * @return whether the device is a cuttlefish device.
+     */
+    public boolean isGoldfish() {
+        String vendorDeviceName = getProperty(KEY_VENDOR_DEVICE);
+        return vendorDeviceName != null && vendorDeviceName.startsWith(GOLDFISH_DEVICE_PREFIX);
+    }
+
+    /**
+     * @return whether the device is a cuttlefish device running on 64 bit Arm.
+     */
+    public boolean isGoldfishArm64() {
+        String vendorDeviceName = getProperty(KEY_VENDOR_DEVICE);
+        return vendorDeviceName != null
+                && vendorDeviceName.startsWith(GOLDFISH_ARM64_DEVICE_PREFIX);
+    }
+
+    /**
      * @return whether the build is HWASAN.
      */
     public boolean isHwasan() {
diff --git a/tests/helper/src/java/com/android/microdroid/test/common/MetricsProcessor.java b/tests/helper/src/java/com/android/microdroid/test/common/MetricsProcessor.java
index dd68d6a..8f93d1e 100644
--- a/tests/helper/src/java/com/android/microdroid/test/common/MetricsProcessor.java
+++ b/tests/helper/src/java/com/android/microdroid/test/common/MetricsProcessor.java
@@ -28,8 +28,8 @@
 
     public static String getMetricPrefix(String debugTag) {
         return "avf_perf"
-            + ((debugTag != null && !debugTag.isEmpty()) ? "[" + debugTag + "]" : "")
-            + "/";
+                + ((debugTag != null && !debugTag.isEmpty()) ? "[" + debugTag + "]" : "")
+                + "/";
     }
 
     public MetricsProcessor(String prefix) {
@@ -41,8 +41,8 @@
      * a {@link Map} with the corresponding keys equal to [mPrefix + name +
      * _[min|max|average|stdev]_ + unit].
      */
-    public Map<String, Double> computeStats(List<? extends Number> metrics, String name,
-            String unit) {
+    public Map<String, Double> computeStats(
+            List<? extends Number> metrics, String name, String unit) {
         List<Double> values = new ArrayList<>(metrics.size());
         for (Number metric : metrics) {
             values.add(metric.doubleValue());
diff --git a/tests/helper/src/java/com/android/microdroid/test/common/ProcessUtil.java b/tests/helper/src/java/com/android/microdroid/test/common/ProcessUtil.java
index e058674..c4aba81 100644
--- a/tests/helper/src/java/com/android/microdroid/test/common/ProcessUtil.java
+++ b/tests/helper/src/java/com/android/microdroid/test/common/ProcessUtil.java
@@ -69,13 +69,13 @@
     }
 
     /** Gets global memory metrics key and values mapping */
-    public static Map<String, Long> getProcessMemoryMap(
-            Function<String, String> shellExecutor) throws IOException {
+    public static Map<String, Long> getProcessMemoryMap(Function<String, String> shellExecutor)
+            throws IOException {
         // The input file of parseMemoryInfo need a header string as the key of output entries.
         // /proc/meminfo doesn't have this line so add one as the key.
         String header = "device memory info\n";
-        List<SMapEntry> entries = parseMemoryInfo(header
-                + shellExecutor.apply("cat /proc/meminfo"));
+        List<SMapEntry> entries =
+                parseMemoryInfo(header + shellExecutor.apply("cat /proc/meminfo"));
         if (entries.size() != 1) {
             throw new RuntimeException(
                     "expected one entry in /proc/meminfo, got " + entries.size());
diff --git a/tests/helper/src/java/com/android/microdroid/test/device/MicrodroidDeviceTestBase.java b/tests/helper/src/java/com/android/microdroid/test/device/MicrodroidDeviceTestBase.java
index 91ff4d8..135d947 100644
--- a/tests/helper/src/java/com/android/microdroid/test/device/MicrodroidDeviceTestBase.java
+++ b/tests/helper/src/java/com/android/microdroid/test/device/MicrodroidDeviceTestBase.java
@@ -81,6 +81,14 @@
         return getDeviceProperties().isCuttlefishArm64();
     }
 
+    public static boolean isGoldfish() {
+        return getDeviceProperties().isGoldfish();
+    }
+
+    private static boolean isGoldfishArm64() {
+        return getDeviceProperties().isGoldfishArm64();
+    }
+
     public static boolean isHwasan() {
         return getDeviceProperties().isHwasan();
     }
@@ -100,15 +108,15 @@
     protected final void grantPermission(String permission) {
         Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
         UiAutomation uiAutomation = instrumentation.getUiAutomation();
-        uiAutomation.grantRuntimePermission(instrumentation.getContext().getPackageName(),
-                permission);
+        uiAutomation.grantRuntimePermission(
+                instrumentation.getContext().getPackageName(), permission);
     }
 
     protected final void revokePermission(String permission) {
         Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
         UiAutomation uiAutomation = instrumentation.getUiAutomation();
-        uiAutomation.revokeRuntimePermission(instrumentation.getContext().getPackageName(),
-                permission);
+        uiAutomation.revokeRuntimePermission(
+                instrumentation.getContext().getPackageName(), permission);
     }
 
     protected final void setMaxPerformanceTaskProfile() throws IOException {
@@ -225,12 +233,11 @@
     }
 
     protected void assumeVsrCompliant() {
-        boolean featureCheck = mCtx.getPackageManager().hasSystemFeature(FEATURE_WATCH) ||
-                               mCtx.getPackageManager().hasSystemFeature(FEATURE_AUTOMOTIVE) ||
-                               mCtx.getPackageManager().hasSystemFeature(FEATURE_LEANBACK);
-        assume().withMessage("This device is not VSR compliant")
-                .that(featureCheck)
-                .isFalse();
+        boolean featureCheck =
+                mCtx.getPackageManager().hasSystemFeature(FEATURE_WATCH)
+                        || mCtx.getPackageManager().hasSystemFeature(FEATURE_AUTOMOTIVE)
+                        || mCtx.getPackageManager().hasSystemFeature(FEATURE_LEANBACK);
+        assume().withMessage("This device is not VSR compliant").that(featureCheck).isFalse();
     }
 
     protected boolean isGsi() {
@@ -246,10 +253,12 @@
                 .that(KERNEL_VERSION)
                 .isNotEqualTo("5.4");
 
-        // Cuttlefish on Arm 64 doesn't and cannot support any form of virtualization, so there's
-        // no point running any of these tests.
-        assume().withMessage("Virtualization not supported on Arm64 Cuttlefish. b/341889915")
-                .that(isCuttlefishArm64())
+        // Cuttlefish/Goldfish on Arm 64 doesn't and cannot support any form of virtualization,
+        // so there's no point running any of these tests.
+        assume().withMessage(
+                        "Virtualization not supported on Arm64 Cuttlefish/Goldfish."
+                                + " b/341889915")
+                .that(isCuttlefishArm64() || isGoldfishArm64())
                 .isFalse();
     }
 
@@ -279,7 +288,8 @@
             if (log.contains("Run /init as init process") && !mInitStartedNanoTime.isPresent()) {
                 mInitStartedNanoTime = OptionalLong.of(System.nanoTime());
             }
-            if (log.contains("microdroid_manager") && log.contains("executing main task")
+            if (log.contains("microdroid_manager")
+                    && log.contains("executing main task")
                     && !mPayloadStartedNanoTime.isPresent()) {
                 mPayloadStartedNanoTime = OptionalLong.of(System.nanoTime());
             }
diff --git a/tests/hostside/helper/java/com/android/microdroid/test/host/CommandResultSubject.java b/tests/hostside/helper/java/com/android/microdroid/test/host/CommandResultSubject.java
index 2e9d078..1d292eb 100644
--- a/tests/hostside/helper/java/com/android/microdroid/test/host/CommandResultSubject.java
+++ b/tests/hostside/helper/java/com/android/microdroid/test/host/CommandResultSubject.java
@@ -25,9 +25,7 @@
 import com.google.common.truth.StringSubject;
 import com.google.common.truth.Subject;
 
-/**
- * A <a href="https://github.com/google/truth">Truth</a> subject for {@link CommandResult}.
- */
+/** A <a href="https://github.com/google/truth">Truth</a> subject for {@link CommandResult}. */
 public class CommandResultSubject extends Subject {
     private final CommandResult mActual;
 
diff --git a/tests/hostside/helper/java/com/android/microdroid/test/host/KvmHypTracer.java b/tests/hostside/helper/java/com/android/microdroid/test/host/KvmHypTracer.java
index 5c72358..3814cdd 100644
--- a/tests/hostside/helper/java/com/android/microdroid/test/host/KvmHypTracer.java
+++ b/tests/hostside/helper/java/com/android/microdroid/test/host/KvmHypTracer.java
@@ -17,22 +17,23 @@
 package com.android.microdroid.test.host;
 
 import static com.google.common.truth.Truth.assertWithMessage;
+
 import static org.junit.Assert.assertNotNull;
 
-import com.android.microdroid.test.host.CommandRunner;
 import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.log.LogUtil.CLog;
 import com.android.tradefed.util.SimpleStats;
 
+import java.io.BufferedReader;
 import java.io.File;
 import java.io.FileReader;
-import java.io.BufferedReader;
 import java.text.ParseException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
+
 import javax.annotation.Nonnull;
 
 class KvmHypEvent {
@@ -42,16 +43,14 @@
     public final String args;
     public final boolean valid;
 
-    private static final Pattern LOST_EVENT_PATTERN = Pattern.compile(
-            "^CPU:[0-9]* \\[LOST ([0-9]*) EVENTS\\]");
+    private static final Pattern LOST_EVENT_PATTERN =
+            Pattern.compile("^CPU:[0-9]* \\[LOST ([0-9]*) EVENTS\\]");
 
     public KvmHypEvent(String str) {
         Matcher matcher = LOST_EVENT_PATTERN.matcher(str);
-        if (matcher.find())
-            throw new OutOfMemoryError("Lost " + matcher.group(1) + " events");
+        if (matcher.find()) throw new OutOfMemoryError("Lost " + matcher.group(1) + " events");
 
-        Pattern pattern = Pattern.compile(
-                "^\\[([0-9]*)\\][ \t]*([0-9]*\\.[0-9]*): (\\S+) (.*)");
+        Pattern pattern = Pattern.compile("^\\[([0-9]*)\\][ \t]*([0-9]*\\.[0-9]*): (\\S+) (.*)");
 
         matcher = pattern.matcher(str);
         if (!matcher.find()) {
@@ -72,8 +71,7 @@
     }
 
     public String toString() {
-        return String.format(
-                "[%03d]\t%f: %s %s", cpu, timestamp, name, args);
+        return String.format("[%03d]\t%f: %s %s", cpu, timestamp, name, args);
     }
 }
 
@@ -99,16 +97,16 @@
     }
 
     public static boolean isSupported(ITestDevice device, String[] events) throws Exception {
-        for (String event: events) {
-            if (!device.doesFileExist(HYP_TRACING_ROOT + eventDir(event) + "/enable"))
-                return false;
+        for (String event : events) {
+            if (!device.doesFileExist(HYP_TRACING_ROOT + eventDir(event) + "/enable")) return false;
         }
         return true;
     }
 
     public KvmHypTracer(@Nonnull ITestDevice device, String[] events) throws Exception {
         assertWithMessage("Hypervisor events " + String.join(",", events) + " not supported")
-            .that(isSupported(device, events)).isTrue();
+                .that(isSupported(device, events))
+                .isTrue();
 
         mDevice = device;
         mRunner = new CommandRunner(mDevice);
@@ -123,8 +121,7 @@
         setNode("tracing_on", 0);
         mRunner.run("echo 0 | tee " + HYP_TRACING_ROOT + "events/*/*/enable");
         setNode("buffer_size_kb", DEFAULT_BUF_SIZE_KB);
-        for (String event: mHypEvents)
-            setNode(eventDir(event) + "/enable", 1);
+        for (String event : mHypEvents) setNode(eventDir(event) + "/enable", 1);
         setNode("trace", 0);
 
         /* Cat each per-cpu trace_pipe in its own tmp file in the background */
@@ -147,8 +144,10 @@
 
         /* Wait for cat to finish reading the pipe interface before killing it */
         for (int i = 0; i < mNrCpus; i++) {
-            cmd += "while $(test '$(ps -o S -p $CPU" + i
-                + "_TRACE_PIPE_PID | tail -n 1)' = 'R'); do sleep 1; done;";
+            cmd +=
+                    "while $(test '$(ps -o S -p $CPU"
+                            + i
+                            + "_TRACE_PIPE_PID | tail -n 1)' = 'R'); do sleep 1; done;";
             cmd += "kill -9 $CPU" + i + "_TRACE_PIPE_PID;";
         }
         cmd += "wait";
@@ -164,7 +163,7 @@
 
         mRunner.run("rm -f " + cmd_script);
 
-        for (String t: trace_pipes) {
+        for (String t : trace_pipes) {
             File trace = mDevice.pullFile(t);
             assertNotNull(trace);
             mTraces.add(trace);
@@ -190,12 +189,10 @@
         KvmHypEvent event;
         String l;
 
-        if ((l = br.readLine()) == null)
-            return null;
+        if ((l = br.readLine()) == null) return null;
 
         event = new KvmHypEvent(l);
-        if (!event.valid)
-            return null;
+        if (!event.valid) return null;
 
         return event;
     }
@@ -205,9 +202,10 @@
         SimpleStats stats = new SimpleStats();
 
         assertWithMessage("KvmHypTracer() is missing events " + String.join(",", reqEvents))
-            .that(hasEvents(reqEvents)).isTrue();
+                .that(hasEvents(reqEvents))
+                .isTrue();
 
-        for (File trace: mTraces) {
+        for (File trace : mTraces) {
             BufferedReader br = new BufferedReader(new FileReader(trace));
             double last = 0.0, hyp_enter = 0.0;
             String prev_event = "";
@@ -219,20 +217,18 @@
                     throw new ParseException("Incorrect CPU number: " + cpu, 0);
 
                 double cur = hypEvent.timestamp;
-                if (cur < last)
-                    throw new ParseException("Time must not go backward: " + cur, 0);
+                if (cur < last) throw new ParseException("Time must not go backward: " + cur, 0);
                 last = cur;
 
                 String event = hypEvent.name;
                 if (event.equals(prev_event)) {
-                    throw new ParseException("Hyp event found twice in a row: " +
-                                             trace + " - " + hypEvent, 0);
+                    throw new ParseException(
+                            "Hyp event found twice in a row: " + trace + " - " + hypEvent, 0);
                 }
 
                 switch (event) {
                     case "hyp_exit":
-                        if (prev_event.equals("hyp_enter"))
-                            stats.add(cur - hyp_enter);
+                        if (prev_event.equals("hyp_enter")) stats.add(cur - hyp_enter);
                         break;
                     case "hyp_enter":
                         hyp_enter = cur;
@@ -252,7 +248,8 @@
         List<Integer> psciMemProtect = new ArrayList<>();
 
         assertWithMessage("KvmHypTracer() is missing events " + String.join(",", reqEvents))
-            .that(hasEvents(reqEvents)).isTrue();
+                .that(hasEvents(reqEvents))
+                .isTrue();
 
         BufferedReader[] brs = new BufferedReader[mTraces.size()];
         KvmHypEvent[] next = new KvmHypEvent[mTraces.size()];
@@ -266,22 +263,20 @@
             double oldest = Double.MAX_VALUE;
             int oldestIdx = -1;
 
-            for (int i = 0; i < mTraces.size(); i ++) {
+            for (int i = 0; i < mTraces.size(); i++) {
                 if ((next[i] != null) && (next[i].timestamp < oldest)) {
                     oldest = next[i].timestamp;
                     oldestIdx = i;
                 }
             }
 
-            if (oldestIdx < 0)
-                break;
+            if (oldestIdx < 0) break;
 
-            Pattern pattern = Pattern.compile(
-                "count=([0-9]*) was=([0-9]*)");
+            Pattern pattern = Pattern.compile("count=([0-9]*) was=([0-9]*)");
             Matcher matcher = pattern.matcher(next[oldestIdx].args);
             if (!matcher.find()) {
-                throw new ParseException("Unexpected psci_mem_protect event: " +
-                                         next[oldestIdx], 0);
+                throw new ParseException(
+                        "Unexpected psci_mem_protect event: " + next[oldestIdx], 0);
             }
 
             int count = Integer.parseInt(matcher.group(1));
diff --git a/tests/hostside/helper/java/com/android/microdroid/test/host/LogArchiver.java b/tests/hostside/helper/java/com/android/microdroid/test/host/LogArchiver.java
index 96ab543..ed753d0 100644
--- a/tests/hostside/helper/java/com/android/microdroid/test/host/LogArchiver.java
+++ b/tests/hostside/helper/java/com/android/microdroid/test/host/LogArchiver.java
@@ -27,15 +27,17 @@
 
 /** A helper class for archiving device log files to the host's tradefed output directory. */
 public abstract class LogArchiver {
-    /** Copy device log (then delete) to a tradefed output directory on the host.
+    /**
+     * Copy device log (then delete) to a tradefed output directory on the host.
      *
      * @param logs A {@link TestLogData} that needs to be owned by the actual test case.
      * @param device The device to pull the log file from.
      * @param remotePath The path on the device.
      * @param localName Local file name to be copied to.
      */
-    public static void archiveLogThenDelete(TestLogData logs, ITestDevice device, String remotePath,
-            String localName) throws DeviceNotAvailableException {
+    public static void archiveLogThenDelete(
+            TestLogData logs, ITestDevice device, String remotePath, String localName)
+            throws DeviceNotAvailableException {
         File logFile = device.pullFile(remotePath);
         if (logFile != null) {
             logs.addTestLog(localName, LogDataType.TEXT, new FileInputStreamSource(logFile));
diff --git a/tests/hostside/helper/java/com/android/microdroid/test/host/MicrodroidHostTestCaseBase.java b/tests/hostside/helper/java/com/android/microdroid/test/host/MicrodroidHostTestCaseBase.java
index cd90fbe..974a58c 100644
--- a/tests/hostside/helper/java/com/android/microdroid/test/host/MicrodroidHostTestCaseBase.java
+++ b/tests/hostside/helper/java/com/android/microdroid/test/host/MicrodroidHostTestCaseBase.java
@@ -67,8 +67,11 @@
     protected static final long MICRODROID_COMMAND_TIMEOUT_MILLIS = 30000;
     private static final long MICRODROID_COMMAND_RETRY_INTERVAL_MILLIS = 500;
     protected static final int MICRODROID_ADB_CONNECT_MAX_ATTEMPTS =
-            (int) (MICRODROID_ADB_CONNECT_TIMEOUT_MINUTES * 60 * 1000
-                / MICRODROID_COMMAND_RETRY_INTERVAL_MILLIS);
+            (int)
+                    (MICRODROID_ADB_CONNECT_TIMEOUT_MINUTES
+                            * 60
+                            * 1000
+                            / MICRODROID_COMMAND_RETRY_INTERVAL_MILLIS);
 
     protected static final Set<String> SUPPORTED_GKI_VERSIONS =
             Collections.unmodifiableSet(new HashSet(Arrays.asList("android15-6.6")));
@@ -148,8 +151,9 @@
                 isGsi && vendorApiLevel < 202404);
     }
 
-    public static void archiveLogThenDelete(TestLogData logs, ITestDevice device, String remotePath,
-            String localName) throws DeviceNotAvailableException {
+    public static void archiveLogThenDelete(
+            TestLogData logs, ITestDevice device, String remotePath, String localName)
+            throws DeviceNotAvailableException {
         LogArchiver.archiveLogThenDelete(logs, device, remotePath, localName);
     }
 
@@ -167,6 +171,7 @@
         CommandResult result = RunUtil.getDefault().runTimedCmd(timeout, cmd);
         return result.getStdout().trim();
     }
+
     private static String join(String... strs) {
         return String.join(" ", Arrays.asList(strs));
     }
@@ -197,8 +202,7 @@
         throw new AssertionError("Failed to find test file " + name + " for module " + moduleName);
     }
 
-    public String getPathForPackage(String packageName)
-            throws DeviceNotAvailableException {
+    public String getPathForPackage(String packageName) throws DeviceNotAvailableException {
         return getPathForPackage(getDevice(), packageName);
     }
 
@@ -210,7 +214,8 @@
         CommandRunner android = new CommandRunner(device);
         String pathLine = android.run("pm", "path", packageName);
         assertWithMessage("Package " + packageName + " not found")
-                .that(pathLine).startsWith("package:");
+                .that(pathLine)
+                .startsWith("package:");
         return pathLine.substring("package:".length());
     }
 
diff --git a/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java b/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
index 80d1fc6..0f7be20 100644
--- a/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
+++ b/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
@@ -19,7 +19,6 @@
 import static com.android.microdroid.test.host.CommandResultSubject.command_results;
 import static com.android.tradefed.device.TestDevice.MicrodroidBuilder;
 import static com.android.tradefed.testtype.DeviceJUnit4ClassRunner.TestLogData;
-import com.android.tradefed.device.DeviceRuntimeException;
 
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
@@ -45,6 +44,7 @@
 import com.android.os.AtomsProto;
 import com.android.os.StatsLog;
 import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.DeviceRuntimeException;
 import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.device.TestDevice;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner.TestMetrics;
@@ -79,13 +79,13 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.concurrent.Callable;
 import java.util.concurrent.TimeUnit;
 import java.util.function.Function;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 import java.util.stream.Collectors;
-import java.util.Objects;
 
 @RunWith(DeviceJUnit4Parameterized.class)
 @UseParametersRunnerFactory(DeviceJUnit4ClassRunnerWithParameters.RunnerFactory.class)
@@ -490,6 +490,7 @@
                         .cpuTopology("match_host")
                         .protectedVm(true)
                         .gki(mGki)
+                        .name("protected_vm_runs_pvmfw")
                         .build(getAndroidDevice());
 
         // Assert
@@ -785,6 +786,7 @@
                         .cpuTopology("match_host")
                         .protectedVm(mProtectedVm)
                         .gki(mGki)
+                        .name("test_telemetry_pushed_atoms")
                         .build(device);
         microdroid.waitForBootComplete(BOOT_COMPLETE_TIMEOUT);
         device.shutdownMicrodroid(microdroid);
@@ -816,7 +818,8 @@
         assertThat(atomVmCreationRequested.getIsProtected()).isEqualTo(mProtectedVm);
         assertThat(atomVmCreationRequested.getCreationSucceeded()).isTrue();
         assertThat(atomVmCreationRequested.getBinderExceptionCode()).isEqualTo(0);
-        assertThat(atomVmCreationRequested.getVmIdentifier()).isEqualTo("VmRunApp");
+        assertThat(atomVmCreationRequested.getVmIdentifier())
+                .isEqualTo("test_telemetry_pushed_atoms");
         assertThat(atomVmCreationRequested.getConfigType())
                 .isEqualTo(AtomsProto.VmCreationRequested.ConfigType.VIRTUAL_MACHINE_APP_CONFIG);
         assertThat(atomVmCreationRequested.getNumCpus()).isEqualTo(getDeviceNumCpus(device));
@@ -826,11 +829,11 @@
 
         // Check VmBooted atom
         AtomsProto.VmBooted atomVmBooted = data.get(1).getAtom().getVmBooted();
-        assertThat(atomVmBooted.getVmIdentifier()).isEqualTo("VmRunApp");
+        assertThat(atomVmBooted.getVmIdentifier()).isEqualTo("test_telemetry_pushed_atoms");
 
         // Check VmExited atom
         AtomsProto.VmExited atomVmExited = data.get(2).getAtom().getVmExited();
-        assertThat(atomVmExited.getVmIdentifier()).isEqualTo("VmRunApp");
+        assertThat(atomVmExited.getVmIdentifier()).isEqualTo("test_telemetry_pushed_atoms");
         assertThat(atomVmExited.getDeathReason()).isEqualTo(AtomsProto.VmExited.DeathReason.KILLED);
         assertThat(atomVmExited.getExitSignal()).isEqualTo(9);
         // In CPU & memory related fields, check whether positive values are collected or not.
@@ -927,6 +930,7 @@
                         .memoryMib(minMemorySize())
                         .cpuTopology("match_host")
                         .protectedVm(mProtectedVm)
+                        .name("test_microdroid_boots")
                         .gki(mGki));
     }
 
@@ -940,6 +944,7 @@
                         .cpuTopology("match_host")
                         .protectedVm(mProtectedVm)
                         .gki(mGki)
+                        .name("test_microdroid_ram_usage")
                         .build(getAndroidDevice());
         mMicrodroidDevice.waitForBootComplete(BOOT_COMPLETE_TIMEOUT);
         mMicrodroidDevice.enableAdbRoot();
@@ -1205,6 +1210,7 @@
                         .protectedVm(mProtectedVm)
                         .gki(mGki)
                         .hugePages(true)
+                        .name("test_huge_pages")
                         .build(getAndroidDevice());
         mMicrodroidDevice.waitForBootComplete(BOOT_COMPLETE_TIMEOUT);
 
diff --git a/tests/pvmfw/helper/java/com/android/pvmfw/test/host/Pvmfw.java b/tests/pvmfw/helper/java/com/android/pvmfw/test/host/Pvmfw.java
index a77ba40..5ae5186 100644
--- a/tests/pvmfw/helper/java/com/android/pvmfw/test/host/Pvmfw.java
+++ b/tests/pvmfw/helper/java/com/android/pvmfw/test/host/Pvmfw.java
@@ -27,8 +27,8 @@
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
 import java.io.IOException;
-import java.util.Objects;
 import java.nio.ByteBuffer;
+import java.util.Objects;
 
 /** pvmfw.bin with custom config payloads on host. */
 public final class Pvmfw {
diff --git a/tests/pvmfw/java/com/android/pvmfw/test/DebugPolicyHostTests.java b/tests/pvmfw/java/com/android/pvmfw/test/DebugPolicyHostTests.java
index 2a6ab2d..7efbbc7 100644
--- a/tests/pvmfw/java/com/android/pvmfw/test/DebugPolicyHostTests.java
+++ b/tests/pvmfw/java/com/android/pvmfw/test/DebugPolicyHostTests.java
@@ -30,8 +30,8 @@
 import com.android.tradefed.device.DeviceRuntimeException;
 import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
-import com.android.tradefed.util.CommandStatus;
 import com.android.tradefed.util.CommandResult;
+import com.android.tradefed.util.CommandStatus;
 
 import org.junit.After;
 import org.junit.Before;
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 915e492..7089b33 100644
--- a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
+++ b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
@@ -335,6 +335,7 @@
         testResults.assertNoException();
         assertThat(testResults.mAddInteger).isEqualTo(37 + 73);
     }
+
     @Test
     @CddTest(requirements = {"9.17/C-1-1"})
     public void autoCloseVm() throws Exception {
@@ -737,7 +738,6 @@
         VirtualMachineConfig.Builder otherOsBuilder =
                 newBaselineBuilder().setOs("microdroid_gki-android14-6.1");
         assertConfigCompatible(microdroidOsConfig, otherOsBuilder).isFalse();
-
     }
 
     private VirtualMachineConfig.Builder newBaselineBuilder() {
@@ -870,11 +870,12 @@
     }
 
     @Test
-    @CddTest(requirements = {
-            "9.17/C-1-1",
-            "9.17/C-1-2",
-            "9.17/C-1-4",
-    })
+    @CddTest(
+            requirements = {
+                "9.17/C-1-1",
+                "9.17/C-1-2",
+                "9.17/C-1-4",
+            })
     public void createVmWithConfigRequiresPermission() throws Exception {
         assumeSupportedDevice();
         revokePermission(VirtualMachine.USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION);
@@ -890,14 +891,16 @@
         SecurityException e =
                 assertThrows(
                         SecurityException.class, () -> runVmTestService(TAG, vm, (ts, tr) -> {}));
-        assertThat(e).hasMessageThat()
+        assertThat(e)
+                .hasMessageThat()
                 .contains("android.permission.USE_CUSTOM_VIRTUAL_MACHINE permission");
     }
 
     @Test
-    @CddTest(requirements = {
-            "9.17/C-1-1",
-    })
+    @CddTest(
+            requirements = {
+                "9.17/C-1-1",
+            })
     public void deleteVm() throws Exception {
         assumeSupportedDevice();
 
@@ -954,9 +957,10 @@
     }
 
     @Test
-    @CddTest(requirements = {
-            "9.17/C-1-1",
-    })
+    @CddTest(
+            requirements = {
+                "9.17/C-1-1",
+            })
     public void validApkPathIsAccepted() throws Exception {
         assumeSupportedDevice();
 
@@ -989,10 +993,7 @@
     }
 
     @Test
-    @CddTest(requirements = {
-            "9.17/C-1-1",
-            "9.17/C-2-1"
-    })
+    @CddTest(requirements = {"9.17/C-1-1", "9.17/C-2-1"})
     public void extraApk() throws Exception {
         assumeSupportedDevice();
 
@@ -1044,7 +1045,7 @@
 
     @Test
     public void bootFailsWhenLowMem() throws Exception {
-        for (int memMib : new int[]{ 10, 20, 40 }) {
+        for (int memMib : new int[] {10, 20, 40}) {
             VirtualMachineConfig lowMemConfig =
                     newVmConfigBuilderWithPayloadBinary("MicrodroidTestNativeLib.so")
                             .setMemoryBytes(memMib)
@@ -1061,8 +1062,9 @@
                             onPayloadReadyExecuted.complete(true);
                             super.onPayloadReady(vm);
                         }
+
                         @Override
-                        public void onStopped(VirtualMachine vm,  int reason) {
+                        public void onStopped(VirtualMachine vm, int reason) {
                             onStoppedExecuted.complete(true);
                             super.onStopped(vm, reason);
                         }
@@ -1210,16 +1212,14 @@
     }
 
     @Test
-    @CddTest(requirements = {
-            "9.17/C-1-1",
-            "9.17/C-2-7"
-    })
+    @CddTest(requirements = {"9.17/C-1-1", "9.17/C-2-7"})
     public void instancesOfSameVmHaveDifferentCdis() throws Exception {
         assumeSupportedDevice();
         // TODO(b/325094712): VMs on CF with same payload have the same secret. This is because
         // `instance-id` which is input to DICE is contained in DT which is missing in CF.
         assumeFalse(
-                "Cuttlefish doesn't support device tree under /proc/device-tree", isCuttlefish());
+                "Cuttlefish/Goldfish doesn't support device tree under /proc/device-tree",
+                isCuttlefish() || isGoldfish());
 
         grantPermission(VirtualMachine.USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION);
         VirtualMachineConfig normalConfig =
@@ -1239,10 +1239,7 @@
     }
 
     @Test
-    @CddTest(requirements = {
-            "9.17/C-1-1",
-            "9.17/C-2-7"
-    })
+    @CddTest(requirements = {"9.17/C-1-1", "9.17/C-2-7"})
     public void sameInstanceKeepsSameCdis() throws Exception {
         assumeSupportedDevice();
         assume().withMessage("Skip on CF. Too Slow. b/257270529").that(isCuttlefish()).isFalse();
@@ -1338,10 +1335,7 @@
     }
 
     @Test
-    @CddTest(requirements = {
-            "9.17/C-1-1",
-            "9.17/C-1-2"
-    })
+    @CddTest(requirements = {"9.17/C-1-1", "9.17/C-1-2"})
     public void accessToCdisIsRestricted() throws Exception {
         assumeSupportedDevice();
 
@@ -1398,8 +1392,7 @@
 
     private void assertThatPartitionIsMissing(UUID partitionUuid) throws Exception {
         RandomAccessFile instanceFile = prepareInstanceImage("test_vm_integrity");
-        assertThat(findPartitionDataOffset(instanceFile, partitionUuid).isPresent())
-                .isFalse();
+        assertThat(findPartitionDataOffset(instanceFile, partitionUuid).isPresent()).isFalse();
     }
 
     // Flips a bit of given partition, and then see if boot fails.
@@ -1419,10 +1412,7 @@
     }
 
     @Test
-    @CddTest(requirements = {
-            "9.17/C-1-1",
-            "9.17/C-2-7"
-    })
+    @CddTest(requirements = {"9.17/C-1-1", "9.17/C-2-7"})
     public void bootFailsWhenMicrodroidDataIsCompromised() throws Exception {
         // If Updatable VM is supported => No instance.img required
         assumeNoUpdatableVmSupport();
@@ -1430,10 +1420,7 @@
     }
 
     @Test
-    @CddTest(requirements = {
-            "9.17/C-1-1",
-            "9.17/C-2-7"
-    })
+    @CddTest(requirements = {"9.17/C-1-1", "9.17/C-2-7"})
     public void bootFailsWhenPvmFwDataIsCompromised() throws Exception {
         // If Updatable VM is supported => No instance.img required
         assumeNoUpdatableVmSupport();
@@ -1455,8 +1442,8 @@
 
         BootResult bootResult = tryBootVmWithConfig(config, "test_vm_invalid_config");
         assertThat(bootResult.payloadStarted).isFalse();
-        assertThat(bootResult.deathReason).isEqualTo(
-                VirtualMachineCallback.STOP_REASON_MICRODROID_INVALID_PAYLOAD_CONFIG);
+        assertThat(bootResult.deathReason)
+                .isEqualTo(VirtualMachineCallback.STOP_REASON_MICRODROID_INVALID_PAYLOAD_CONFIG);
     }
 
     @Test
@@ -1693,7 +1680,8 @@
         // TODO(b/325094712): VMs on CF with same payload have the same secret. This is because
         // `instance-id` which is input to DICE is contained in DT which is missing in CF.
         assumeFalse(
-                "Cuttlefish doesn't support device tree under /proc/device-tree", isCuttlefish());
+                "Cuttlefish/Goldfish doesn't support device tree under /proc/device-tree",
+                isCuttlefish() || isGoldfish());
 
         VirtualMachineConfig config =
                 newVmConfigBuilderWithPayloadBinary("MicrodroidTestNativeLib.so")
@@ -2141,7 +2129,6 @@
         IVmShareTestService service = connection.waitForService();
         assertWithMessage("Timed out connecting to " + serviceIntent).that(service).isNotNull();
 
-
         try {
             ITestService testServiceProxy = transferAndStartVm(service, vmDesc, "vm_to_share");
 
@@ -2401,7 +2388,8 @@
         assumeSupportedDevice();
         // TODO(b/325094712): Boot fails with vendor partition in Cuttlefish.
         assumeFalse(
-                "Cuttlefish doesn't support device tree under /proc/device-tree", isCuttlefish());
+                "Cuttlefish/Goldfish doesn't support device tree under /proc/device-tree",
+                isCuttlefish() || isGoldfish());
         // TODO(b/317567210): Boot fails with vendor partition in HWASAN enabled microdroid
         // after introducing verification based on DT and fstab in microdroid vendor partition.
         assumeFalse(
@@ -2624,16 +2612,15 @@
     }
 
     private long minMemoryRequired() {
-      assertThat(Build.SUPPORTED_ABIS).isNotEmpty();
-      String primaryAbi = Build.SUPPORTED_ABIS[0];
-      switch (primaryAbi) {
-        case "x86_64":
-          return MIN_MEM_X86_64;
-        case "arm64-v8a":
-        case "arm64-v8a-hwasan":
-          return MIN_MEM_ARM64;
-      }
-      throw new AssertionError("Unsupported ABI: " + primaryAbi);
+        assertThat(Build.SUPPORTED_ABIS).isNotEmpty();
+        String primaryAbi = Build.SUPPORTED_ABIS[0];
+        switch (primaryAbi) {
+            case "x86_64":
+                return MIN_MEM_X86_64;
+            case "arm64-v8a":
+            case "arm64-v8a-hwasan":
+                return MIN_MEM_ARM64;
+        }
+        throw new AssertionError("Unsupported ABI: " + primaryAbi);
     }
-
 }
diff --git a/tests/testapk_no_perm/src/java/com/android/microdroid/test/MicrodroidTestAppNoPerm.java b/tests/testapk_no_perm/src/java/com/android/microdroid/test/MicrodroidTestAppNoPerm.java
index 1772e6b..27e26e5 100644
--- a/tests/testapk_no_perm/src/java/com/android/microdroid/test/MicrodroidTestAppNoPerm.java
+++ b/tests/testapk_no_perm/src/java/com/android/microdroid/test/MicrodroidTestAppNoPerm.java
@@ -16,18 +16,19 @@
 
 package com.android.microdroid.test;
 
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+
 import android.system.virtualmachine.VirtualMachineConfig;
 
 import com.android.compatibility.common.util.CddTest;
 import com.android.microdroid.test.device.MicrodroidDeviceTestBase;
 
-import static com.google.common.truth.Truth.assertThat;
-import static org.junit.Assert.assertThrows;
-
 import org.junit.Before;
-import org.junit.runners.Parameterized;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
 
 /**
  * Test that the android.permission.MANAGE_VIRTUAL_MACHINE is enforced and that an app cannot launch
diff --git a/tests/vm_accessor/README.md b/tests/vm_accessor/README.md
new file mode 100644
index 0000000..c85cf3c
--- /dev/null
+++ b/tests/vm_accessor/README.md
@@ -0,0 +1,40 @@
+# Demo for serving a service in a VM
+
+You can implement a service in a VM, and let client in the Android can use it
+as if it's in the Android. To do so, implement IAccessor.
+
+IAccessor allows AIDL service in a VM can be accessed via servicemanager.
+To do so, VM owners should also provide IAccessor implementation. servicemanager
+will connect to the IAccessor and get the binder of the service in a VM with it.
+
+com.android.virt.accessor_demo apex contains the minimum setup for IAccessor as
+follows:
+  - accessor_demo: Sample implementation of IAccessor, which is expected to
+      launch VM and returns the Vsock connection of service in the VM.
+  - AccessorVmApp: Sample app that conatins VM payload. Provides the actual
+      implementation of service in a VM.
+
+## Build
+
+You need to do envsetup.sh
+```shell
+m com.android.virt.accessor_demo
+```
+
+## Install (requires userdebug build)
+
+For very first install,
+
+```shell
+adb remount -R || adb wait-for-device  # Remount to push apex to /system_ext
+adb root && adb remount                # Ensure it's rebooted.
+adb push $ANDROID_PRODUCT_OUT/system_ext/apex/com.android.virt.accessor_demo.apex /system_ext/apex
+adb reboot && adb wait-for-device      # Ensure that newly pushed apex at /system_ext is installed
+```
+
+Once it's installed, you can simply use `adb install` for update.
+
+```shell
+adb install $ANDROID_PRODUCT_OUT/system_ext/com.android.virt.accessor_demo.apex
+adb reboot
+```
diff --git a/tests/vm_accessor/accessor/Android.bp b/tests/vm_accessor/accessor/Android.bp
new file mode 100644
index 0000000..7c0ee6d
--- /dev/null
+++ b/tests/vm_accessor/accessor/Android.bp
@@ -0,0 +1,31 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+rust_binary {
+    name: "accessor_demo",
+    crate_name: "accessor_demo",
+    srcs: ["src/main.rs"],
+    edition: "2021",
+    prefer_rlib: true,
+    defaults: ["avf_build_flags_rust"], // for reading llvm_changes
+    apex_available: [
+        "com.android.virt.accessor_demo",
+    ],
+    rustlibs: [
+        "android.system.virtualizationservice-rust",
+        "android.os.accessor-rust",
+        "libanyhow",
+        "libandroid_logger",
+        "libbinder_rs",
+        "libenv_logger",
+        "libglob",
+        "libhypervisor_props",
+        "liblog_rust",
+        "libmicrodroid_payload_config",
+        "librand",
+        "libvmconfig",
+        "libvmclient",
+        "libnix",
+    ],
+}
diff --git a/tests/vm_accessor/accessor/src/accessor.rs b/tests/vm_accessor/accessor/src/accessor.rs
new file mode 100644
index 0000000..6a9ced6
--- /dev/null
+++ b/tests/vm_accessor/accessor/src/accessor.rs
@@ -0,0 +1,52 @@
+// Copyright 2024, 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.
+
+//! IAcessor implementation.
+//! TODO: Keep this in proper places, so other pVMs can use this.
+//! TODO: Allows to customize VMs for launching. (e.g. port, ...)
+
+use android_os_accessor::aidl::android::os::IAccessor::IAccessor;
+use binder::{self, Interface, ParcelFileDescriptor};
+use log::info;
+use std::time::Duration;
+use vmclient::VmInstance;
+
+// Note: Do not use LazyServiceGuard here, to make this service and VM are quit
+//       when nobody references it.
+// TODO(b/353492849): Do not use IAccessor directly.
+#[derive(Debug)]
+pub struct Accessor {
+    // Note: we can't simply keep reference by specifying lifetime to Accessor,
+    //       because 'trait Interface' requires 'static.
+    vm: VmInstance,
+    port: i32,
+}
+
+impl Accessor {
+    pub fn new(vm: VmInstance, port: i32) -> Self {
+        Self { vm, port }
+    }
+}
+
+impl Interface for Accessor {}
+
+impl IAccessor for Accessor {
+    fn addConnection(&self) -> binder::Result<ParcelFileDescriptor> {
+        self.vm.wait_until_ready(Duration::from_secs(10)).unwrap();
+
+        info!("VM is ready. Connecting to service via port {}", self.port);
+
+        self.vm.vm.connectVsock(self.port)
+    }
+}
diff --git a/tests/vm_accessor/accessor/src/main.rs b/tests/vm_accessor/accessor/src/main.rs
new file mode 100644
index 0000000..27ce415
--- /dev/null
+++ b/tests/vm_accessor/accessor/src/main.rs
@@ -0,0 +1,55 @@
+// Copyright 2024, 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.
+
+//! Android VM control tool.
+
+mod accessor;
+mod run;
+
+use accessor::Accessor;
+use android_os_accessor::aidl::android::os::IAccessor::BnAccessor;
+use anyhow::Error;
+use anyhow::{anyhow, bail};
+use binder::{BinderFeatures, ProcessState};
+use log::info;
+use run::run_vm;
+
+// Private contract between IAccessor impl and VM service.
+const PORT: i32 = 5678;
+
+// MUST match with VINTF and init.rc
+// TODO(b/354632613): Get this from VINTF
+const SERVICE_NAME: &str = "android.os.IAccessor/IAccessorVmService/default";
+
+fn main() -> Result<(), Error> {
+    android_logger::init_once(
+        android_logger::Config::default()
+            .with_tag("accessor_demo")
+            .with_max_level(log::LevelFilter::Debug),
+    );
+
+    let vm = run_vm()?;
+
+    // If you want to serve multiple services in a VM, then register Accessor impls multiple times.
+    let accessor = Accessor::new(vm, PORT);
+    let accessor_binder = BnAccessor::new_binder(accessor, BinderFeatures::default());
+    binder::register_lazy_service(SERVICE_NAME, accessor_binder.as_binder()).map_err(|e| {
+        anyhow!("Failed to register lazy service, service={SERVICE_NAME}, err={e:?}",)
+    })?;
+    info!("service {SERVICE_NAME} is registered as lazy service");
+
+    ProcessState::join_thread_pool();
+
+    bail!("Thread pool unexpectedly ended")
+}
diff --git a/tests/vm_accessor/accessor/src/run.rs b/tests/vm_accessor/accessor/src/run.rs
new file mode 100644
index 0000000..932baab
--- /dev/null
+++ b/tests/vm_accessor/accessor/src/run.rs
@@ -0,0 +1,181 @@
+// Copyright 2024, 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.
+
+//! Command to run a VM.
+
+use android_system_virtualizationservice::aidl::android::system::virtualizationservice::{
+    IVirtualizationService::IVirtualizationService,
+    PartitionType::PartitionType,
+    VirtualMachineAppConfig::{DebugLevel::DebugLevel, Payload::Payload, VirtualMachineAppConfig},
+    VirtualMachineConfig::VirtualMachineConfig,
+    VirtualMachinePayloadConfig::VirtualMachinePayloadConfig,
+};
+use anyhow::{bail, Context, Error};
+use binder::{ParcelFileDescriptor, Strong};
+use glob::glob;
+use log::{error, info};
+use rand::{distributions::Alphanumeric, Rng};
+use std::fs::{self, File};
+use std::io::{self, BufRead, BufReader};
+use std::path::PathBuf;
+use std::thread;
+use vmclient::{ErrorCode, VmInstance};
+use vmconfig::open_parcel_file;
+
+// These are private contract between IAccessor impl and VM service.
+const PAYLOAD_BINARY_NAME: &str = "libaccessor_vm_payload.so";
+const VM_OS_NAME: &str = "microdroid";
+
+const INSTANCE_FILE_SIZE: u64 = 10 * 1024 * 1024;
+
+fn get_service() -> Result<Strong<dyn IVirtualizationService>, Error> {
+    let virtmgr =
+        vmclient::VirtualizationService::new().context("Failed to spawn VirtualizationService")?;
+    virtmgr.connect().context("Failed to connect to VirtualizationService")
+}
+
+fn find_vm_apk_path() -> Result<PathBuf, Error> {
+    const GLOB_PATTERN: &str = "/apex/com.android.virt.accessor_demo/app/**/AccessorVmApp*.apk";
+    let mut entries: Vec<PathBuf> =
+        glob(GLOB_PATTERN).context("failed to glob")?.filter_map(|e| e.ok()).collect();
+    if entries.len() > 1 {
+        bail!("Found more than one apk matching {}", GLOB_PATTERN);
+    }
+    if let Some(path) = entries.pop() {
+        info!("Found accessor apk at {path:?}");
+        Ok(path)
+    } else {
+        bail!("No apks match {}", GLOB_PATTERN)
+    }
+}
+
+fn create_work_dir() -> Result<PathBuf, Error> {
+    let s: String =
+        rand::thread_rng().sample_iter(&Alphanumeric).take(17).map(char::from).collect();
+    let work_dir = PathBuf::from("/data/local/tmp/microdroid").join(s);
+    info!("creating work dir {}", work_dir.display());
+    fs::create_dir_all(&work_dir).context("failed to mkdir")?;
+    Ok(work_dir)
+}
+
+/// Run a VM with Microdroid
+pub fn run_vm() -> Result<VmInstance, Error> {
+    let service = get_service()?;
+
+    let apk = File::open(find_vm_apk_path()?).context("Failed to open APK file")?;
+    let apk_fd = ParcelFileDescriptor::new(apk);
+
+    let work_dir = create_work_dir()?;
+    info!("work dir: {}", work_dir.display());
+
+    let idsig =
+        File::create_new(work_dir.join("apk.idsig")).context("Failed to create idsig file")?;
+    let idsig_fd = ParcelFileDescriptor::new(idsig);
+    service.createOrUpdateIdsigFile(&apk_fd, &idsig_fd)?;
+
+    let instance_img_path = work_dir.join("instance.img");
+    let instance_img =
+        File::create_new(&instance_img_path).context("Failed to create instance.img file")?;
+    service.initializeWritablePartition(
+        &ParcelFileDescriptor::new(instance_img),
+        INSTANCE_FILE_SIZE.try_into()?,
+        PartitionType::ANDROID_VM_INSTANCE,
+    )?;
+    info!("created instance image at: {instance_img_path:?}");
+
+    let instance_id = if cfg!(llpvm_changes) {
+        let id = service.allocateInstanceId().context("Failed to allocate instance_id")?;
+        fs::write(work_dir.join("instance_id"), id)?;
+        id
+    } else {
+        // if llpvm feature flag is disabled, instance_id is not used.
+        [0u8; 64]
+    };
+
+    let payload = Payload::PayloadConfig(VirtualMachinePayloadConfig {
+        payloadBinaryName: PAYLOAD_BINARY_NAME.to_owned(),
+        extraApks: Default::default(),
+    });
+
+    let vm_config = VirtualMachineConfig::AppConfig(VirtualMachineAppConfig {
+        name: String::from("AccessorVm"),
+        apk: apk_fd.into(),
+        idsig: idsig_fd.into(),
+        extraIdsigs: Default::default(),
+        instanceImage: open_parcel_file(&instance_img_path, true /* writable */)?.into(),
+        instanceId: instance_id,
+        payload,
+        osName: VM_OS_NAME.to_owned(),
+        debugLevel: DebugLevel::FULL,
+        ..Default::default()
+    });
+
+    info!("creating VM");
+    let vm = VmInstance::create(
+        service.as_ref(),
+        &vm_config,
+        Some(android_log_fd()?), /* console_out */
+        None,                    /* console_in */
+        Some(android_log_fd()?), /* log */
+        Some(Box::new(Callback {})),
+    )
+    .context("Failed to create VM")?;
+    vm.start().context("Failed to start VM")?;
+
+    info!("started IAccessor VM with CID {}", vm.cid());
+
+    Ok(vm)
+}
+
+struct Callback {}
+
+impl vmclient::VmCallback for Callback {
+    fn on_payload_started(&self, _cid: i32) {
+        info!("payload started");
+    }
+
+    fn on_payload_ready(&self, _cid: i32) {
+        info!("payload is ready");
+    }
+
+    fn on_payload_finished(&self, _cid: i32, exit_code: i32) {
+        info!("payload finished with exit code {}", exit_code);
+    }
+
+    fn on_error(&self, _cid: i32, error_code: ErrorCode, message: &str) {
+        error!("VM encountered an error: code={:?}, message={}", error_code, message);
+    }
+}
+
+/// This function is only exposed for testing.
+/// Production code prefer not expose logs from VM.
+fn android_log_fd() -> io::Result<File> {
+    let (reader_fd, writer_fd) = nix::unistd::pipe()?;
+
+    let reader = File::from(reader_fd);
+    let writer = File::from(writer_fd);
+
+    thread::spawn(|| {
+        for line in BufReader::new(reader).lines() {
+            match line {
+                Ok(l) => info!("{}", l),
+                Err(e) => {
+                    error!("Failed to read line from VM: {e:?}");
+                    break;
+                }
+            }
+        }
+    });
+    Ok(writer)
+}
diff --git a/tests/vm_accessor/accessor_vm/Android.bp b/tests/vm_accessor/accessor_vm/Android.bp
new file mode 100644
index 0000000..bbd15bd
--- /dev/null
+++ b/tests/vm_accessor/accessor_vm/Android.bp
@@ -0,0 +1,30 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_app {
+    name: "AccessorVmApp",
+    installable: true,
+    jni_libs: ["libaccessor_vm_payload"],
+    jni_uses_platform_apis: true,
+    use_embedded_native_libs: true,
+    sdk_version: "system_current",
+    compile_multilib: "first",
+    apex_available: ["com.android.virt.accessor_demo"],
+}
+
+rust_ffi {
+    name: "libaccessor_vm_payload",
+    crate_name: "accessor_vm_payload",
+    defaults: ["avf_build_flags_rust"],
+    srcs: ["src/main.rs"],
+    prefer_rlib: true,
+    rustlibs: [
+        "com.android.virt.accessor_demo.vm_service-rust",
+        "libandroid_logger",
+        "libanyhow",
+        "liblog_rust",
+        "libvm_payload_rs",
+    ],
+    apex_available: ["com.android.virt.accessor_demo"],
+}
diff --git a/tests/vm_accessor/accessor_vm/AndroidManifest.xml b/tests/vm_accessor/accessor_vm/AndroidManifest.xml
new file mode 100644
index 0000000..429e08a
--- /dev/null
+++ b/tests/vm_accessor/accessor_vm/AndroidManifest.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2024 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.virtualization.accessor_demo">
+     <application android:hasCode="false"/>
+</manifest>
diff --git a/tests/vm_accessor/accessor_vm/assets/config.json b/tests/vm_accessor/accessor_vm/assets/config.json
new file mode 100644
index 0000000..f921ec1
--- /dev/null
+++ b/tests/vm_accessor/accessor_vm/assets/config.json
@@ -0,0 +1,10 @@
+{
+    "os": {
+        "name": "microdroid"
+    },
+    "task": {
+        "type": "microdroid_launcher",
+        "command": "accessor_vm.so"
+    },
+    "export_tombstones": true
+}
diff --git a/tests/vm_accessor/accessor_vm/src/main.rs b/tests/vm_accessor/accessor_vm/src/main.rs
new file mode 100644
index 0000000..bd83cc1
--- /dev/null
+++ b/tests/vm_accessor/accessor_vm/src/main.rs
@@ -0,0 +1,64 @@
+// Copyright 2024, 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.
+
+//! VM with the simplest service for IAccessor demo
+
+use anyhow::Result;
+use com_android_virt_accessor_demo_vm_service::{
+    aidl::com::android::virt::accessor_demo::vm_service::IAccessorVmService::{
+        BnAccessorVmService, IAccessorVmService,
+    },
+    binder::{self, BinderFeatures, Interface, Strong},
+};
+use log::{error, info};
+
+// Private contract between IAccessor impl and VM service.
+const PORT: u32 = 5678;
+
+vm_payload::main!(main);
+
+// Entry point of the Service VM client.
+fn main() {
+    android_logger::init_once(
+        android_logger::Config::default()
+            .with_tag("accessor_vm")
+            .with_max_level(log::LevelFilter::Debug),
+    );
+    if let Err(e) = try_main() {
+        error!("failed with {:?}", e);
+        std::process::exit(1);
+    }
+}
+
+fn try_main() -> Result<()> {
+    info!("Starting stub payload for IAccessor demo");
+
+    vm_payload::run_single_vsock_service(AccessorVmService::new_binder(), PORT)
+}
+
+struct AccessorVmService {}
+
+impl Interface for AccessorVmService {}
+
+impl AccessorVmService {
+    fn new_binder() -> Strong<dyn IAccessorVmService> {
+        BnAccessorVmService::new_binder(AccessorVmService {}, BinderFeatures::default())
+    }
+}
+
+impl IAccessorVmService for AccessorVmService {
+    fn add(&self, a: i32, b: i32) -> binder::Result<i32> {
+        Ok(a + b)
+    }
+}
diff --git a/tests/vm_accessor/aidl/Android.bp b/tests/vm_accessor/aidl/Android.bp
new file mode 100644
index 0000000..0078608
--- /dev/null
+++ b/tests/vm_accessor/aidl/Android.bp
@@ -0,0 +1,20 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+aidl_interface {
+    name: "com.android.virt.accessor_demo.vm_service",
+    srcs: ["**/*.aidl"],
+    unstable: true,
+    backend: {
+        java: {
+            gen_rpc: true,
+        },
+        rust: {
+            enabled: true,
+            apex_available: [
+                "com.android.virt.accessor_demo",
+            ],
+        },
+    },
+}
diff --git a/rialto/image.ld b/tests/vm_accessor/aidl/com/android/virt/accessor_demo/vm_service/IAccessorVmService.aidl
similarity index 62%
copy from rialto/image.ld
copy to tests/vm_accessor/aidl/com/android/virt/accessor_demo/vm_service/IAccessorVmService.aidl
index 368acbb..29bf979 100644
--- a/rialto/image.ld
+++ b/tests/vm_accessor/aidl/com/android/virt/accessor_demo/vm_service/IAccessorVmService.aidl
@@ -1,11 +1,11 @@
 /*
- * Copyright 2022 The Android Open Source Project
+ * Copyright 2024 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
  *
- *     https://www.apache.org/licenses/LICENSE-2.0
+ *      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,
@@ -14,9 +14,10 @@
  * limitations under the License.
  */
 
-MEMORY
-{
-	dtb_region	: ORIGIN = 0x80000000, LENGTH = 2M
-	image		: ORIGIN = 0x80200000, LENGTH = 2M
-	writable_data	: ORIGIN = 0x80400000, LENGTH = 2M
+package com.android.virt.accessor_demo.vm_service;
+
+/** {@hide} */
+// TODO(b/349578050): Add more methods that take or return another binder.
+interface IAccessorVmService {
+    int add(int a, int b);
 }
diff --git a/tests/vm_accessor/apex/Android.bp b/tests/vm_accessor/apex/Android.bp
new file mode 100644
index 0000000..e954572
--- /dev/null
+++ b/tests/vm_accessor/apex/Android.bp
@@ -0,0 +1,52 @@
+// Copyright 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+// apex_test allows to skips apex_available checks for dependencies.
+// However, real apex should add itself to apex_available for all dependencies.
+apex_test {
+    name: "com.android.virt.accessor_demo",
+    manifest: "manifest.json",
+    file_contexts: "accessor_demo-file_contexts",
+
+    // You probably need your own key
+    key: "com.android.virt.key",
+
+    updatable: false,
+    future_updatable: false,
+    platform_apis: true,
+    system_ext_specific: true,
+
+    binaries: ["accessor_demo"],
+    apps: ["AccessorVmApp"],
+    prebuilts: [
+        "accessor_demo.init.rc",
+        "accessor_demo.xml",
+    ],
+}
+
+prebuilt_etc {
+    name: "accessor_demo.init.rc",
+    src: "accessor_demo.init.rc",
+    installable: false,
+}
+
+prebuilt_etc {
+    name: "accessor_demo.xml",
+    src: "accessor_demo.xml",
+    sub_dir: "vintf",
+    installable: false,
+}
diff --git a/tests/vm_accessor/apex/accessor_demo-file_contexts b/tests/vm_accessor/apex/accessor_demo-file_contexts
new file mode 100644
index 0000000..2007157
--- /dev/null
+++ b/tests/vm_accessor/apex/accessor_demo-file_contexts
@@ -0,0 +1,3 @@
+# TODO: Give proper label
+(/.*)?                         u:object_r:system_file:s0
+/bin/accessor_demo             u:object_r:virtualizationservice_exec:s0
diff --git a/tests/vm_accessor/apex/accessor_demo.init.rc b/tests/vm_accessor/apex/accessor_demo.init.rc
new file mode 100644
index 0000000..f3dfae9
--- /dev/null
+++ b/tests/vm_accessor/apex/accessor_demo.init.rc
@@ -0,0 +1,20 @@
+# Copyright 2024 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.
+
+# Register this as lazy service, so IAccessor and its VM is only running while binder is in use.
+service accessor_demo /apex/com.android.virt.accessor_demo/bin/accessor_demo
+    disabled
+    oneshot
+    # MUST match with VINTF and accessor/src/main.rs
+    interface aidl android.os.IAccessor/IAccessorVmService/default
diff --git a/tests/vm_accessor/apex/accessor_demo.xml b/tests/vm_accessor/apex/accessor_demo.xml
new file mode 100644
index 0000000..e9df3df
--- /dev/null
+++ b/tests/vm_accessor/apex/accessor_demo.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2024 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 version="1.0" type="framework">
+    <hal format="aidl">
+        <name>com.android.virt.accessor_demo.vm_service</name>
+        <version>1</version>
+        <fqname>IAccessorVmService/default</fqname>
+        <!-- MUST match with init.rc and accessor/src/main.rs -->
+        <accessor>android.os.IAccessor/IAccessorVmService/default</accessor>
+    </hal>
+</manifest>
\ No newline at end of file
diff --git a/tests/vm_accessor/apex/manifest.json b/tests/vm_accessor/apex/manifest.json
new file mode 100644
index 0000000..a09523a
--- /dev/null
+++ b/tests/vm_accessor/apex/manifest.json
@@ -0,0 +1,4 @@
+{
+  "name": "com.android.virt.accessor_demo",
+  "version": 1
+}
diff --git a/tests/vm_accessor/test/Android.bp b/tests/vm_accessor/test/Android.bp
new file mode 100644
index 0000000..71746c7
--- /dev/null
+++ b/tests/vm_accessor/test/Android.bp
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package {
+    default_team: "trendy_team_virtualization",
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+rust_test {
+    name: "vm_accessor_test",
+    srcs: ["src/test.rs"],
+    defaults: [
+        "rdroidtest.defaults",
+    ],
+    test_suites: [
+        "general-tests",
+    ],
+    test_config: "AndroidTest.xml",
+    rustlibs: [
+        "com.android.virt.accessor_demo.vm_service-rust",
+        "libbinder_rs",
+        "liblog_rust",
+    ],
+    data: [":com.android.virt.accessor_demo"],
+    compile_multilib: "first",
+}
diff --git a/tests/vm_accessor/test/AndroidTest.xml b/tests/vm_accessor/test/AndroidTest.xml
new file mode 100644
index 0000000..4cbf9ec
--- /dev/null
+++ b/tests/vm_accessor/test/AndroidTest.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2024 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.
+-->
+<configuration description="Accessor demo test">
+  <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+  <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+  <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+
+  <!-- Install APEX begins -->
+  <!-- Step 0: adb reboot, so PushFilePreparer can remount system if needed -->
+  <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer">
+    <option name="force-root" value="true"/>
+  </target_preparer>
+  <!-- Step 1: Push for the very first install. -->
+  <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+    <option name="abort-on-push-failure" value="true" />
+    <!-- Disclaimer: 'remount-system' remounts all partitions (adb remount),
+         but do so after checking the verity of /system partition.
+         This works for now, but may misbehave in the future. -->
+    <option name="remount-system" value="true" />
+    <option name="push-file" key="com.android.virt.accessor_demo.apex" value="/system_ext/apex/com.android.virt.accessor_demo.apex" />
+  </target_preparer>
+  <!-- Step 2: Reboot for pushed APEX to be installed. -->
+  <target_preparer class="com.android.tradefed.targetprep.RebootTargetPreparer" />
+
+  <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer" />
+  <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+    <option name="abort-on-push-failure" value="true" />
+    <option name="push-file" key="vm_accessor_test" value="/data/local/tmp/vm_accessor_test" />
+  </target_preparer>
+
+  <!-- TODO(b/346763236): Remove this -->
+  <target_preparer class="com.android.tradefed.targetprep.DisableSELinuxTargetPreparer" />
+
+  <test class="com.android.tradefed.testtype.rust.RustBinaryTest" >
+    <option name="test-device-path" value="/data/local/tmp" />
+    <option name="module-name" value="vm_accessor_test" />
+  </test>
+</configuration>
diff --git a/tests/vm_accessor/test/src/test.rs b/tests/vm_accessor/test/src/test.rs
new file mode 100644
index 0000000..d521acf
--- /dev/null
+++ b/tests/vm_accessor/test/src/test.rs
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2024 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.
+ */
+
+//! Test end-to-end IAccessor implementation with accessor_demo.
+
+use com_android_virt_accessor_demo_vm_service::aidl::com::android::virt::accessor_demo::vm_service::IAccessorVmService::IAccessorVmService;
+use binder::{Strong, ProcessState};
+use rdroidtest::rdroidtest;
+
+const VM_SERVICE: &str = "com.android.virt.accessor_demo.vm_service.IAccessorVmService/default";
+
+fn init() {
+    ProcessState::set_thread_pool_max_thread_count(5);
+    ProcessState::start_thread_pool();
+}
+
+fn wait_for_interface() -> Strong<dyn IAccessorVmService> {
+    binder::wait_for_interface(VM_SERVICE).unwrap()
+}
+
+fn get_interface() -> Strong<dyn IAccessorVmService> {
+    binder::get_interface(VM_SERVICE).unwrap()
+}
+
+fn check_interface() -> Strong<dyn IAccessorVmService> {
+    binder::check_interface(VM_SERVICE).unwrap()
+}
+
+#[rdroidtest]
+fn test_wait_for_interface() {
+    init();
+
+    let service = wait_for_interface();
+    let sum = service.add(11, 12).unwrap();
+
+    assert_eq!(sum, 23);
+}
+
+#[rdroidtest]
+fn test_wait_for_interface_twice() {
+    init();
+
+    let service1 = wait_for_interface();
+    let service2 = wait_for_interface();
+
+    assert_eq!(service1.add(11, 12).unwrap(), 23);
+    assert_eq!(service2.add(11, 12).unwrap(), 23);
+}
+
+#[rdroidtest]
+fn test_wait_and_get_interface() {
+    init();
+
+    let service1 = wait_for_interface();
+    let service2 = get_interface();
+
+    assert_eq!(service1.add(11, 12).unwrap(), 23);
+    assert_eq!(service2.add(11, 12).unwrap(), 23);
+}
+
+#[rdroidtest]
+fn test_wait_and_check_interface() {
+    init();
+
+    let service1 = wait_for_interface();
+    let service2 = check_interface();
+
+    assert_eq!(service1.add(11, 12).unwrap(), 23);
+    assert_eq!(service2.add(11, 12).unwrap(), 23);
+}
+
+rdroidtest::test_main!();
diff --git a/service_vm/test_apk/Android.bp b/tests/vm_attestation/Android.bp
similarity index 100%
rename from service_vm/test_apk/Android.bp
rename to tests/vm_attestation/Android.bp
diff --git a/service_vm/test_apk/AndroidManifest.rkpd.xml b/tests/vm_attestation/AndroidManifest.rkpd.xml
similarity index 100%
rename from service_vm/test_apk/AndroidManifest.rkpd.xml
rename to tests/vm_attestation/AndroidManifest.rkpd.xml
diff --git a/service_vm/test_apk/AndroidManifest.xml b/tests/vm_attestation/AndroidManifest.xml
similarity index 100%
rename from service_vm/test_apk/AndroidManifest.xml
rename to tests/vm_attestation/AndroidManifest.xml
diff --git a/service_vm/test_apk/AndroidTest.rkpd.xml b/tests/vm_attestation/AndroidTest.rkpd.xml
similarity index 100%
rename from service_vm/test_apk/AndroidTest.rkpd.xml
rename to tests/vm_attestation/AndroidTest.rkpd.xml
diff --git a/service_vm/test_apk/AndroidTest.xml b/tests/vm_attestation/AndroidTest.xml
similarity index 100%
rename from service_vm/test_apk/AndroidTest.xml
rename to tests/vm_attestation/AndroidTest.xml
diff --git a/service_vm/test_apk/aidl/Android.bp b/tests/vm_attestation/aidl/Android.bp
similarity index 100%
rename from service_vm/test_apk/aidl/Android.bp
rename to tests/vm_attestation/aidl/Android.bp
diff --git a/service_vm/test_apk/aidl/com/android/virt/vm_attestation/testservice/IAttestationService.aidl b/tests/vm_attestation/aidl/com/android/virt/vm_attestation/testservice/IAttestationService.aidl
similarity index 100%
rename from service_vm/test_apk/aidl/com/android/virt/vm_attestation/testservice/IAttestationService.aidl
rename to tests/vm_attestation/aidl/com/android/virt/vm_attestation/testservice/IAttestationService.aidl
diff --git a/service_vm/test_apk/src/java/com/android/virt/rkpd/vm_attestation/testapp/RkpdVmAttestationTest.java b/tests/vm_attestation/src/java/com/android/virt/rkpd/vm_attestation/testapp/RkpdVmAttestationTest.java
similarity index 99%
rename from service_vm/test_apk/src/java/com/android/virt/rkpd/vm_attestation/testapp/RkpdVmAttestationTest.java
rename to tests/vm_attestation/src/java/com/android/virt/rkpd/vm_attestation/testapp/RkpdVmAttestationTest.java
index f456cb4..b41323f 100644
--- a/service_vm/test_apk/src/java/com/android/virt/rkpd/vm_attestation/testapp/RkpdVmAttestationTest.java
+++ b/tests/vm_attestation/src/java/com/android/virt/rkpd/vm_attestation/testapp/RkpdVmAttestationTest.java
@@ -20,17 +20,17 @@
 
 import static com.google.common.truth.TruthJUnit.assume;
 
-import android.net.ConnectivityManager;
-import android.net.NetworkCapabilities;
-import android.net.Network;
 import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.Network;
+import android.net.NetworkCapabilities;
 import android.system.virtualmachine.VirtualMachine;
 import android.system.virtualmachine.VirtualMachineConfig;
+import android.system.virtualmachine.VirtualMachineManager;
 
 import com.android.microdroid.test.device.MicrodroidDeviceTestBase;
 import com.android.virt.vm_attestation.testservice.IAttestationService.SigningResult;
 import com.android.virt.vm_attestation.util.X509Utils;
-import android.system.virtualmachine.VirtualMachineManager;
 
 import org.junit.Before;
 import org.junit.Test;
diff --git a/service_vm/test_apk/src/java/com/android/virt/vm_attestation/testapp/VmAttestationTests.java b/tests/vm_attestation/src/java/com/android/virt/vm_attestation/testapp/VmAttestationTests.java
similarity index 99%
rename from service_vm/test_apk/src/java/com/android/virt/vm_attestation/testapp/VmAttestationTests.java
rename to tests/vm_attestation/src/java/com/android/virt/vm_attestation/testapp/VmAttestationTests.java
index ff760b4..43a4c66 100644
--- a/service_vm/test_apk/src/java/com/android/virt/vm_attestation/testapp/VmAttestationTests.java
+++ b/tests/vm_attestation/src/java/com/android/virt/vm_attestation/testapp/VmAttestationTests.java
@@ -16,27 +16,29 @@
 
 package com.android.virt.vm_attestation.testapp;
 
+import static android.system.virtualmachine.VirtualMachineConfig.DEBUG_LEVEL_FULL;
+
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.TruthJUnit.assume;
-import static android.system.virtualmachine.VirtualMachineConfig.DEBUG_LEVEL_FULL;
 
 import android.system.virtualmachine.VirtualMachine;
 import android.system.virtualmachine.VirtualMachineConfig;
 import android.system.virtualmachine.VirtualMachineManager;
 
+import com.android.microdroid.test.device.MicrodroidDeviceTestBase;
+import com.android.virt.vm_attestation.testservice.IAttestationService;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
 import java.util.concurrent.CompletableFuture;
 
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import com.android.microdroid.test.device.MicrodroidDeviceTestBase;
-import com.android.virt.vm_attestation.testservice.IAttestationService;
-
 @RunWith(Parameterized.class)
 public class VmAttestationTests extends MicrodroidDeviceTestBase {
     private static final String TAG = "VmAttestationTest";
diff --git a/service_vm/test_apk/src/java/com/android/virt/vm_attestation/util/X509Utils.java b/tests/vm_attestation/src/java/com/android/virt/vm_attestation/util/X509Utils.java
similarity index 100%
rename from service_vm/test_apk/src/java/com/android/virt/vm_attestation/util/X509Utils.java
rename to tests/vm_attestation/src/java/com/android/virt/vm_attestation/util/X509Utils.java
diff --git a/service_vm/test_apk/src/native/main.rs b/tests/vm_attestation/src/native/main.rs
similarity index 100%
rename from service_vm/test_apk/src/native/main.rs
rename to tests/vm_attestation/src/native/main.rs
diff --git a/tests/vmbase_example/Android.bp b/tests/vmbase_example/Android.bp
new file mode 100644
index 0000000..4c1aa30
--- /dev/null
+++ b/tests/vmbase_example/Android.bp
@@ -0,0 +1,31 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+rust_test {
+    name: "vmbase_example.integration_test",
+    crate_name: "vmbase_example_test",
+    srcs: ["src/main.rs"],
+    prefer_rlib: true,
+    edition: "2021",
+    rustlibs: [
+        "android.system.virtualizationservice-rust",
+        "libandroid_logger",
+        "libanyhow",
+        "liblibc",
+        "liblog_rust",
+        "libnix",
+        "libvmclient",
+    ],
+    data: [
+        ":vmbase_example_bios_bin",
+        ":vmbase_example_kernel_bin",
+    ],
+    test_suites: ["general-tests"],
+    enabled: false,
+    target: {
+        android_arm64: {
+            enabled: true,
+        },
+    },
+}
diff --git a/vmbase/example/tests/test.rs b/tests/vmbase_example/src/main.rs
similarity index 86%
rename from vmbase/example/tests/test.rs
rename to tests/vmbase_example/src/main.rs
index 8f9fafc..e0563b7 100644
--- a/vmbase/example/tests/test.rs
+++ b/tests/vmbase_example/src/main.rs
@@ -31,13 +31,27 @@
 };
 use vmclient::{DeathReason, VmInstance};
 
-const VMBASE_EXAMPLE_PATH: &str = "vmbase_example.bin";
+const VMBASE_EXAMPLE_KERNEL_PATH: &str = "vmbase_example_kernel.bin";
+const VMBASE_EXAMPLE_BIOS_PATH: &str = "vmbase_example_bios.bin";
 const TEST_DISK_IMAGE_PATH: &str = "test_disk.img";
 const EMPTY_DISK_IMAGE_PATH: &str = "empty_disk.img";
 
-/// Runs the vmbase_example VM as an unprotected VM via VirtualizationService.
+/// Runs the vmbase_example VM as an unprotected VM kernel via VirtualizationService.
 #[test]
-fn test_run_example_vm() -> Result<(), Error> {
+fn test_run_example_kernel_vm() -> Result<(), Error> {
+    run_test(Some(open_payload(VMBASE_EXAMPLE_KERNEL_PATH)?), None)
+}
+
+/// Runs the vmbase_example VM as an unprotected VM BIOS via VirtualizationService.
+#[test]
+fn test_run_example_bios_vm() -> Result<(), Error> {
+    run_test(None, Some(open_payload(VMBASE_EXAMPLE_BIOS_PATH)?))
+}
+
+fn run_test(
+    kernel: Option<ParcelFileDescriptor>,
+    bootloader: Option<ParcelFileDescriptor>,
+) -> Result<(), Error> {
     android_logger::init_once(
         android_logger::Config::default()
             .with_tag("vmbase")
@@ -56,12 +70,6 @@
         vmclient::VirtualizationService::new().context("Failed to spawn VirtualizationService")?;
     let service = virtmgr.connect().context("Failed to connect to VirtualizationService")?;
 
-    // Start example VM.
-    let bootloader = ParcelFileDescriptor::new(
-        File::open(VMBASE_EXAMPLE_PATH)
-            .with_context(|| format!("Failed to open VM image {}", VMBASE_EXAMPLE_PATH))?,
-    );
-
     // Make file for test disk image.
     let mut test_image = File::options()
         .create(true)
@@ -91,10 +99,10 @@
 
     let config = VirtualMachineConfig::RawConfig(VirtualMachineRawConfig {
         name: String::from("VmBaseTest"),
-        kernel: None,
+        kernel,
         initrd: None,
         params: None,
-        bootloader: Some(bootloader),
+        bootloader,
         disks: vec![disk_image, empty_disk_image],
         protectedVm: false,
         memoryMib: 300,
@@ -142,6 +150,11 @@
     Ok((reader_fd.into(), writer_fd.into()))
 }
 
+fn open_payload(path: &str) -> Result<ParcelFileDescriptor, Error> {
+    let file = File::open(path).with_context(|| format!("Failed to open VM image {path}"))?;
+    Ok(ParcelFileDescriptor::new(file))
+}
+
 struct VmLogProcessor {
     reader: Option<File>,
     expected: VecDeque<String>,
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 109486c..9f606e5 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
@@ -28,8 +28,8 @@
 import android.util.Log;
 
 import com.android.microdroid.test.vmshare.IVmShareTestService;
-import com.android.microdroid.testservice.ITestService;
 import com.android.microdroid.testservice.IAppCallback;
+import com.android.microdroid.testservice.ITestService;
 
 import java.util.UUID;
 import java.util.concurrent.CountDownLatch;
diff --git a/vmbase/example/Android.bp b/vmbase/example/Android.bp
deleted file mode 100644
index fe9de44..0000000
--- a/vmbase/example/Android.bp
+++ /dev/null
@@ -1,74 +0,0 @@
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-rust_ffi_static {
-    name: "libvmbase_example",
-    defaults: ["vmbase_ffi_defaults"],
-    crate_name: "vmbase_example",
-    srcs: ["src/main.rs"],
-    rustlibs: [
-        "libaarch64_paging",
-        "libcstr",
-        "libdiced_open_dice_nostd",
-        "libfdtpci",
-        "liblibfdt",
-        "liblog_rust_nostd",
-        "libvirtio_drivers",
-        "libvmbase",
-    ],
-}
-
-cc_binary {
-    name: "vmbase_example",
-    defaults: ["vmbase_elf_defaults"],
-    srcs: [
-        "idmap.S",
-    ],
-    static_libs: [
-        "libvmbase_example",
-    ],
-    linker_scripts: [
-        "image.ld",
-        ":vmbase_sections",
-    ],
-}
-
-raw_binary {
-    name: "vmbase_example_bin",
-    stem: "vmbase_example.bin",
-    src: ":vmbase_example",
-    enabled: false,
-    target: {
-        android_arm64: {
-            enabled: true,
-        },
-    },
-}
-
-rust_test {
-    name: "vmbase_example.integration_test",
-    crate_name: "vmbase_example_test",
-    srcs: ["tests/test.rs"],
-    prefer_rlib: true,
-    edition: "2021",
-    rustlibs: [
-        "android.system.virtualizationservice-rust",
-        "libandroid_logger",
-        "libanyhow",
-        "liblibc",
-        "liblog_rust",
-        "libnix",
-        "libvmclient",
-    ],
-    data: [
-        ":vmbase_example_bin",
-    ],
-    test_suites: ["general-tests"],
-    enabled: false,
-    target: {
-        android_arm64: {
-            enabled: true,
-        },
-    },
-}
diff --git a/vmbase/example/image.ld b/vmbase/example/image.ld
deleted file mode 100644
index 368acbb..0000000
--- a/vmbase/example/image.ld
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     https://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.
- */
-
-MEMORY
-{
-	dtb_region	: ORIGIN = 0x80000000, LENGTH = 2M
-	image		: ORIGIN = 0x80200000, LENGTH = 2M
-	writable_data	: ORIGIN = 0x80400000, LENGTH = 2M
-}
diff --git a/vmlauncher_app/AndroidManifest.xml b/vmlauncher_app/AndroidManifest.xml
deleted file mode 100644
index 32724f8..0000000
--- a/vmlauncher_app/AndroidManifest.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.virtualization.vmlauncher" >
-
-    <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.INTERNET" />
-    <uses-permission android:name="android.permission.RECORD_AUDIO" />
-    <uses-feature android:name="android.software.virtualization_framework" android:required="true" />
-    <application
-        android:label="VmLauncherApp">
-        <activity android:name=".MainActivity"
-                  android:screenOrientation="landscape"
-                  android:configChanges="orientation|screenSize|keyboard|keyboardHidden|navigation|uiMode"
-                  android:theme="@style/MyTheme"
-                  android:exported="true">
-            <intent-filter>
-                <action android:name="android.virtualization.VM_LAUNCHER" />
-                <category android:name="android.intent.category.DEFAULT" />
-	    </intent-filter>
-        </activity>
-    </application>
-
-</manifest>
diff --git a/vmlauncher_app/java/com/android/virtualization/vmlauncher/MainActivity.java b/vmlauncher_app/java/com/android/virtualization/vmlauncher/MainActivity.java
deleted file mode 100644
index 8bb0fd4..0000000
--- a/vmlauncher_app/java/com/android/virtualization/vmlauncher/MainActivity.java
+++ /dev/null
@@ -1,773 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.virtualization.vmlauncher;
-
-import static android.content.pm.PackageManager.PERMISSION_GRANTED;
-import static android.os.ParcelFileDescriptor.AutoCloseInputStream;
-import static android.os.ParcelFileDescriptor.AutoCloseOutputStream;
-import static android.system.virtualmachine.VirtualMachineConfig.CPU_TOPOLOGY_MATCH_HOST;
-
-import android.Manifest.permission;
-import android.app.Activity;
-import android.content.ClipData;
-import android.content.ClipboardManager;
-import android.crosvm.ICrosvmAndroidDisplayService;
-import android.graphics.PixelFormat;
-import android.graphics.Rect;
-import android.os.Bundle;
-import android.os.ParcelFileDescriptor;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.system.virtualizationservice_internal.IVirtualizationServiceInternal;
-import android.system.virtualmachine.VirtualMachine;
-import android.system.virtualmachine.VirtualMachineCallback;
-import android.system.virtualmachine.VirtualMachineConfig;
-import android.system.virtualmachine.VirtualMachineCustomImageConfig;
-import android.system.virtualmachine.VirtualMachineCustomImageConfig.AudioConfig;
-import android.system.virtualmachine.VirtualMachineCustomImageConfig.DisplayConfig;
-import android.system.virtualmachine.VirtualMachineCustomImageConfig.GpuConfig;
-import android.system.virtualmachine.VirtualMachineException;
-import android.system.virtualmachine.VirtualMachineManager;
-import android.util.DisplayMetrics;
-import android.util.Log;
-import android.view.Display;
-import android.view.InputDevice;
-import android.view.KeyEvent;
-import android.view.SurfaceHolder;
-import android.view.SurfaceView;
-import android.view.View;
-import android.view.WindowInsets;
-import android.view.WindowInsetsController;
-import android.view.WindowManager;
-import android.view.WindowMetrics;
-
-import libcore.io.IoBridge;
-
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import java.io.BufferedOutputStream;
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.OutputStream;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-
-public class MainActivity extends Activity {
-    private static final String TAG = "VmLauncherApp";
-    private static final String VM_NAME = "my_custom_vm";
-    private static final boolean DEBUG = true;
-    private ExecutorService mExecutorService;
-    private VirtualMachine mVirtualMachine;
-    private CursorHandler mCursorHandler;
-    private ClipboardManager mClipboardManager;
-    private static final int RECORD_AUDIO_PERMISSION_REQUEST_CODE = 101;
-
-    private VirtualMachineConfig createVirtualMachineConfig(String jsonPath) {
-        VirtualMachineConfig.Builder configBuilder =
-                new VirtualMachineConfig.Builder(getApplication());
-        configBuilder.setCpuTopology(CPU_TOPOLOGY_MATCH_HOST);
-
-        configBuilder.setProtectedVm(false);
-        if (DEBUG) {
-            configBuilder.setDebugLevel(VirtualMachineConfig.DEBUG_LEVEL_FULL);
-            configBuilder.setVmOutputCaptured(true);
-            configBuilder.setConnectVmConsole(true);
-        }
-        VirtualMachineCustomImageConfig.Builder customImageConfigBuilder =
-                new VirtualMachineCustomImageConfig.Builder();
-        try {
-            String rawJson = new String(Files.readAllBytes(Path.of(jsonPath)));
-            JSONObject json = new JSONObject(rawJson);
-            customImageConfigBuilder.setName(json.optString("name", ""));
-            if (json.has("kernel")) {
-                customImageConfigBuilder.setKernelPath(json.getString("kernel"));
-            }
-            if (json.has("initrd")) {
-                customImageConfigBuilder.setInitrdPath(json.getString("initrd"));
-            }
-            if (json.has("params")) {
-                Arrays.stream(json.getString("params").split(" "))
-                        .forEach(customImageConfigBuilder::addParam);
-            }
-            if (json.has("bootloader")) {
-                customImageConfigBuilder.setBootloaderPath(json.getString("bootloader"));
-            }
-            if (json.has("disks")) {
-                JSONArray diskArr = json.getJSONArray("disks");
-                for (int i = 0; i < diskArr.length(); i++) {
-                    JSONObject item = diskArr.getJSONObject(i);
-                    if (item.has("image")) {
-                        if (item.optBoolean("writable", false)) {
-                            customImageConfigBuilder.addDisk(
-                                    VirtualMachineCustomImageConfig.Disk.RWDisk(
-                                            item.getString("image")));
-                        } else {
-                            customImageConfigBuilder.addDisk(
-                                    VirtualMachineCustomImageConfig.Disk.RODisk(
-                                            item.getString("image")));
-                        }
-                    } else if (item.has("partitions")) {
-                        boolean diskWritable = item.optBoolean("writable", false);
-                        VirtualMachineCustomImageConfig.Disk disk =
-                                diskWritable
-                                        ? VirtualMachineCustomImageConfig.Disk.RWDisk(null)
-                                        : VirtualMachineCustomImageConfig.Disk.RODisk(null);
-                        JSONArray partitions = item.getJSONArray("partitions");
-                        for (int j = 0; j < partitions.length(); j++) {
-                            JSONObject partition = partitions.getJSONObject(j);
-                            String label = partition.getString("label");
-                            String path = partition.getString("path");
-                            boolean partitionWritable =
-                                    diskWritable && partition.optBoolean("writable", false);
-                            String guid = partition.optString("guid");
-                            VirtualMachineCustomImageConfig.Partition p =
-                                    new VirtualMachineCustomImageConfig.Partition(
-                                            label, path, partitionWritable, guid);
-                            disk.addPartition(p);
-                        }
-                        customImageConfigBuilder.addDisk(disk);
-                    }
-                }
-            }
-            if (json.has("console_input_device")) {
-                configBuilder.setConsoleInputDevice(json.getString("console_input_device"));
-            }
-            if (json.has("gpu")) {
-                JSONObject gpuJson = json.getJSONObject("gpu");
-
-                GpuConfig.Builder gpuConfigBuilder = new GpuConfig.Builder();
-
-                if (gpuJson.has("backend")) {
-                    gpuConfigBuilder.setBackend(gpuJson.getString("backend"));
-                }
-                if (gpuJson.has("context_types")) {
-                    ArrayList<String> contextTypes = new ArrayList<String>();
-                    JSONArray contextTypesJson = gpuJson.getJSONArray("context_types");
-                    for (int i = 0; i < contextTypesJson.length(); i++) {
-                        contextTypes.add(contextTypesJson.getString(i));
-                    }
-                    gpuConfigBuilder.setContextTypes(contextTypes.toArray(new String[0]));
-                }
-                if (gpuJson.has("pci_address")) {
-                    gpuConfigBuilder.setPciAddress(gpuJson.getString("pci_address"));
-                }
-                if (gpuJson.has("renderer_features")) {
-                    gpuConfigBuilder.setRendererFeatures(gpuJson.getString("renderer_features"));
-                }
-                if (gpuJson.has("renderer_use_egl")) {
-                    gpuConfigBuilder.setRendererUseEgl(gpuJson.getBoolean("renderer_use_egl"));
-                }
-                if (gpuJson.has("renderer_use_gles")) {
-                    gpuConfigBuilder.setRendererUseGles(gpuJson.getBoolean("renderer_use_gles"));
-                }
-                if (gpuJson.has("renderer_use_glx")) {
-                    gpuConfigBuilder.setRendererUseGlx(gpuJson.getBoolean("renderer_use_glx"));
-                }
-                if (gpuJson.has("renderer_use_surfaceless")) {
-                    gpuConfigBuilder.setRendererUseSurfaceless(
-                            gpuJson.getBoolean("renderer_use_surfaceless"));
-                }
-                if (gpuJson.has("renderer_use_vulkan")) {
-                    gpuConfigBuilder.setRendererUseVulkan(
-                            gpuJson.getBoolean("renderer_use_vulkan"));
-                }
-                customImageConfigBuilder.setGpuConfig(gpuConfigBuilder.build());
-            }
-
-            configBuilder.setMemoryBytes(8L * 1024 * 1024 * 1024 /* 8 GB */);
-            WindowMetrics windowMetrics = getWindowManager().getCurrentWindowMetrics();
-            Rect windowSize = windowMetrics.getBounds();
-            int dpi = (int) (DisplayMetrics.DENSITY_DEFAULT * windowMetrics.getDensity());
-            DisplayConfig.Builder displayConfigBuilder = new DisplayConfig.Builder();
-            displayConfigBuilder.setWidth(windowSize.right);
-            displayConfigBuilder.setHeight(windowSize.bottom);
-            displayConfigBuilder.setHorizontalDpi(dpi);
-            displayConfigBuilder.setVerticalDpi(dpi);
-
-            Display display = getDisplay();
-            if (display != null) {
-                displayConfigBuilder.setRefreshRate((int) display.getRefreshRate());
-            }
-
-            customImageConfigBuilder.setDisplayConfig(displayConfigBuilder.build());
-            customImageConfigBuilder.useTouch(true);
-            customImageConfigBuilder.useKeyboard(true);
-            customImageConfigBuilder.useMouse(true);
-            customImageConfigBuilder.useSwitches(true);
-            customImageConfigBuilder.useTrackpad(true);
-            customImageConfigBuilder.useNetwork(true);
-
-            AudioConfig.Builder audioConfigBuilder = new AudioConfig.Builder();
-            audioConfigBuilder.setUseMicrophone(true);
-            audioConfigBuilder.setUseSpeaker(true);
-            customImageConfigBuilder.setAudioConfig(audioConfigBuilder.build());
-            configBuilder.setCustomImageConfig(customImageConfigBuilder.build());
-
-        } catch (JSONException | IOException e) {
-            throw new IllegalStateException("malformed input", e);
-        }
-        return configBuilder.build();
-    }
-
-    private static boolean isVolumeKey(int keyCode) {
-        return keyCode == KeyEvent.KEYCODE_VOLUME_UP
-                || keyCode == KeyEvent.KEYCODE_VOLUME_DOWN
-                || keyCode == KeyEvent.KEYCODE_VOLUME_MUTE;
-    }
-
-    @Override
-    public boolean onKeyDown(int keyCode, KeyEvent event) {
-        if (mVirtualMachine == null) {
-            return false;
-        }
-        return !isVolumeKey(keyCode) && mVirtualMachine.sendKeyEvent(event);
-    }
-
-    @Override
-    public boolean onKeyUp(int keyCode, KeyEvent event) {
-        if (mVirtualMachine == null) {
-            return false;
-        }
-        return !isVolumeKey(keyCode) && mVirtualMachine.sendKeyEvent(event);
-    }
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        checkAndRequestRecordAudioPermission();
-        mExecutorService = Executors.newCachedThreadPool();
-        try {
-            // To ensure that the previous display service is removed.
-            IVirtualizationServiceInternal.Stub.asInterface(
-                            ServiceManager.waitForService("android.system.virtualizationservice"))
-                    .clearDisplayService();
-        } catch (RemoteException e) {
-            Log.d(TAG, "failed to clearDisplayService");
-        }
-        getWindow().setDecorFitsSystemWindows(false);
-        setContentView(R.layout.activity_main);
-        VirtualMachineCallback callback =
-                new VirtualMachineCallback() {
-                    // store reference to ExecutorService to avoid race condition
-                    private final ExecutorService mService = mExecutorService;
-
-                    @Override
-                    public void onPayloadStarted(VirtualMachine vm) {
-                        // This event is only from Microdroid-based VM. Custom VM shouldn't emit
-                        // this.
-                    }
-
-                    @Override
-                    public void onPayloadReady(VirtualMachine vm) {
-                        // This event is only from Microdroid-based VM. Custom VM shouldn't emit
-                        // this.
-                    }
-
-                    @Override
-                    public void onPayloadFinished(VirtualMachine vm, int exitCode) {
-                        // This event is only from Microdroid-based VM. Custom VM shouldn't emit
-                        // this.
-                    }
-
-                    @Override
-                    public void onError(VirtualMachine vm, int errorCode, String message) {
-                        Log.e(TAG, "Error from VM. code: " + errorCode + " (" + message + ")");
-                        setResult(RESULT_CANCELED);
-                        finish();
-                    }
-
-                    @Override
-                    public void onStopped(VirtualMachine vm, int reason) {
-                        Log.d(TAG, "VM stopped. Reason: " + reason);
-                        setResult(RESULT_OK);
-                        finish();
-                    }
-                };
-
-        try {
-            VirtualMachineConfig config =
-                    createVirtualMachineConfig("/data/local/tmp/vm_config.json");
-            VirtualMachineManager vmm =
-                    getApplication().getSystemService(VirtualMachineManager.class);
-            if (vmm == null) {
-                Log.e(TAG, "vmm is null");
-                return;
-            }
-            mVirtualMachine = vmm.getOrCreate(VM_NAME, config);
-            try {
-                mVirtualMachine.setConfig(config);
-            } catch (VirtualMachineException e) {
-                vmm.delete(VM_NAME);
-                mVirtualMachine = vmm.create(VM_NAME, config);
-                Log.e(TAG, "error for setting VM config", e);
-            }
-
-            Log.d(TAG, "vm start");
-            mVirtualMachine.run();
-            mVirtualMachine.setCallback(Executors.newSingleThreadExecutor(), callback);
-            if (DEBUG) {
-                InputStream console = mVirtualMachine.getConsoleOutput();
-                InputStream log = mVirtualMachine.getLogOutput();
-                OutputStream consoleLogFile =
-                        new LineBufferedOutputStream(
-                                getApplicationContext().openFileOutput("console.log", 0));
-                mExecutorService.execute(new CopyStreamTask("console", console, consoleLogFile));
-                mExecutorService.execute(new Reader("log", log));
-            }
-        } catch (VirtualMachineException | IOException e) {
-            throw new RuntimeException(e);
-        }
-
-        SurfaceView surfaceView = findViewById(R.id.surface_view);
-        SurfaceView cursorSurfaceView = findViewById(R.id.cursor_surface_view);
-        cursorSurfaceView.setZOrderMediaOverlay(true);
-        View backgroundTouchView = findViewById(R.id.background_touch_view);
-        backgroundTouchView.setOnTouchListener(
-                (v, event) -> {
-                    if (mVirtualMachine == null) {
-                        return false;
-                    }
-                    return mVirtualMachine.sendMultiTouchEvent(event);
-                });
-        surfaceView.requestUnbufferedDispatch(InputDevice.SOURCE_ANY);
-        surfaceView.setOnCapturedPointerListener(
-                (v, event) -> {
-                    if (mVirtualMachine == null) {
-                        return false;
-                    }
-                    int eventSource = event.getSource();
-                    if ((eventSource & InputDevice.SOURCE_CLASS_POSITION) != 0) {
-                        return mVirtualMachine.sendTrackpadEvent(event);
-                    }
-                    return mVirtualMachine.sendMouseEvent(event);
-                });
-        surfaceView
-                .getHolder()
-                .addCallback(
-                        // TODO(b/331708504): it should be handled in AVF framework.
-                        new SurfaceHolder.Callback() {
-                            @Override
-                            public void surfaceCreated(SurfaceHolder holder) {
-                                Log.d(
-                                        TAG,
-                                        "surface size: "
-                                                + holder.getSurfaceFrame().flattenToString());
-                                Log.d(
-                                        TAG,
-                                        "ICrosvmAndroidDisplayService.setSurface("
-                                                + holder.getSurface()
-                                                + ")");
-                                runWithDisplayService(
-                                        (service) ->
-                                                service.setSurface(
-                                                        holder.getSurface(),
-                                                        false /* forCursor */));
-                            }
-
-                            @Override
-                            public void surfaceChanged(
-                                    SurfaceHolder holder, int format, int width, int height) {
-                                Log.d(
-                                        TAG,
-                                        "surface changed, width: " + width + ", height: " + height);
-                            }
-
-                            @Override
-                            public void surfaceDestroyed(SurfaceHolder holder) {
-                                Log.d(TAG, "ICrosvmAndroidDisplayService.removeSurface()");
-                                runWithDisplayService(
-                                        (service) -> service.removeSurface(false /* forCursor */));
-                            }
-                        });
-        cursorSurfaceView.getHolder().setFormat(PixelFormat.RGBA_8888);
-        cursorSurfaceView
-                .getHolder()
-                .addCallback(
-                        new SurfaceHolder.Callback() {
-                            @Override
-                            public void surfaceCreated(SurfaceHolder holder) {
-                                try {
-                                    ParcelFileDescriptor[] pfds =
-                                            ParcelFileDescriptor.createSocketPair();
-                                    if (mCursorHandler != null) {
-                                        mCursorHandler.interrupt();
-                                    }
-                                    mCursorHandler = new CursorHandler(cursorSurfaceView, pfds[0]);
-                                    mCursorHandler.start();
-                                    runWithDisplayService(
-                                            (service) -> service.setCursorStream(pfds[1]));
-                                } catch (Exception e) {
-                                    Log.d(TAG, "failed to run cursor stream handler", e);
-                                }
-                                Log.d(
-                                        TAG,
-                                        "ICrosvmAndroidDisplayService.setSurface("
-                                                + holder.getSurface()
-                                                + ")");
-                                runWithDisplayService(
-                                        (service) ->
-                                                service.setSurface(
-                                                        holder.getSurface(), true /* forCursor */));
-                            }
-
-                            @Override
-                            public void surfaceChanged(
-                                    SurfaceHolder holder, int format, int width, int height) {
-                                Log.d(
-                                        TAG,
-                                        "cursor surface changed, width: "
-                                                + width
-                                                + ", height: "
-                                                + height);
-                            }
-
-                            @Override
-                            public void surfaceDestroyed(SurfaceHolder holder) {
-                                Log.d(TAG, "ICrosvmAndroidDisplayService.removeSurface()");
-                                runWithDisplayService(
-                                        (service) -> service.removeSurface(true /* forCursor */));
-                            }
-                        });
-        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
-
-        // Fullscreen:
-        WindowInsetsController windowInsetsController = surfaceView.getWindowInsetsController();
-        windowInsetsController.setSystemBarsBehavior(
-                WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE);
-        windowInsetsController.hide(WindowInsets.Type.systemBars());
-    }
-
-    @Override
-    protected void onStop() {
-        super.onStop();
-        if (mVirtualMachine != null) {
-            try {
-                mVirtualMachine.sendLidEvent(/* close */ true);
-                mVirtualMachine.suspend();
-            } catch (VirtualMachineException e) {
-                Log.e(TAG, "Failed to suspend VM" + e);
-            }
-        }
-    }
-
-    @Override
-    protected void onRestart() {
-        super.onRestart();
-        if (mVirtualMachine != null) {
-            try {
-                mVirtualMachine.resume();
-                mVirtualMachine.sendLidEvent(/* close */ false);
-            } catch (VirtualMachineException e) {
-                Log.e(TAG, "Failed to resume VM" + e);
-            }
-        }
-    }
-
-    @Override
-    protected void onDestroy() {
-        super.onDestroy();
-        if (mExecutorService != null) {
-            mExecutorService.shutdownNow();
-        }
-        Log.d(TAG, "destroyed");
-    }
-
-    private static final int CLIPBOARD_SHARING_SERVER_PORT = 3580;
-    private static final byte READ_CLIPBOARD_FROM_VM = 0;
-    private static final byte WRITE_CLIPBOARD_TYPE_EMPTY = 1;
-    private static final byte WRITE_CLIPBOARD_TYPE_TEXT_PLAIN = 2;
-
-    private ClipboardManager getClipboardManager() {
-        if (mClipboardManager == null) {
-            mClipboardManager = getSystemService(ClipboardManager.class);
-        }
-        return mClipboardManager;
-    }
-
-    // Construct header for the clipboard data.
-    // Byte 0: Data type
-    // Byte 1-3: Padding alignment & Reserved for other use cases in the future
-    // Byte 4-7: Data size of the payload
-    private byte[] constructClipboardHeader(byte type, int dataSize) {
-        ByteBuffer header = ByteBuffer.allocate(8);
-        header.clear();
-        header.order(ByteOrder.LITTLE_ENDIAN);
-        header.put(0, type);
-        header.putInt(4, dataSize);
-        return header.array();
-    }
-
-    private ParcelFileDescriptor connectClipboardSharingServer() {
-        ParcelFileDescriptor pfd;
-        try {
-            // TODO(349702313): Consider when clipboard sharing server is started to run in VM.
-            pfd = mVirtualMachine.connectVsock(CLIPBOARD_SHARING_SERVER_PORT);
-        } catch (VirtualMachineException e) {
-            Log.d(TAG, "cannot connect to the clipboard sharing server", e);
-            return null;
-        }
-        return pfd;
-    }
-
-    private boolean writeClipboardToVm() {
-        ClipboardManager clipboardManager = getClipboardManager();
-        if (!clipboardManager.hasPrimaryClip()) {
-            Log.d(TAG, "host device has no clipboard data");
-            return true;
-        }
-        ClipData clip = clipboardManager.getPrimaryClip();
-        String text = clip.getItemAt(0).getText().toString();
-        byte[] header =
-                constructClipboardHeader(
-                        WRITE_CLIPBOARD_TYPE_TEXT_PLAIN, text.getBytes().length + 1);
-        ParcelFileDescriptor pfd = connectClipboardSharingServer();
-        if (pfd == null) {
-            Log.d(TAG, "file descriptor of ClipboardSharingServer is null");
-            return false;
-        }
-        try (OutputStream stream = new AutoCloseOutputStream(pfd)) {
-            stream.write(header);
-            stream.write(text.getBytes());
-            stream.write('\0');
-            stream.flush();
-            Log.d(TAG, "successfully wrote clipboard data to the VM");
-            return true;
-        } catch (IOException e) {
-            Log.e(TAG, "failed to write clipboard data to the VM", e);
-            return false;
-        }
-    }
-
-    private boolean readClipboardFromVm() {
-        byte[] request = constructClipboardHeader(READ_CLIPBOARD_FROM_VM, 0);
-        ParcelFileDescriptor pfd = connectClipboardSharingServer();
-        if (pfd == null) {
-            Log.d(TAG, "file descriptor of ClipboardSharingServer is null");
-            return false;
-        }
-        try (OutputStream output = new AutoCloseOutputStream(pfd.dup())) {
-            output.write(request);
-            output.flush();
-            Log.d(TAG, "successfully send request to the VM for reading clipboard");
-        } catch (IOException e) {
-            Log.e(TAG, "failed to send request to the VM for read clipboard", e);
-            try {
-                pfd.close();
-            } catch (IOException err) {
-                Log.e(TAG, "failed to close file descriptor", err);
-            }
-            return false;
-        }
-
-        try (InputStream input = new AutoCloseInputStream(pfd)) {
-            ByteBuffer header = ByteBuffer.wrap(input.readNBytes(8));
-            header.order(ByteOrder.LITTLE_ENDIAN);
-            switch (header.get(0)) {
-                case WRITE_CLIPBOARD_TYPE_EMPTY:
-                    Log.d(TAG, "clipboard data in VM is empty");
-                    return true;
-                case WRITE_CLIPBOARD_TYPE_TEXT_PLAIN:
-                    int dataSize = header.getInt(4);
-                    String text_data =
-                            new String(input.readNBytes(dataSize), StandardCharsets.UTF_8);
-                    getClipboardManager().setPrimaryClip(ClipData.newPlainText(null, text_data));
-                    Log.d(TAG, "successfully received clipboard data from VM");
-                    return true;
-                default:
-                    Log.e(TAG, "unknown clipboard response type");
-                    return false;
-            }
-        } catch (IOException e) {
-            Log.e(TAG, "failed to receive clipboard content from the VM", e);
-            return false;
-        }
-    }
-
-    @Override
-    public void onWindowFocusChanged(boolean hasFocus) {
-        super.onWindowFocusChanged(hasFocus);
-        if (hasFocus) {
-            SurfaceView surfaceView = findViewById(R.id.surface_view);
-            Log.d(TAG, "requestPointerCapture()");
-            surfaceView.requestPointerCapture();
-        }
-        if (mVirtualMachine != null) {
-            if (hasFocus) {
-                Log.d(TAG, "writing clipboard of host device into VM");
-                writeClipboardToVm();
-            } else {
-                Log.d(TAG, "reading clipboard of VM");
-                readClipboardFromVm();
-            }
-        }
-    }
-
-    @FunctionalInterface
-    public interface RemoteExceptionCheckedFunction<T> {
-        void apply(T t) throws RemoteException;
-    }
-
-    private void runWithDisplayService(
-            RemoteExceptionCheckedFunction<ICrosvmAndroidDisplayService> func) {
-        IVirtualizationServiceInternal vs =
-                IVirtualizationServiceInternal.Stub.asInterface(
-                        ServiceManager.waitForService("android.system.virtualizationservice"));
-        try {
-            Log.d(TAG, "wait for the display service");
-            ICrosvmAndroidDisplayService service =
-                    ICrosvmAndroidDisplayService.Stub.asInterface(vs.waitDisplayService());
-            assert service != null;
-            func.apply(service);
-            Log.d(TAG, "display service runs successfully");
-        } catch (Exception e) {
-            Log.d(TAG, "error on running display service", e);
-        }
-    }
-
-    static class CursorHandler extends Thread {
-        private final SurfaceView mSurfaceView;
-        private final ParcelFileDescriptor mStream;
-
-        CursorHandler(SurfaceView s, ParcelFileDescriptor stream) {
-            mSurfaceView = s;
-            mStream = stream;
-        }
-
-        @Override
-        public void run() {
-            Log.d(TAG, "running CursorHandler");
-            try {
-                ByteBuffer byteBuffer = ByteBuffer.allocate(8 /* (x: u32, y: u32) */);
-                byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
-                while (true) {
-                    if (Thread.interrupted()) {
-                        Log.d(TAG, "interrupted: exiting CursorHandler");
-                        return;
-                    }
-                    byteBuffer.clear();
-                    int bytes =
-                            IoBridge.read(
-                                    mStream.getFileDescriptor(),
-                                    byteBuffer.array(),
-                                    0,
-                                    byteBuffer.array().length);
-                    if (bytes == -1) {
-                        Log.e(TAG, "cannot read from cursor stream, stop the handler");
-                        return;
-                    }
-                    float x = (float) (byteBuffer.getInt() & 0xFFFFFFFF);
-                    float y = (float) (byteBuffer.getInt() & 0xFFFFFFFF);
-                    mSurfaceView.post(
-                            () -> {
-                                mSurfaceView.setTranslationX(x);
-                                mSurfaceView.setTranslationY(y);
-                            });
-                }
-            } catch (IOException e) {
-                Log.e(TAG, "failed to run CursorHandler", e);
-            }
-        }
-    }
-
-    private void checkAndRequestRecordAudioPermission() {
-        if (getApplicationContext().checkSelfPermission(permission.RECORD_AUDIO)
-                != PERMISSION_GRANTED) {
-            requestPermissions(
-                    new String[] {permission.RECORD_AUDIO}, RECORD_AUDIO_PERMISSION_REQUEST_CODE);
-        }
-    }
-
-    /** Reads data from an input stream and posts it to the output data */
-    static class Reader implements Runnable {
-        private final String mName;
-        private final InputStream mStream;
-
-        Reader(String name, InputStream stream) {
-            mName = name;
-            mStream = stream;
-        }
-
-        @Override
-        public void run() {
-            try {
-                BufferedReader reader = new BufferedReader(new InputStreamReader(mStream));
-                String line;
-                while ((line = reader.readLine()) != null && !Thread.interrupted()) {
-                    Log.d(TAG, mName + ": " + line);
-                }
-            } catch (IOException e) {
-                Log.e(TAG, "Exception while posting " + mName + " output: " + e.getMessage());
-            }
-        }
-    }
-
-    private static class CopyStreamTask implements Runnable {
-        private final String mName;
-        private final InputStream mIn;
-        private final OutputStream mOut;
-
-        CopyStreamTask(String name, InputStream in, OutputStream out) {
-            mName = name;
-            mIn = in;
-            mOut = out;
-        }
-
-        @Override
-        public void run() {
-            try {
-                byte[] buffer = new byte[2048];
-                while (!Thread.interrupted()) {
-                    int len = mIn.read(buffer);
-                    if (len < 0) {
-                        break;
-                    }
-                    mOut.write(buffer, 0, len);
-                }
-            } catch (Exception e) {
-                Log.e(TAG, "Exception while posting " + mName, e);
-            }
-        }
-    }
-
-    private static class LineBufferedOutputStream extends BufferedOutputStream {
-        LineBufferedOutputStream(OutputStream out) {
-            super(out);
-        }
-
-        @Override
-        public void write(byte[] buf, int off, int len) throws IOException {
-            super.write(buf, off, len);
-            for (int i = 0; i < len; ++i) {
-                if (buf[off + i] == '\n') {
-                    flush();
-                    break;
-                }
-            }
-        }
-    }
-}