Merge "VmTerminalApp: Implement Wi-Fi only checkbox in installer" into main
diff --git a/OWNERS b/OWNERS
index 81217f3..af71ad6 100644
--- a/OWNERS
+++ b/OWNERS
@@ -31,6 +31,4 @@
 
 # Ferrochrome
 per-file android/TerminalApp/**=jiyong@google.com,jeongik@google.com
-per-file android/VmLauncherApp/**=jiyong@google.com,jeongik@google.com
-per-file libs/vm_launcher_lib/**=jiyong@google.com,jeongik@google.com
 per-file build/debian/**=jiyong@google.com,jeongik@google.com
diff --git a/android/TerminalApp/Android.bp b/android/TerminalApp/Android.bp
index 3b5f9b8..2711af0 100644
--- a/android/TerminalApp/Android.bp
+++ b/android/TerminalApp/Android.bp
@@ -11,15 +11,22 @@
     resource_dirs: ["res"],
     asset_dirs: ["assets"],
     static_libs: [
-        "VmTerminalApp.aidl-java",
-        "vm_launcher_lib",
         "androidx-constraintlayout_constraintlayout",
-        "com.google.android.material_material",
         "androidx.window_window",
+        "apache-commons-compress",
+        "com.google.android.material_material",
+        "debian-service-grpclib-lite",
+        "gson",
+        "VmTerminalApp.aidl-java",
     ],
     jni_libs: [
         "libforwarder_host_jni",
     ],
+    libs: [
+        "androidx.annotation_annotation",
+        "framework-virtualization.impl",
+        "framework-annotations-lib",
+    ],
     use_embedded_native_libs: true,
     platform_apis: true,
     privileged: true,
diff --git a/android/TerminalApp/AndroidManifest.xml b/android/TerminalApp/AndroidManifest.xml
index b74b8b0..dad07ee 100644
--- a/android/TerminalApp/AndroidManifest.xml
+++ b/android/TerminalApp/AndroidManifest.xml
@@ -76,7 +76,7 @@
             android:stopWithTask="true" />
 
         <service
-            android:name="com.android.virtualization.vmlauncher.VmLauncherService"
+            android:name=".VmLauncherService"
             android:exported="false"
             android:foregroundServiceType="specialUse"
             android:stopWithTask="true" >
diff --git a/libs/vm_launcher_lib/java/com/android/virtualization/vmlauncher/ConfigJson.java b/android/TerminalApp/java/com/android/virtualization/terminal/ConfigJson.java
similarity index 99%
rename from libs/vm_launcher_lib/java/com/android/virtualization/vmlauncher/ConfigJson.java
rename to android/TerminalApp/java/com/android/virtualization/terminal/ConfigJson.java
index f229964..e1342e9 100644
--- a/libs/vm_launcher_lib/java/com/android/virtualization/vmlauncher/ConfigJson.java
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/ConfigJson.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.virtualization.vmlauncher;
+package com.android.virtualization.terminal;
 
 import android.content.Context;
 import android.content.pm.PackageManager.NameNotFoundException;
diff --git a/libs/vm_launcher_lib/java/com/android/virtualization/vmlauncher/DebianServiceImpl.java b/android/TerminalApp/java/com/android/virtualization/terminal/DebianServiceImpl.java
similarity index 91%
rename from libs/vm_launcher_lib/java/com/android/virtualization/vmlauncher/DebianServiceImpl.java
rename to android/TerminalApp/java/com/android/virtualization/terminal/DebianServiceImpl.java
index 68ff2ec..0b65cf6 100644
--- a/libs/vm_launcher_lib/java/com/android/virtualization/vmlauncher/DebianServiceImpl.java
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/DebianServiceImpl.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.virtualization.vmlauncher;
+package com.android.virtualization.terminal;
 
 import android.content.Context;
 import android.content.SharedPreferences;
@@ -22,13 +22,13 @@
 
 import androidx.annotation.Keep;
 
-import com.android.virtualization.vmlauncher.proto.DebianServiceGrpc;
-import com.android.virtualization.vmlauncher.proto.ForwardingRequestItem;
-import com.android.virtualization.vmlauncher.proto.IpAddr;
-import com.android.virtualization.vmlauncher.proto.QueueOpeningRequest;
-import com.android.virtualization.vmlauncher.proto.ReportVmActivePortsRequest;
-import com.android.virtualization.vmlauncher.proto.ReportVmActivePortsResponse;
-import com.android.virtualization.vmlauncher.proto.ReportVmIpAddrResponse;
+import com.android.virtualization.terminal.proto.DebianServiceGrpc;
+import com.android.virtualization.terminal.proto.ForwardingRequestItem;
+import com.android.virtualization.terminal.proto.IpAddr;
+import com.android.virtualization.terminal.proto.QueueOpeningRequest;
+import com.android.virtualization.terminal.proto.ReportVmActivePortsRequest;
+import com.android.virtualization.terminal.proto.ReportVmActivePortsResponse;
+import com.android.virtualization.terminal.proto.ReportVmIpAddrResponse;
 
 import io.grpc.stub.StreamObserver;
 
@@ -49,7 +49,6 @@
     private SharedPreferences.OnSharedPreferenceChangeListener mPortForwardingListener;
     private final DebianServiceCallback mCallback;
 
-
     static {
         System.loadLibrary("forwarder_host_jni");
     }
diff --git a/libs/vm_launcher_lib/java/com/android/virtualization/vmlauncher/InstallUtils.java b/android/TerminalApp/java/com/android/virtualization/terminal/InstallUtils.java
similarity index 98%
rename from libs/vm_launcher_lib/java/com/android/virtualization/vmlauncher/InstallUtils.java
rename to android/TerminalApp/java/com/android/virtualization/terminal/InstallUtils.java
index 4044fff..b17e636 100644
--- a/libs/vm_launcher_lib/java/com/android/virtualization/vmlauncher/InstallUtils.java
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/InstallUtils.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.virtualization.vmlauncher;
+package com.android.virtualization.terminal;
 
 import android.content.Context;
 import android.os.Environment;
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/InstallerActivity.java b/android/TerminalApp/java/com/android/virtualization/terminal/InstallerActivity.java
index db42b9f..a49403c 100644
--- a/android/TerminalApp/java/com/android/virtualization/terminal/InstallerActivity.java
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/InstallerActivity.java
@@ -35,7 +35,6 @@
 import android.widget.TextView;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.virtualization.vmlauncher.InstallUtils;
 
 import com.google.android.material.progressindicator.LinearProgressIndicator;
 import com.google.android.material.snackbar.Snackbar;
@@ -46,7 +45,7 @@
 public class InstallerActivity extends BaseActivity {
     private static final String TAG = "LinuxInstaller";
 
-    private static final long ESTIMATED_IMG_SIZE_BYTES = FileUtils.parseSize("350MB");
+    private static final long ESTIMATED_IMG_SIZE_BYTES = FileUtils.parseSize("550MB");
 
     private ExecutorService mExecutorService;
     private CheckBox mWaitForWifiCheckbox;
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/InstallerService.java b/android/TerminalApp/java/com/android/virtualization/terminal/InstallerService.java
index e0923ee..5d4c4ad 100644
--- a/android/TerminalApp/java/com/android/virtualization/terminal/InstallerService.java
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/InstallerService.java
@@ -33,7 +33,6 @@
 import androidx.annotation.Nullable;
 
 import com.android.internal.annotations.GuardedBy;
-import com.android.virtualization.vmlauncher.InstallUtils;
 
 import org.apache.commons.compress.archivers.ArchiveEntry;
 import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
diff --git a/libs/vm_launcher_lib/java/com/android/virtualization/vmlauncher/Logger.java b/android/TerminalApp/java/com/android/virtualization/terminal/Logger.java
similarity index 97%
rename from libs/vm_launcher_lib/java/com/android/virtualization/vmlauncher/Logger.java
rename to android/TerminalApp/java/com/android/virtualization/terminal/Logger.java
index e1cb285..2c0149e 100644
--- a/libs/vm_launcher_lib/java/com/android/virtualization/vmlauncher/Logger.java
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/Logger.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.virtualization.vmlauncher;
+package com.android.virtualization.terminal;
 
 import android.system.virtualmachine.VirtualMachine;
 import android.system.virtualmachine.VirtualMachineConfig;
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.java b/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.java
index 8d03a72..eb0e7e2 100644
--- a/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.java
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.java
@@ -54,9 +54,6 @@
 import androidx.activity.result.contract.ActivityResultContracts;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.virtualization.vmlauncher.InstallUtils;
-import com.android.virtualization.vmlauncher.VmLauncherService;
-import com.android.virtualization.vmlauncher.VmLauncherServices;
 
 import com.google.android.material.appbar.MaterialToolbar;
 
diff --git a/libs/vm_launcher_lib/java/com/android/virtualization/vmlauncher/Runner.java b/android/TerminalApp/java/com/android/virtualization/terminal/Runner.java
similarity index 98%
rename from libs/vm_launcher_lib/java/com/android/virtualization/vmlauncher/Runner.java
rename to android/TerminalApp/java/com/android/virtualization/terminal/Runner.java
index 9b97fee..a2247b1 100644
--- a/libs/vm_launcher_lib/java/com/android/virtualization/vmlauncher/Runner.java
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/Runner.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.virtualization.vmlauncher;
+package com.android.virtualization.terminal;
 
 import android.content.Context;
 import android.system.virtualmachine.VirtualMachine;
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/SettingsDiskResizeActivity.kt b/android/TerminalApp/java/com/android/virtualization/terminal/SettingsDiskResizeActivity.kt
index 7ccce9c..817808f 100644
--- a/android/TerminalApp/java/com/android/virtualization/terminal/SettingsDiskResizeActivity.kt
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/SettingsDiskResizeActivity.kt
@@ -27,7 +27,6 @@
 import android.widget.TextView
 import androidx.appcompat.app.AppCompatActivity
 import androidx.core.view.isVisible
-import com.android.virtualization.vmlauncher.InstallUtils
 import com.google.android.material.button.MaterialButton
 import com.google.android.material.slider.Slider
 import java.util.regex.Pattern
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/SettingsRecoveryActivity.kt b/android/TerminalApp/java/com/android/virtualization/terminal/SettingsRecoveryActivity.kt
index e2bb28f..ef76e03 100644
--- a/android/TerminalApp/java/com/android/virtualization/terminal/SettingsRecoveryActivity.kt
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/SettingsRecoveryActivity.kt
@@ -22,7 +22,6 @@
 import androidx.appcompat.app.AppCompatActivity
 import androidx.core.view.isVisible
 import androidx.lifecycle.lifecycleScope
-import com.android.virtualization.vmlauncher.InstallUtils
 import com.google.android.material.card.MaterialCardView
 import com.google.android.material.dialog.MaterialAlertDialogBuilder
 import com.google.android.material.snackbar.Snackbar
diff --git a/libs/vm_launcher_lib/java/com/android/virtualization/vmlauncher/VmLauncherService.java b/android/TerminalApp/java/com/android/virtualization/terminal/VmLauncherService.java
similarity index 97%
rename from libs/vm_launcher_lib/java/com/android/virtualization/vmlauncher/VmLauncherService.java
rename to android/TerminalApp/java/com/android/virtualization/terminal/VmLauncherService.java
index 1eb558e..25afcb7 100644
--- a/libs/vm_launcher_lib/java/com/android/virtualization/vmlauncher/VmLauncherService.java
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/VmLauncherService.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.virtualization.vmlauncher;
+package com.android.virtualization.terminal;
 
 import android.app.Notification;
 import android.app.Service;
@@ -123,8 +123,8 @@
         Path logPath = getFileStreamPath(mVirtualMachine.getName() + ".log").toPath();
         Logger.setup(mVirtualMachine, logPath, mExecutorService);
 
-        Notification notification = intent.getParcelableExtra(EXTRA_NOTIFICATION,
-                Notification.class);
+        Notification notification =
+                intent.getParcelableExtra(EXTRA_NOTIFICATION, Notification.class);
 
         startForeground(notification);
 
diff --git a/libs/vm_launcher_lib/java/com/android/virtualization/vmlauncher/VmLauncherServices.java b/android/TerminalApp/java/com/android/virtualization/terminal/VmLauncherServices.java
similarity index 95%
rename from libs/vm_launcher_lib/java/com/android/virtualization/vmlauncher/VmLauncherServices.java
rename to android/TerminalApp/java/com/android/virtualization/terminal/VmLauncherServices.java
index 6eca2b3..d6c6786 100644
--- a/libs/vm_launcher_lib/java/com/android/virtualization/vmlauncher/VmLauncherServices.java
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/VmLauncherServices.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.virtualization.vmlauncher;
+package com.android.virtualization.terminal;
 
 import android.app.Notification;
 import android.content.Context;
@@ -67,8 +67,8 @@
         context.stopService(i);
     }
 
-    public static void startVmLauncherService(Context context, VmLauncherServiceCallback callback,
-            Notification notification) {
+    public static void startVmLauncherService(
+            Context context, VmLauncherServiceCallback callback, Notification notification) {
         Intent i = buildVmLauncherServiceIntent(context);
         if (i == null) {
             return;
diff --git a/android/TerminalApp/proguard.flags b/android/TerminalApp/proguard.flags
index 8433e82..88b8a9c 100644
--- a/android/TerminalApp/proguard.flags
+++ b/android/TerminalApp/proguard.flags
@@ -11,8 +11,8 @@
 #-keep class com.google.gson.stream.** { *; }
 
 # Application classes that will be serialized/deserialized over Gson
--keep class com.android.virtualization.vmlauncher.ConfigJson { <fields>; }
--keep class com.android.virtualization.vmlauncher.ConfigJson$* { <fields>; }
+-keep class com.android.virtualization.terminal.ConfigJson { <fields>; }
+-keep class com.android.virtualization.terminal.ConfigJson$* { <fields>; }
 
 # Prevent proguard from stripping interface information from TypeAdapter, TypeAdapterFactory,
 # JsonSerializer, JsonDeserializer instances (so they can be used in @JsonAdapter)
diff --git a/android/TerminalApp/res/layout/settings_disk_resize.xml b/android/TerminalApp/res/layout/settings_disk_resize.xml
index 1a2b5ef..21ff070 100644
--- a/android/TerminalApp/res/layout/settings_disk_resize.xml
+++ b/android/TerminalApp/res/layout/settings_disk_resize.xml
@@ -29,6 +29,7 @@
         android:layout_width="wrap_content"
         android:text="@string/settings_disk_resize_title"
         android:textSize="48sp"
+        android:hyphenationFrequency="normal"
         android:layout_marginBottom="24dp"/>
 
     <androidx.constraintlayout.widget.ConstraintLayout
diff --git a/android/TerminalApp/res/layout/settings_port_forwarding.xml b/android/TerminalApp/res/layout/settings_port_forwarding.xml
index 199b8cb..98ba02c 100644
--- a/android/TerminalApp/res/layout/settings_port_forwarding.xml
+++ b/android/TerminalApp/res/layout/settings_port_forwarding.xml
@@ -28,6 +28,7 @@
         android:layout_width="wrap_content"
         android:text="@string/settings_port_forwarding_title"
         android:textSize="48sp"
+        android:hyphenationFrequency="normal"
         android:layout_marginBottom="24dp"/>
 
     <androidx.recyclerview.widget.RecyclerView
@@ -35,4 +36,4 @@
         android:layout_marginHorizontal="16dp"
         android:layout_width="match_parent"
         android:layout_height="match_parent" />
-</LinearLayout>
\ No newline at end of file
+</LinearLayout>
diff --git a/android/TerminalApp/res/layout/settings_recovery.xml b/android/TerminalApp/res/layout/settings_recovery.xml
index 3f83588..c72447f 100644
--- a/android/TerminalApp/res/layout/settings_recovery.xml
+++ b/android/TerminalApp/res/layout/settings_recovery.xml
@@ -28,6 +28,7 @@
         android:layout_width="wrap_content"
         android:text="@string/settings_recovery_title"
         android:textSize="48sp"
+        android:hyphenationFrequency="normal"
         android:layout_marginStart="24dp"
         android:layout_marginBottom="24dp"/>
     <FrameLayout
diff --git a/android/VmLauncherApp/Android.bp b/android/VmLauncherApp/Android.bp
deleted file mode 100644
index 2e8cc93..0000000
--- a/android/VmLauncherApp/Android.bp
+++ /dev/null
@@ -1,29 +0,0 @@
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-android_app {
-    name: "VmLauncherApp",
-    srcs: ["java/**/*.java"],
-    resource_dirs: ["res"],
-    static_libs: [
-        // TODO(b/330257000): will be removed when binder RPC is used
-        "android.system.virtualizationservice_internal-java",
-        // TODO(b/331708504): will be removed when AVF framework handles surface
-        "libcrosvm_android_display_service-java",
-        "vm_launcher_lib",
-    ],
-    libs: [
-        "framework-virtualization.impl",
-        "framework-annotations-lib",
-    ],
-    platform_apis: true,
-    privileged: true,
-    apex_available: [
-        "com.android.virt",
-    ],
-    optimize: {
-        proguard_flags_files: ["proguard.flags"],
-        shrink_resources: true,
-    },
-}
diff --git a/android/VmLauncherApp/AndroidManifest.xml b/android/VmLauncherApp/AndroidManifest.xml
deleted file mode 100644
index 4fb4b5c..0000000
--- a/android/VmLauncherApp/AndroidManifest.xml
+++ /dev/null
@@ -1,32 +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" />
-
-    <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>
-
-    </application>
-
-</manifest>
diff --git a/android/VmLauncherApp/README.md b/android/VmLauncherApp/README.md
deleted file mode 100644
index 0109f37..0000000
--- a/android/VmLauncherApp/README.md
+++ /dev/null
@@ -1,19 +0,0 @@
-# VM launcher app
-
-## Building
-
-This app is now part of the virt APEX.
-
-## Enabling
-
-This app is disabled by default. To re-enable it, execute the following command.
-
-```
-adb root
-adb shell pm enable com.android.virtualization.vmlauncher/.MainActivity
-```
-
-## Running
-
-Copy vm config json file to /data/local/tmp/vm_config.json.
-And then, run the app, check log meesage.
diff --git a/android/VmLauncherApp/java/com/android/virtualization/vmlauncher/ClipboardHandler.java b/android/VmLauncherApp/java/com/android/virtualization/vmlauncher/ClipboardHandler.java
deleted file mode 100644
index def464e..0000000
--- a/android/VmLauncherApp/java/com/android/virtualization/vmlauncher/ClipboardHandler.java
+++ /dev/null
@@ -1,84 +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 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() throws InterruptedException {
-        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 (InterruptedException | 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 (InterruptedException | 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/DisplayProvider.java b/android/VmLauncherApp/java/com/android/virtualization/vmlauncher/DisplayProvider.java
deleted file mode 100644
index 6eba709..0000000
--- a/android/VmLauncherApp/java/com/android/virtualization/vmlauncher/DisplayProvider.java
+++ /dev/null
@@ -1,210 +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 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
deleted file mode 100644
index 1be362b..0000000
--- a/android/VmLauncherApp/java/com/android/virtualization/vmlauncher/InputForwarder.java
+++ /dev/null
@@ -1,148 +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 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/MainActivity.java b/android/VmLauncherApp/java/com/android/virtualization/vmlauncher/MainActivity.java
deleted file mode 100644
index e18b0d1..0000000
--- a/android/VmLauncherApp/java/com/android/virtualization/vmlauncher/MainActivity.java
+++ /dev/null
@@ -1,194 +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 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;
-    private OpenUrlHandler mOpenUrlHandler;
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        Log.d(TAG, "onCreate intent: " + getIntent());
-        checkAndRequestRecordAudioPermission();
-        mExecutorService = Executors.newCachedThreadPool();
-
-        ConfigJson json = ConfigJson.from(VM_CONFIG_PATH);
-        VirtualMachineConfig config = json.toConfigBuilder(this).build();
-
-        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);
-        mOpenUrlHandler = new OpenUrlHandler(mVmAgent);
-        handleIntent(getIntent());
-    }
-
-    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();
-        mOpenUrlHandler.shutdown();
-        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) {
-        Log.d(TAG, "onNewIntent intent: " + intent);
-        handleIntent(intent);
-    }
-
-    private void handleIntent(Intent intent) {
-        if (ACTION_VM_OPEN_URL.equals(intent.getAction())) {
-            String url = intent.getStringExtra(Intent.EXTRA_TEXT);
-            if (url != null) {
-                mOpenUrlHandler.sendUrlToVm(url);
-            }
-        }
-    }
-
-    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/OpenUrlHandler.java b/android/VmLauncherApp/java/com/android/virtualization/vmlauncher/OpenUrlHandler.java
deleted file mode 100644
index fb0c6bf..0000000
--- a/android/VmLauncherApp/java/com/android/virtualization/vmlauncher/OpenUrlHandler.java
+++ /dev/null
@@ -1,50 +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 android.util.Log;
-
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-
-class OpenUrlHandler {
-    private static final String TAG = MainActivity.TAG;
-
-    private final VmAgent mVmAgent;
-    private final ExecutorService mExecutorService;
-
-    OpenUrlHandler(VmAgent vmAgent) {
-        mVmAgent = vmAgent;
-        mExecutorService = Executors.newSingleThreadExecutor();
-    }
-
-    void shutdown() {
-        mExecutorService.shutdownNow();
-    }
-
-    void sendUrlToVm(String url) {
-        mExecutorService.execute(
-                () -> {
-                    try {
-                        mVmAgent.connect().sendData(VmAgent.OPEN_URL, url.getBytes());
-                        Log.d(TAG, "Successfully sent URL to the VM");
-                    } catch (InterruptedException | RuntimeException e) {
-                        Log.e(TAG, "Failed to send URL to the VM", e);
-                    }
-                });
-    }
-}
diff --git a/android/VmLauncherApp/java/com/android/virtualization/vmlauncher/VmAgent.java b/android/VmLauncherApp/java/com/android/virtualization/vmlauncher/VmAgent.java
deleted file mode 100644
index af1d298..0000000
--- a/android/VmLauncherApp/java/com/android/virtualization/vmlauncher/VmAgent.java
+++ /dev/null
@@ -1,145 +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 android.os.ParcelFileDescriptor;
-import android.os.SystemClock;
-import android.system.virtualmachine.VirtualMachine;
-import android.system.virtualmachine.VirtualMachineException;
-import android.util.Log;
-
-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
-    private static final long RETRY_INTERVAL_MS = 1_000;
-
-    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;
-    }
-
-    /**
-     * Connects to the agent and returns the established communication channel. This can block.
-     *
-     * @throws InterruptedException If the current thread was interrupted
-     */
-    Connection connect() throws InterruptedException {
-        boolean shouldLog = true;
-        while (true) {
-            if (Thread.interrupted()) {
-                throw new InterruptedException();
-            }
-            try {
-                return new Connection(mVirtualMachine.connectVsock(DATA_SHARING_SERVICE_PORT));
-            } catch (VirtualMachineException e) {
-                if (shouldLog) {
-                    shouldLog = false;
-                    Log.d(TAG, "Still waiting for VM agent to start", e);
-                }
-            }
-            SystemClock.sleep(RETRY_INTERVAL_MS);
-        }
-    }
-
-    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/proguard.flags b/android/VmLauncherApp/proguard.flags
deleted file mode 100644
index b93240c..0000000
--- a/android/VmLauncherApp/proguard.flags
+++ /dev/null
@@ -1,33 +0,0 @@
-##---------------Begin: proguard configuration for Gson  ----------
-# Gson uses generic type information stored in a class file when working with fields. Proguard
-# removes such information by default, so configure it to keep all of it.
--keepattributes Signature
-
-# For using GSON @Expose annotation
--keepattributes *Annotation*
-
-# Gson specific classes
--dontwarn sun.misc.**
-#-keep class com.google.gson.stream.** { *; }
-
-# Application classes that will be serialized/deserialized over Gson
--keep class com.android.virtualization.vmlauncher.ConfigJson { <fields>; }
--keep class com.android.virtualization.vmlauncher.ConfigJson$* { <fields>; }
-
-# Prevent proguard from stripping interface information from TypeAdapter, TypeAdapterFactory,
-# JsonSerializer, JsonDeserializer instances (so they can be used in @JsonAdapter)
--keep class * extends com.google.gson.TypeAdapter
--keep class * implements com.google.gson.TypeAdapterFactory
--keep class * implements com.google.gson.JsonSerializer
--keep class * implements com.google.gson.JsonDeserializer
-
-# Prevent R8 from leaving Data object members always null
--keepclassmembers,allowobfuscation class * {
-  @com.google.gson.annotations.SerializedName <fields>;
-}
-
-# Retain generic signatures of TypeToken and its subclasses with R8 version 3.0 and higher.
--keep,allowobfuscation,allowshrinking class com.google.gson.reflect.TypeToken
--keep,allowobfuscation,allowshrinking class * extends com.google.gson.reflect.TypeToken
-
-##---------------End: proguard configuration for Gson  ----------
\ No newline at end of file
diff --git a/android/VmLauncherApp/res/layout/activity_main.xml b/android/VmLauncherApp/res/layout/activity_main.xml
deleted file mode 100644
index a80ece0..0000000
--- a/android/VmLauncherApp/res/layout/activity_main.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<merge 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"
-    tools:context=".MainActivity">
-  <View
-      android:id="@+id/background_touch_view"
-      android:layout_width="match_parent"
-      android:layout_height="match_parent"
-    />
-  <SurfaceView
-      android:id="@+id/surface_view"
-      android:layout_width="match_parent"
-      android:layout_height="match_parent"
-      android:focusable="true"
-      android:focusableInTouchMode="true"
-      android:focusedByDefault="true"
-      android:defaultFocusHighlightEnabled="true">
-    <requestFocus />
-  </SurfaceView>
-  <!-- A cursor size in virtio-gpu spec is always 64x64 -->
-  <SurfaceView
-      android:id="@+id/cursor_surface_view"
-      android:layout_width="64px"
-      android:layout_height="64px">
-  </SurfaceView>
-
-</merge>
diff --git a/android/VmLauncherApp/res/values/themes.xml b/android/VmLauncherApp/res/values/themes.xml
deleted file mode 100644
index c10b6d9..0000000
--- a/android/VmLauncherApp/res/values/themes.xml
+++ /dev/null
@@ -1,14 +0,0 @@
-
-<resources xmlns:tools="http://schemas.android.com/tools">
-    <style name="MyTheme" parent="@android:style/Theme.DeviceDefault.NoActionBar">
-        <item name="android:navigationBarColor">
-            @android:color/transparent
-        </item>
-        <item name="android:statusBarColor">
-            @android:color/transparent
-        </item>
-        <item name="android:windowLayoutInDisplayCutoutMode">
-            always
-        </item>
-    </style>
-</resources>
diff --git a/android/forwarder_host/src/forwarder_host.rs b/android/forwarder_host/src/forwarder_host.rs
index 7496a02..2138957 100644
--- a/android/forwarder_host/src/forwarder_host.rs
+++ b/android/forwarder_host/src/forwarder_host.rs
@@ -378,7 +378,7 @@
 
 /// JNI function for running forwarder_host.
 #[no_mangle]
-pub extern "C" fn Java_com_android_virtualization_vmlauncher_DebianServiceImpl_runForwarderHost(
+pub extern "C" fn Java_com_android_virtualization_terminal_DebianServiceImpl_runForwarderHost(
     env: JNIEnv,
     _class: JObject,
     cid: jint,
@@ -396,7 +396,7 @@
 
 /// JNI function for terminating forwarder_host.
 #[no_mangle]
-pub extern "C" fn Java_com_android_virtualization_vmlauncher_DebianServiceImpl_terminateForwarderHost(
+pub extern "C" fn Java_com_android_virtualization_terminal_DebianServiceImpl_terminateForwarderHost(
     _env: JNIEnv,
     _class: JObject,
 ) {
@@ -405,7 +405,7 @@
 
 /// JNI function for updating listening ports.
 #[no_mangle]
-pub extern "C" fn Java_com_android_virtualization_vmlauncher_DebianServiceImpl_updateListeningPorts(
+pub extern "C" fn Java_com_android_virtualization_terminal_DebianServiceImpl_updateListeningPorts(
     env: JNIEnv,
     _class: JObject,
     ports: JIntArray,
diff --git a/build/debian/build.sh b/build/debian/build.sh
index 899e376..59a98b6 100755
--- a/build/debian/build.sh
+++ b/build/debian/build.sh
@@ -183,13 +183,13 @@
 	root_partition_num=1
 	efi_partition_num=15
 
-	loop=$(losetup -f --show --partscan image.raw)
+	loop=$(losetup -f --show --partscan $built_image)
 	dd if=${loop}p$root_partition_num of=root_part
 	dd if=${loop}p$efi_partition_num of=efi_part
 	losetup -d ${loop}
 
-	sed -i "s/{root_part_guid}/$(sfdisk --part-uuid image.raw $root_partition_num)/g" vm_config.json
-	sed -i "s/{efi_part_guid}/$(sfdisk --part-uuid image.raw $efi_partition_num)/g" vm_config.json
+	sed -i "s/{root_part_guid}/$(sfdisk --part-uuid $built_image $root_partition_num)/g" vm_config.json
+	sed -i "s/{efi_part_guid}/$(sfdisk --part-uuid $built_image $efi_partition_num)/g" vm_config.json
 }
 
 clean_up() {
@@ -214,7 +214,7 @@
 download_debian_cloud_image
 copy_android_config
 run_fai
-fdisk -l image.raw
+fdisk -l "${built_image}"
 images=()
 
 cp $(dirname $0)/vm_config.json.${arch} vm_config.json
@@ -229,11 +229,11 @@
 
 # TODO(b/365955006): remove these lines when uboot supports x86_64 EFI application
 if [[ "$arch" == "x86_64" ]]; then
-	virt-get-kernel -a image.raw
+	virt-get-kernel -a "${built_image}"
 	mv vmlinuz* vmlinuz
 	mv initrd.img* initrd.img
 	images+=(
-		image.raw
+		"${built_image}"
 		vmlinuz
 		initrd.img
 	)
diff --git a/build/debian/fai_config/files/etc/systemd/system/forwarder_guest_launcher.service/AVF b/build/debian/fai_config/files/etc/systemd/system/forwarder_guest_launcher.service/AVF
index 6dbabea..f4c2a24 100644
--- a/build/debian/fai_config/files/etc/systemd/system/forwarder_guest_launcher.service/AVF
+++ b/build/debian/fai_config/files/etc/systemd/system/forwarder_guest_launcher.service/AVF
@@ -4,7 +4,7 @@
 After=network.target
 After=virtiofs_internal.service
 [Service]
-ExecStart=/usr/bin/bash -c 'RUST_LOG=debug /usr/local/bin/forwarder_guest_launcher --host 192.168.0.1 --grpc_port $(cat /mnt/internal/debian_service_port)'
+ExecStart=/usr/bin/bash -c '/usr/local/bin/forwarder_guest_launcher --host 192.168.0.1 --grpc_port $(cat /mnt/internal/debian_service_port)'
 Type=simple
 Restart=on-failure
 RestartSec=1
diff --git a/docs/custom_vm.md b/docs/custom_vm.md
index 2863eb6..9a9ede4 100644
--- a/docs/custom_vm.md
+++ b/docs/custom_vm.md
@@ -24,85 +24,3 @@
 
 The `vm` command also has other subcommands for debugging; run
 `/apex/com.android.virt/bin/vm help` for details.
-
-### 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",
-    "disks": [
-        {
-            "image": "/data/local/tmp/debian.img",
-            "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": "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 vm_config.json /data/local/tmp/
-```
-
-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
-```
diff --git a/guest/forwarder_guest_launcher/src/main.rs b/guest/forwarder_guest_launcher/src/main.rs
index c3fdd7e..0bb3b4d 100644
--- a/guest/forwarder_guest_launcher/src/main.rs
+++ b/guest/forwarder_guest_launcher/src/main.rs
@@ -31,7 +31,7 @@
 use tonic::Request;
 
 mod debian_service {
-    tonic::include_proto!("com.android.virtualization.vmlauncher.proto");
+    tonic::include_proto!("com.android.virtualization.terminal.proto");
 }
 
 const NON_PREVILEGED_PORT_RANGE_START: i32 = 1024;
@@ -110,6 +110,7 @@
 async fn report_active_ports(
     mut client: DebianServiceClient<Channel>,
 ) -> Result<(), Box<dyn std::error::Error>> {
+    // TODO: we can remove python3 -u when https://github.com/iovisor/bcc/pull/5142 is deployed
     let mut cmd = Command::new("python3")
         .arg("-u")
         .arg("/usr/sbin/tcpstates-bpfcc")
diff --git a/guest/ip_addr_reporter/src/main.rs b/guest/ip_addr_reporter/src/main.rs
index 2c782d3..62a7aef 100644
--- a/guest/ip_addr_reporter/src/main.rs
+++ b/guest/ip_addr_reporter/src/main.rs
@@ -3,7 +3,7 @@
 
 use clap::Parser;
 pub mod api {
-    tonic::include_proto!("com.android.virtualization.vmlauncher.proto");
+    tonic::include_proto!("com.android.virtualization.terminal.proto");
 }
 
 #[derive(Parser)]
diff --git a/guest/pvmfw/Android.bp b/guest/pvmfw/Android.bp
index 5c767a3..51f7802 100644
--- a/guest/pvmfw/Android.bp
+++ b/guest/pvmfw/Android.bp
@@ -118,6 +118,7 @@
         "libciborium",
         "libdiced_open_dice_nostd",
         "libpvmfw_avb_nostd",
+        "libdiced_sample_inputs_nostd",
         "libzerocopy_nostd",
         "libhex",
     ],
diff --git a/guest/pvmfw/platform.dts b/guest/pvmfw/platform.dts
index 44834ed..c3ecd0e 100644
--- a/guest/pvmfw/platform.dts
+++ b/guest/pvmfw/platform.dts
@@ -4,6 +4,11 @@
 
 #include <dt-bindings/interrupt-controller/arm-gic.h>
 
+// Undefine macros conflicting with our definitions.
+#ifdef linux
+#undef linux
+#endif
+
 #define PLACEHOLDER	0xffffffff
 #define PLACEHOLDER2	PLACEHOLDER PLACEHOLDER
 #define PLACEHOLDER4	PLACEHOLDER2 PLACEHOLDER2
diff --git a/guest/pvmfw/src/dice.rs b/guest/pvmfw/src/dice.rs
index f3a2337..b597309 100644
--- a/guest/pvmfw/src/dice.rs
+++ b/guest/pvmfw/src/dice.rs
@@ -21,7 +21,7 @@
 use ciborium::Value;
 use core::mem::size_of;
 use diced_open_dice::{
-    bcc_handover_main_flow, hash, Config, DiceMode, Hash, InputValues, HIDDEN_SIZE,
+    bcc_handover_main_flow, hash, Config, DiceContext, DiceMode, Hash, InputValues, HIDDEN_SIZE,
 };
 use pvmfw_avb::{Capability, DebugLevel, Digest, VerifiedBootData};
 use zerocopy::AsBytes;
@@ -102,6 +102,7 @@
         instance_hash: Option<Hash>,
         deferred_rollback_protection: bool,
         next_bcc: &mut [u8],
+        context: DiceContext,
     ) -> Result<()> {
         let config = self
             .generate_config_descriptor(instance_hash)
@@ -114,7 +115,7 @@
             self.mode,
             self.make_hidden(salt, deferred_rollback_protection)?,
         );
-        let _ = bcc_handover_main_flow(current_bcc_handover, &dice_inputs, next_bcc)?;
+        let _ = bcc_handover_main_flow(current_bcc_handover, &dice_inputs, next_bcc, context)?;
         Ok(())
     }
 
@@ -177,8 +178,11 @@
     };
     use ciborium::Value;
     use diced_open_dice::DiceArtifacts;
+    use diced_open_dice::DiceContext;
     use diced_open_dice::DiceMode;
+    use diced_open_dice::KeyAlgorithm;
     use diced_open_dice::HIDDEN_SIZE;
+    use diced_sample_inputs::make_sample_bcc_and_cdis;
     use pvmfw_avb::Capability;
     use pvmfw_avb::DebugLevel;
     use pvmfw_avb::Digest;
@@ -298,6 +302,10 @@
         let mut buffer_without_defer = [0; 4096];
         let mut buffer_with_defer = [0; 4096];
         let mut buffer_without_defer_retry = [0; 4096];
+        let context = DiceContext {
+            authority_algorithm: KeyAlgorithm::Ed25519,
+            subject_algorithm: KeyAlgorithm::Ed25519,
+        };
 
         let sample_dice_input: &[u8] = &[
             0xa3, // CDI attest
@@ -321,6 +329,7 @@
                 Some([0u8; 64]),
                 false,
                 &mut buffer_without_defer,
+                context.clone(),
             )
             .unwrap();
         let bcc_handover1 = diced_open_dice::bcc_handover_parse(&buffer_without_defer).unwrap();
@@ -333,6 +342,7 @@
                 Some([0u8; 64]),
                 true,
                 &mut buffer_with_defer,
+                context.clone(),
             )
             .unwrap();
         let bcc_handover2 = diced_open_dice::bcc_handover_parse(&buffer_with_defer).unwrap();
@@ -345,6 +355,7 @@
                 Some([0u8; 64]),
                 false,
                 &mut buffer_without_defer_retry,
+                context.clone(),
             )
             .unwrap();
         let bcc_handover3 =
@@ -353,4 +364,82 @@
         assert_ne!(bcc_handover1.cdi_seal(), bcc_handover2.cdi_seal());
         assert_eq!(bcc_handover1.cdi_seal(), bcc_handover3.cdi_seal());
     }
+
+    #[test]
+    fn dice_derivation_with_different_algorithms_is_valid() {
+        let dice_artifacts = make_sample_bcc_and_cdis().unwrap();
+        let bcc_handover0_bytes = to_bcc_handover(&dice_artifacts);
+        let vb_data = VerifiedBootData { debug_level: DebugLevel::Full, ..BASE_VB_DATA };
+        let inputs = PartialInputs::new(&vb_data).unwrap();
+        let mut buffer = [0; 4096];
+
+        inputs
+            .clone()
+            .write_next_bcc(
+                &bcc_handover0_bytes,
+                &[0u8; HIDDEN_SIZE],
+                Some([0u8; 64]),
+                true,
+                &mut buffer,
+                DiceContext {
+                    authority_algorithm: KeyAlgorithm::Ed25519,
+                    subject_algorithm: KeyAlgorithm::EcdsaP256,
+                },
+            )
+            .expect("Failed to derive Ed25519 -> EcdsaP256 BCC");
+        let bcc_handover1 = diced_open_dice::bcc_handover_parse(&buffer).unwrap();
+        let bcc_handover1_bytes = to_bcc_handover(&bcc_handover1);
+        buffer.fill(0);
+
+        inputs
+            .clone()
+            .write_next_bcc(
+                &bcc_handover1_bytes,
+                &[0u8; HIDDEN_SIZE],
+                Some([0u8; 64]),
+                true,
+                &mut buffer,
+                DiceContext {
+                    authority_algorithm: KeyAlgorithm::EcdsaP256,
+                    subject_algorithm: KeyAlgorithm::EcdsaP384,
+                },
+            )
+            .expect("Failed to derive EcdsaP256 -> EcdsaP384 BCC");
+        let bcc_handover2 = diced_open_dice::bcc_handover_parse(&buffer).unwrap();
+        let bcc_handover2_bytes = to_bcc_handover(&bcc_handover2);
+        buffer.fill(0);
+
+        inputs
+            .clone()
+            .write_next_bcc(
+                &bcc_handover2_bytes,
+                &[0u8; HIDDEN_SIZE],
+                Some([0u8; 64]),
+                true,
+                &mut buffer,
+                DiceContext {
+                    authority_algorithm: KeyAlgorithm::EcdsaP384,
+                    subject_algorithm: KeyAlgorithm::Ed25519,
+                },
+            )
+            .expect("Failed to derive EcdsaP384 -> Ed25519 BCC");
+        let _bcc_handover3 = diced_open_dice::bcc_handover_parse(&buffer).unwrap();
+
+        // TODO(b/378813154): Check the DICE chain with `hwtrust` once the profile version
+        // is updated.
+        // The check cannot be done now because parsing the chain causes the following error:
+        // Invalid payload at index 3. Caused by:
+        // 0: opendice.example.p256
+        // 1: unknown profile version
+    }
+
+    fn to_bcc_handover(dice_artifacts: &dyn DiceArtifacts) -> Vec<u8> {
+        let dice_chain = cbor_util::deserialize::<Value>(dice_artifacts.bcc().unwrap()).unwrap();
+        let bcc_handover = Value::Map(vec![
+            (Value::Integer(1.into()), Value::Bytes(dice_artifacts.cdi_attest().to_vec())),
+            (Value::Integer(2.into()), Value::Bytes(dice_artifacts.cdi_seal().to_vec())),
+            (Value::Integer(3.into()), dice_chain),
+        ]);
+        cbor_util::serialize(&bcc_handover).unwrap()
+    }
 }
diff --git a/guest/pvmfw/src/main.rs b/guest/pvmfw/src/main.rs
index aeced51..2c1eba9 100644
--- a/guest/pvmfw/src/main.rs
+++ b/guest/pvmfw/src/main.rs
@@ -45,7 +45,7 @@
 use bssl_avf::Digester;
 use core::ops::Range;
 use cstr::cstr;
-use diced_open_dice::{bcc_handover_parse, DiceArtifacts, Hidden};
+use diced_open_dice::{bcc_handover_parse, DiceArtifacts, DiceContext, Hidden, VM_KEY_ALGORITHM};
 use libfdt::{Fdt, FdtNode};
 use log::{debug, error, info, trace, warn};
 use pvmfw_avb::verify_payload;
@@ -202,6 +202,13 @@
 
     trace!("BCC leaf subject public key algorithm: {:?}", bcc.leaf_subject_pubkey().cose_alg);
 
+    let dice_context = DiceContext {
+        authority_algorithm: bcc.leaf_subject_pubkey().cose_alg.try_into().map_err(|e| {
+            error!("{e}");
+            RebootReason::InternalError
+        })?,
+        subject_algorithm: VM_KEY_ALGORITHM,
+    };
     dice_inputs
         .write_next_bcc(
             new_bcc_handover.as_ref(),
@@ -209,6 +216,7 @@
             instance_hash,
             defer_rollback_protection,
             next_bcc,
+            dice_context,
         )
         .map_err(|e| {
             error!("Failed to derive next-stage DICE secrets: {e:?}");
diff --git a/libs/debian_service/proto/DebianService.proto b/libs/debian_service/proto/DebianService.proto
index bf05ebe..61bcece 100644
--- a/libs/debian_service/proto/DebianService.proto
+++ b/libs/debian_service/proto/DebianService.proto
@@ -16,9 +16,9 @@
 
 syntax = "proto3";
 
-package com.android.virtualization.vmlauncher.proto;
+package com.android.virtualization.terminal.proto;
 
-option java_package = "com.android.virtualization.vmlauncher.proto";
+option java_package = "com.android.virtualization.terminal.proto";
 option java_multiple_files = true;
 
 service DebianService {
diff --git a/libs/dice/open_dice/Android.bp b/libs/dice/open_dice/Android.bp
index c60260e..4241c47 100644
--- a/libs/dice/open_dice/Android.bp
+++ b/libs/dice/open_dice/Android.bp
@@ -19,6 +19,9 @@
         "libopen_dice_cbor_bindgen_nostd",
         "libzeroize_nostd",
     ],
+    features: [
+        "multialg",
+    ],
     whole_static_libs: [
         "libcrypto_baremetal",
     ],
@@ -132,6 +135,7 @@
         "--rustified-enum DiceConfigType",
         "--rustified-enum DiceMode",
         "--rustified-enum DiceResult",
+        "--rustified-enum DicePrincipal",
 
         // By generating only essential functions, we can make bindings concise and
         // optimize compilation time.
@@ -171,7 +175,11 @@
         "libopen_dice_cbor_bindgen.rust_defaults",
         "libopen_dice_bindgen_nostd.rust_defaults",
     ],
-    whole_static_libs: ["libopen_dice_cbor_baremetal"],
+    bindgen_flags: [
+        "--rustified-enum DiceKeyAlgorithm",
+        "--allowlist-type=DiceContext",
+    ],
+    whole_static_libs: ["libopen_dice_cbor_baremetal_multialg"],
 }
 
 rust_defaults {
@@ -227,7 +235,7 @@
     rustlibs: [
         "libopen_dice_cbor_bindgen_nostd",
     ],
-    whole_static_libs: ["libopen_dice_android_baremetal"],
+    whole_static_libs: ["libopen_dice_android_baremetal_multialg"],
 }
 
 rust_test {
diff --git a/libs/dice/open_dice/src/bcc.rs b/libs/dice/open_dice/src/bcc.rs
index 9c9545b..fabd7c7 100644
--- a/libs/dice/open_dice/src/bcc.rs
+++ b/libs/dice/open_dice/src/bcc.rs
@@ -14,13 +14,13 @@
 
 //! This module mirrors the content in open-dice/include/dice/android.h
 
-use crate::dice::{Cdi, CdiValues, DiceArtifacts, InputValues, CDI_SIZE};
+use crate::dice::{context, Cdi, CdiValues, DiceArtifacts, InputValues, CDI_SIZE};
 use crate::error::{check_result, DiceError, Result};
 use open_dice_android_bindgen::{
-    DiceAndroidConfigValues, DiceAndroidFormatConfigDescriptor, DiceAndroidHandoverMainFlow,
-    DiceAndroidHandoverParse, DiceAndroidMainFlow, DICE_ANDROID_CONFIG_COMPONENT_NAME,
-    DICE_ANDROID_CONFIG_COMPONENT_VERSION, DICE_ANDROID_CONFIG_RESETTABLE,
-    DICE_ANDROID_CONFIG_RKP_VM_MARKER, DICE_ANDROID_CONFIG_SECURITY_VERSION,
+    DiceAndroidConfigValues, DiceAndroidFormatConfigDescriptor, DiceAndroidHandoverParse,
+    DiceAndroidMainFlow, DICE_ANDROID_CONFIG_COMPONENT_NAME, DICE_ANDROID_CONFIG_COMPONENT_VERSION,
+    DICE_ANDROID_CONFIG_RESETTABLE, DICE_ANDROID_CONFIG_RKP_VM_MARKER,
+    DICE_ANDROID_CONFIG_SECURITY_VERSION,
 };
 use std::{ffi::CStr, ptr};
 
@@ -101,10 +101,11 @@
         // SAFETY: `DiceAndroidMainFlow` only reads the `current_chain` and CDI values and writes
         // to `next_chain` and next CDI values within its bounds. It also reads `input_values` as a
         // constant input and doesn't store any pointer.
-        // The first argument can be null and is not used in the current implementation.
+        // The first argument is a pointer to a valid |DiceContext_| object for multi-alg open-dice
+        // and a null pointer otherwise.
         unsafe {
             DiceAndroidMainFlow(
-                ptr::null_mut(), // context
+                context(),
                 current_cdi_attest.as_ptr(),
                 current_cdi_seal.as_ptr(),
                 current_chain.as_ptr(),
@@ -127,20 +128,23 @@
 /// A handover combines the DICE chain and CDIs in a single CBOR object.
 /// This function takes the current boot stage's handover bundle and produces a
 /// bundle for the next stage.
+#[cfg(feature = "multialg")]
 pub fn bcc_handover_main_flow(
     current_handover: &[u8],
     input_values: &InputValues,
     next_handover: &mut [u8],
+    ctx: crate::dice::DiceContext,
 ) -> Result<usize> {
     let mut next_handover_size = 0;
+    let mut ctx: open_dice_cbor_bindgen::DiceContext_ = ctx.into();
     check_result(
         // SAFETY: The function only reads `current_handover` and writes to `next_handover`
         // within its bounds,
         // It also reads `input_values` as a constant input and doesn't store any pointer.
-        // The first argument can be null and is not used in the current implementation.
+        // The first argument is a pointer to a valid |DiceContext_| object.
         unsafe {
-            DiceAndroidHandoverMainFlow(
-                ptr::null_mut(), // context
+            open_dice_android_bindgen::DiceAndroidHandoverMainFlow(
+                &mut ctx as *mut _ as *mut std::ffi::c_void,
                 current_handover.as_ptr(),
                 current_handover.len(),
                 input_values.as_ptr(),
diff --git a/libs/dice/open_dice/src/dice.rs b/libs/dice/open_dice/src/dice.rs
index 6404508..206769c 100644
--- a/libs/dice/open_dice/src/dice.rs
+++ b/libs/dice/open_dice/src/dice.rs
@@ -15,7 +15,7 @@
 //! Structs and functions about the types used in DICE.
 //! This module mirrors the content in open-dice/include/dice/dice.h
 
-use crate::error::{check_result, Result};
+use crate::error::{check_result, DiceError, Result};
 use coset::iana;
 pub use open_dice_cbor_bindgen::DiceMode;
 use open_dice_cbor_bindgen::{
@@ -23,9 +23,11 @@
     DiceMainFlow, DICE_CDI_SIZE, DICE_HASH_SIZE, DICE_HIDDEN_SIZE, DICE_ID_SIZE,
     DICE_INLINE_CONFIG_SIZE, DICE_PRIVATE_KEY_SEED_SIZE, DICE_PRIVATE_KEY_SIZE,
 };
+#[cfg(feature = "multialg")]
+use open_dice_cbor_bindgen::{DiceContext_, DiceKeyAlgorithm};
 #[cfg(feature = "serde_derive")]
 use serde_derive::{Deserialize, Serialize};
-use std::{marker::PhantomData, ptr};
+use std::{ffi::c_void, marker::PhantomData, ptr};
 use zeroize::{Zeroize, ZeroizeOnDrop};
 
 /// The size of a DICE hash.
@@ -114,6 +116,69 @@
     }
 }
 
+impl TryFrom<iana::Algorithm> for KeyAlgorithm {
+    type Error = DiceError;
+
+    fn try_from(alg: iana::Algorithm) -> Result<Self> {
+        match alg {
+            iana::Algorithm::EdDSA => Ok(KeyAlgorithm::Ed25519),
+            iana::Algorithm::ES256 => Ok(KeyAlgorithm::EcdsaP256),
+            iana::Algorithm::ES384 => Ok(KeyAlgorithm::EcdsaP384),
+            other => Err(DiceError::UnsupportedKeyAlgorithm(other)),
+        }
+    }
+}
+
+#[cfg(feature = "multialg")]
+impl From<KeyAlgorithm> for DiceKeyAlgorithm {
+    fn from(alg: KeyAlgorithm) -> Self {
+        match alg {
+            KeyAlgorithm::Ed25519 => DiceKeyAlgorithm::kDiceKeyAlgorithmEd25519,
+            KeyAlgorithm::EcdsaP256 => DiceKeyAlgorithm::kDiceKeyAlgorithmP256,
+            KeyAlgorithm::EcdsaP384 => DiceKeyAlgorithm::kDiceKeyAlgorithmP384,
+        }
+    }
+}
+
+/// Represents the context used for DICE operations.
+#[cfg(feature = "multialg")]
+#[derive(Debug, Clone)]
+pub struct DiceContext {
+    /// The algorithm used for the authority key.
+    pub authority_algorithm: KeyAlgorithm,
+    /// The algorithm used for the subject key.
+    pub subject_algorithm: KeyAlgorithm,
+}
+
+#[cfg(feature = "multialg")]
+impl From<DiceContext> for DiceContext_ {
+    fn from(ctx: DiceContext) -> Self {
+        DiceContext_ {
+            authority_algorithm: ctx.authority_algorithm.into(),
+            subject_algorithm: ctx.subject_algorithm.into(),
+        }
+    }
+}
+
+#[cfg(feature = "multialg")]
+const VM_DICE_CONTEXT: DiceContext_ = DiceContext_ {
+    authority_algorithm: DiceKeyAlgorithm::kDiceKeyAlgorithmEd25519,
+    subject_algorithm: DiceKeyAlgorithm::kDiceKeyAlgorithmEd25519,
+};
+
+/// Returns the pointer points to |DiceContext_| for DICE operations when `multialg`
+/// feature is enabled.
+#[cfg(feature = "multialg")]
+pub(crate) fn context() -> *mut c_void {
+    &VM_DICE_CONTEXT as *const DiceContext_ as *mut c_void
+}
+
+/// Returns a null pointer when `multialg` feature is disabled.
+#[cfg(not(feature = "multialg"))]
+pub(crate) fn context() -> *mut c_void {
+    ptr::null_mut()
+}
+
 /// A trait for types that represent Dice artifacts, which include:
 ///
 /// - Attestation CDI
@@ -322,10 +387,9 @@
     check_result(
         // SAFETY: The function only reads the current CDI values and inputs and writes
         // to `next_cdi_certificate` and next CDI values within its bounds.
-        // The first argument can be null and is not used in the current implementation.
         unsafe {
             DiceMainFlow(
-                ptr::null_mut(), // context
+                context(),
                 current_cdi_attest.as_ptr(),
                 current_cdi_seal.as_ptr(),
                 input_values.as_ptr(),
diff --git a/libs/dice/open_dice/src/error.rs b/libs/dice/open_dice/src/error.rs
index bef9a9c..9089432 100644
--- a/libs/dice/open_dice/src/error.rs
+++ b/libs/dice/open_dice/src/error.rs
@@ -29,6 +29,8 @@
     BufferTooSmall(usize),
     /// Platform error.
     PlatformError,
+    /// Unsupported key algorithm.
+    UnsupportedKeyAlgorithm(coset::iana::Algorithm),
 }
 
 /// This makes `DiceError` accepted by anyhow.
@@ -43,6 +45,9 @@
                 write!(f, "Buffer too small; need {buffer_required_size} bytes")
             }
             Self::PlatformError => write!(f, "Platform error"),
+            Self::UnsupportedKeyAlgorithm(algorithm) => {
+                write!(f, "Unsupported key algorithm: {algorithm:?}")
+            }
         }
     }
 }
diff --git a/libs/dice/open_dice/src/lib.rs b/libs/dice/open_dice/src/lib.rs
index a347d46..4d05255 100644
--- a/libs/dice/open_dice/src/lib.rs
+++ b/libs/dice/open_dice/src/lib.rs
@@ -28,10 +28,13 @@
 mod ops;
 mod retry;
 
+#[cfg(feature = "multialg")]
+pub use bcc::bcc_handover_main_flow;
 pub use bcc::{
-    bcc_format_config_descriptor, bcc_handover_main_flow, bcc_handover_parse, bcc_main_flow,
-    BccHandover, DiceConfigValues,
+    bcc_format_config_descriptor, bcc_handover_parse, bcc_main_flow, BccHandover, DiceConfigValues,
 };
+#[cfg(feature = "multialg")]
+pub use dice::DiceContext;
 pub use dice::{
     derive_cdi_certificate_id, derive_cdi_private_key_seed, dice_main_flow, Cdi, CdiValues, Config,
     DiceArtifacts, DiceMode, Hash, Hidden, InlineConfig, InputValues, KeyAlgorithm, PrivateKey,
diff --git a/libs/dice/open_dice/src/ops.rs b/libs/dice/open_dice/src/ops.rs
index 137736f..41951bf 100644
--- a/libs/dice/open_dice/src/ops.rs
+++ b/libs/dice/open_dice/src/ops.rs
@@ -17,13 +17,14 @@
 //! main DICE functions depend on.
 
 use crate::dice::{
-    derive_cdi_private_key_seed, DiceArtifacts, Hash, InputValues, PrivateKey, HASH_SIZE,
+    context, derive_cdi_private_key_seed, DiceArtifacts, Hash, InputValues, PrivateKey, HASH_SIZE,
     PRIVATE_KEY_SEED_SIZE, PRIVATE_KEY_SIZE, VM_KEY_ALGORITHM,
 };
 use crate::error::{check_result, DiceError, Result};
 use alloc::{vec, vec::Vec};
 use open_dice_cbor_bindgen::{
-    DiceGenerateCertificate, DiceHash, DiceKdf, DiceKeypairFromSeed, DiceSign, DiceVerify,
+    DiceGenerateCertificate, DiceHash, DiceKdf, DiceKeypairFromSeed, DicePrincipal, DiceSign,
+    DiceVerify,
 };
 use std::ptr;
 
@@ -75,13 +76,20 @@
 pub fn keypair_from_seed(seed: &[u8; PRIVATE_KEY_SEED_SIZE]) -> Result<(Vec<u8>, PrivateKey)> {
     let mut public_key = vec![0u8; VM_KEY_ALGORITHM.public_key_size()];
     let mut private_key = PrivateKey::default();
+    // This function is used with an open-dice config that uses the same algorithms for the
+    // subject and authority. Therefore, the principal is irrelevant in this context as this
+    // function only derives the key pair cryptographically without caring about which
+    // principal it is for. Hence, we arbitrarily set it to `DicePrincipal::kDicePrincipalSubject`.
+    let principal = DicePrincipal::kDicePrincipalSubject;
     check_result(
         // SAFETY: The function writes to the `public_key` and `private_key` within the given
-        // bounds, and only reads the `seed`. The first argument context is not used in this
-        // function.
+        // bounds, and only reads the `seed`.
+        // The first argument is a pointer to a valid |DiceContext_| object for multi-alg open-dice
+        // and a null pointer otherwise.
         unsafe {
             DiceKeypairFromSeed(
-                ptr::null_mut(), // context
+                context(),
+                principal,
                 seed.as_ptr(),
                 public_key.as_mut_ptr(),
                 private_key.as_mut_ptr(),
@@ -111,11 +119,12 @@
     let mut signature = vec![0u8; VM_KEY_ALGORITHM.signature_size()];
     check_result(
         // SAFETY: The function writes to the `signature` within the given bounds, and only reads
-        // the message and the private key. The first argument context is not used in this
-        // function.
+        // the message and the private key.
+        // The first argument is a pointer to a valid |DiceContext_| object for multi-alg open-dice
+        // and a null pointer otherwise.
         unsafe {
             DiceSign(
-                ptr::null_mut(), // context
+                context(),
                 message.as_ptr(),
                 message.len(),
                 private_key.as_ptr(),
@@ -136,10 +145,11 @@
     }
     check_result(
         // SAFETY: only reads the messages, signature and public key as constant values.
-        // The first argument context is not used in this function.
+        // The first argument is a pointer to a valid |DiceContext_| object for multi-alg open-dice
+        // and a null pointer otherwise.
         unsafe {
             DiceVerify(
-                ptr::null_mut(), // context
+                context(),
                 message.as_ptr(),
                 message.len(),
                 signature.as_ptr(),
@@ -164,11 +174,12 @@
     let mut certificate_actual_size = 0;
     check_result(
         // SAFETY: The function writes to the `certificate` within the given bounds, and only reads
-        // the input values and the key seeds. The first argument context is not used in this
-        // function.
+        // the input values and the key seeds.
+        // The first argument is a pointer to a valid |DiceContext_| object for multi-alg open-dice
+        // and a null pointer otherwise.
         unsafe {
             DiceGenerateCertificate(
-                ptr::null_mut(), // context
+                context(),
                 subject_private_key_seed.as_ptr(),
                 authority_private_key_seed.as_ptr(),
                 input_values.as_ptr(),
diff --git a/libs/vm_launcher_lib/Android.bp b/libs/vm_launcher_lib/Android.bp
deleted file mode 100644
index 7dced4e..0000000
--- a/libs/vm_launcher_lib/Android.bp
+++ /dev/null
@@ -1,23 +0,0 @@
-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",
-    ],
-    platform_apis: true,
-    static_libs: [
-        "gson",
-        "debian-service-grpclib-lite",
-        "apache-commons-compress",
-    ],
-    libs: [
-        "androidx.annotation_annotation",
-        "framework-virtualization.impl",
-        "framework-annotations-lib",
-    ],
-}
diff --git a/tests/Terminal/src/com/android/virtualization/terminal/TerminalAppTest.java b/tests/Terminal/src/com/android/virtualization/terminal/TerminalAppTest.java
index fd07973..3c0461d 100644
--- a/tests/Terminal/src/com/android/virtualization/terminal/TerminalAppTest.java
+++ b/tests/Terminal/src/com/android/virtualization/terminal/TerminalAppTest.java
@@ -27,7 +27,6 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.microdroid.test.common.MetricsProcessor;
-import com.android.virtualization.vmlauncher.InstallUtils;
 
 import org.junit.After;
 import org.junit.Before;
diff --git a/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java b/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
index 03d7fef..fefedc9 100644
--- a/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
+++ b/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
@@ -93,6 +93,8 @@
     private static final String APK_NAME = "MicrodroidTestApp.apk";
     private static final String APK_UPDATED_NAME = "MicrodroidTestAppUpdated.apk";
     private static final String PACKAGE_NAME = "com.android.microdroid.test";
+    private static final String EMPTY_AOSP_PACKAGE_NAME = "com.android.microdroid.empty_payload";
+    private static final String EMPTY_PACKAGE_NAME = "com.google.android.microdroid.empty_payload";
     private static final String SHELL_PACKAGE_NAME = "com.android.shell";
     private static final String VIRT_APEX = "/apex/com.android.virt/";
     private static final String INSTANCE_IMG = TEST_ROOT + "instance.img";
@@ -1130,6 +1132,70 @@
         assertThat(ret).contains("Payload binary name must not specify a path");
     }
 
+    private boolean hasAppPackage(String pkgName, CommandRunner android) throws DeviceNotAvailableException {
+        String hasPackage =
+        android.run(
+                "pm list package | grep -w " + pkgName + " 1> /dev/null" + "; echo $?");
+        if (hasPackage.equals("0")) {
+            return true;
+        }
+
+        return false;
+    }
+
+    @Test
+    public void testRunEmptyPayload() throws Exception {
+        CommandRunner android = new CommandRunner(getDevice());
+
+        // Create the idsig file for the APK
+        String apkPath;
+        if (hasAppPackage(EMPTY_AOSP_PACKAGE_NAME, android))
+            apkPath = getPathForPackage(EMPTY_AOSP_PACKAGE_NAME);
+        else
+            apkPath = getPathForPackage(EMPTY_PACKAGE_NAME);
+
+        final String idSigPath = TEST_ROOT + "idsig";
+        final String instanceImgPath = TEST_ROOT + "instance.img";
+
+        android.run(VIRT_APEX + "bin/vm", "create-idsig", apkPath, idSigPath);
+
+        List<String> cmd =
+                new ArrayList<>(
+                        Arrays.asList(
+                                "adb",
+                                "-s",
+                                getDevice().getSerialNumber(),
+                                "shell",
+                                VIRT_APEX + "bin/vm",
+                                "run-app",
+                                "--debug full",
+                                "--console " + CONSOLE_PATH,
+                                "--payload-binary-name",
+                                "MicrodroidEmptyPayloadJniLib.so",
+                                apkPath,
+                                idSigPath,
+                                instanceImgPath));
+        if (isFeatureEnabled("com.android.kvm.LLPVM_CHANGES")) {
+            cmd.add("--instance-id-file");
+            cmd.add(TEST_ROOT + "instance_id");
+        }
+
+        PipedInputStream pis = new PipedInputStream();
+        Process process = createRunUtil().runCmdInBackground(cmd, new PipedOutputStream(pis));
+        String bufferedInput = "";
+
+        do {
+            byte[] pipeBuffer = new byte[4096];
+            pis.read(pipeBuffer, 0, 4096);
+            bufferedInput += new String(pipeBuffer);
+        } while (!bufferedInput.contains("payload is ready"));
+
+        String consoleLog = getDevice().pullFileContents(CONSOLE_PATH);
+        assertThat(consoleLog).contains("Hello Microdroid");
+
+        process.destroy();
+    }
+
     @Test
     @CddTest(requirements = {"9.17/C-2-2", "9.17/C-2-6"})
     public void testAllVbmetaUseSHA256() throws Exception {