Merge "Add x86_64 image to kokoro build" into main
diff --git a/OWNERS b/OWNERS
index afd2555..81217f3 100644
--- a/OWNERS
+++ b/OWNERS
@@ -30,8 +30,6 @@
 victorhsieh@google.com
 
 # Ferrochrome
-per-file android/FerrochromeApp/**=jiyong@google.com,jeongik@google.com
-per-file android/LinuxInstaller/**=jiyong@google.com,jeongik@google.com
 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
diff --git a/android/LinuxInstaller/Android.bp b/android/LinuxInstaller/Android.bp
deleted file mode 100644
index f7994ef..0000000
--- a/android/LinuxInstaller/Android.bp
+++ /dev/null
@@ -1,62 +0,0 @@
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-java_defaults {
-    name: "LinuxVmPayloadInstaller",
-    init_rc: [":linux_vm_setup.rc"],
-    required: ["linux_vm_setup"],
-    system_ext_specific: true,
-    platform_apis: true,
-    privileged: true,
-}
-
-android_app {
-    name: "LinuxInstallerApp",
-    srcs: ["java/**/*.java"],
-    resource_dirs: ["res"],
-    asset_dirs: ["assets"],
-    manifest: "AndroidManifest.xml",
-    defaults: ["LinuxVmPayloadInstaller"],
-    overrides: ["LinuxInstallerAppStub"],
-    required: [
-        "privapp-permissions-linuxinstaller.xml",
-    ],
-    certificate: ":com.android.virtualization.linuxinstaller_certificate",
-}
-
-android_app {
-    name: "LinuxInstallerAppStub",
-    srcs: ["java/**/*.java"],
-    resource_dirs: ["res"],
-    manifest: "AndroidManifest_stub.xml",
-    defaults: ["LinuxVmPayloadInstaller"],
-    required: [
-        "privapp-permissions-linuxinstaller.xml",
-    ],
-    certificate: ":com.android.virtualization.linuxinstaller_certificate",
-}
-
-prebuilt_etc {
-    name: "privapp-permissions-linuxinstaller.xml",
-    src: "privapp-permissions-linuxinstaller.xml",
-    sub_dir: "permissions",
-    system_ext_specific: true,
-}
-
-android_app_certificate {
-    name: "com.android.virtualization.linuxinstaller_certificate",
-    certificate: "com_android_virtualization_linuxinstaller",
-}
-
-filegroup {
-    name: "linux_vm_setup.rc",
-    srcs: ["linux_vm_setup.rc"],
-}
-
-sh_binary {
-    name: "linux_vm_setup",
-    src: "linux_vm_setup.sh",
-    system_ext_specific: true,
-    host_supported: false,
-}
diff --git a/android/LinuxInstaller/AndroidManifest.xml b/android/LinuxInstaller/AndroidManifest.xml
deleted file mode 100644
index e5653f6..0000000
--- a/android/LinuxInstaller/AndroidManifest.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.virtualization.linuxinstaller"
-    android:versionCode="2100000000" >
-    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
-    <uses-permission android:name="android.permission.CHANGE_COMPONENT_ENABLED_STATE" />
-    <queries>
-        <intent>
-            <action android:name="android.virtualization.VM_TERMINAL" />
-        </intent>
-    </queries>
-    <application
-        android:label="LinuxInstaller">
-        <activity android:name=".MainActivity"
-                  android:exported="true">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
-            </intent-filter>
-        </activity>
-    </application>
-
-</manifest>
diff --git a/android/LinuxInstaller/AndroidManifest_stub.xml b/android/LinuxInstaller/AndroidManifest_stub.xml
deleted file mode 100644
index 49365ea..0000000
--- a/android/LinuxInstaller/AndroidManifest_stub.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.virtualization.linuxinstaller" >
-    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
-    <uses-permission android:name="android.permission.CHANGE_COMPONENT_ENABLED_STATE" />
-    <queries>
-        <intent>
-            <action android:name="android.virtualization.VM_TERMINAL" />
-        </intent>
-    </queries>
-    <application
-        android:label="LinuxInstaller">
-        <activity android:name=".MainActivity"
-                  android:exported="true">
-        </activity>
-    </application>
-
-</manifest>
diff --git a/android/LinuxInstaller/com_android_virtualization_linuxinstaller.pk8 b/android/LinuxInstaller/com_android_virtualization_linuxinstaller.pk8
deleted file mode 100644
index 3f74303..0000000
--- a/android/LinuxInstaller/com_android_virtualization_linuxinstaller.pk8
+++ /dev/null
Binary files differ
diff --git a/android/LinuxInstaller/com_android_virtualization_linuxinstaller.x509.pem b/android/LinuxInstaller/com_android_virtualization_linuxinstaller.x509.pem
deleted file mode 100644
index 3ca64b7..0000000
--- a/android/LinuxInstaller/com_android_virtualization_linuxinstaller.x509.pem
+++ /dev/null
@@ -1,24 +0,0 @@
------BEGIN CERTIFICATE-----
-MIIEETCCAvmgAwIBAgIUfBxyELS+ri3QErq8DXHu+47xx4EwDQYJKoZIhvcNAQEL
-BQAwgZYxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQH
-DA1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKDAtHb29nbGUgSW5jLjEQMA4GA1UECwwH
-QW5kcm9pZDEyMDAGA1UEAwwpY29tX2FuZHJvaWRfdmlydHVhbGl6YXRpb25fbGlu
-dXhpbnN0YWxsZXIwIBcNMjQwODMwMTIyNjU2WhgPMjA1MjAxMTYxMjI2NTZaMIGW
-MQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91
-bnRhaW4gVmlldzEUMBIGA1UECgwLR29vZ2xlIEluYy4xEDAOBgNVBAsMB0FuZHJv
-aWQxMjAwBgNVBAMMKWNvbV9hbmRyb2lkX3ZpcnR1YWxpemF0aW9uX2xpbnV4aW5z
-dGFsbGVyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA8V/rH9ju6Wce
-1BdWuxfWaLmZJHGShXeDO6MB86Wrm10m26j9PFzd8/8FRKsZaujZphwNZsqBsdlt
-pWeNKts9T9luZn19Ci4E8A2EtgSxmfI8Fjwj/OJHHO0hG5+JcwIlUnmFQPcGtu/r
-EL3i7SfcF2ok+IC6aKYohnSbo+YkjyCSwb39i6POe6v6cPIZJtmOnecThS+fYCYR
-2yoMSSr3Bf8ayySrG0pJp7xZ1I5NixK6hUFZhQRLusyiv/KYTpAElMd+n1YJEYbf
-pW30DYAu+31S0hx8JXncFmI0uG3Zxx+LgNQwY8OPV6NPFfVwMPluZR6ep0tZ6q7e
-KIV2w5uC7QIDAQABo1MwUTAdBgNVHQ4EFgQU6FBYv7mW+9DR9q0c9uS4NNdX4Acw
-HwYDVR0jBBgwFoAU6FBYv7mW+9DR9q0c9uS4NNdX4AcwDwYDVR0TAQH/BAUwAwEB
-/zANBgkqhkiG9w0BAQsFAAOCAQEAj3bvUpwKjvpCggXzjMNkn7fAaQ0s1BubnkFe
-ge4zwz4tObP3OGRcxt5V9R5EZ7UY6bPcybA/rfg9FCzjcUQOBjmuepcQpbNHFW2I
-lasFa42UHkHSUFzeg2n9UC5iO3B+sclOr4EPaEE4HbG4B2vj++BYMW3C7PDyHc7R
-fq5ZsEEWcYUa8qZCO46I8AbMZ8iv1HpR4mZeQMkSxhD3uVHDQW+VqDTpzne/YBkJ
-yNfjpgFVZ/Y1E6BvvjzWZpBfj668fo7P3DekWHbvPPr/DiZ7OA6PCmAH1FBsi2c+
-xPgb9clDc2Zjb2Cd9lAoZdeB14zDOh6ZCF1c/i+qYt5tA9t+GA==
------END CERTIFICATE-----
diff --git a/android/LinuxInstaller/privapp-permissions-linuxinstaller.xml b/android/LinuxInstaller/privapp-permissions-linuxinstaller.xml
deleted file mode 100644
index e46ec97..0000000
--- a/android/LinuxInstaller/privapp-permissions-linuxinstaller.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<permissions>
-    <privapp-permissions package="com.android.virtualization.linuxinstaller">
-        <permission name="android.permission.CHANGE_COMPONENT_ENABLED_STATE"/>
-    </privapp-permissions>
-</permissions>
\ No newline at end of file
diff --git a/android/LinuxInstaller/.gitignore b/android/TerminalApp/.gitignore
similarity index 100%
rename from android/LinuxInstaller/.gitignore
rename to android/TerminalApp/.gitignore
diff --git a/android/TerminalApp/Android.bp b/android/TerminalApp/Android.bp
index d91af2f..e5e8b0a 100644
--- a/android/TerminalApp/Android.bp
+++ b/android/TerminalApp/Android.bp
@@ -9,6 +9,7 @@
         "java/**/*.kt",
     ],
     resource_dirs: ["res"],
+    asset_dirs: ["assets"],
     static_libs: [
         "vm_launcher_lib",
         "androidx-constraintlayout_constraintlayout",
@@ -25,3 +26,15 @@
         "com.android.virt",
     ],
 }
+
+filegroup {
+    name: "linux_vm_setup.rc",
+    srcs: ["linux_vm_setup.rc"],
+}
+
+sh_binary {
+    name: "linux_vm_setup",
+    src: "linux_vm_setup.sh",
+    init_rc: [":linux_vm_setup.rc"],
+    host_supported: false,
+}
diff --git a/android/TerminalApp/AndroidManifest.xml b/android/TerminalApp/AndroidManifest.xml
index f09412e..105e454 100644
--- a/android/TerminalApp/AndroidManifest.xml
+++ b/android/TerminalApp/AndroidManifest.xml
@@ -3,6 +3,7 @@
     xmlns:tools="http://schemas.android.com/tools"
     package="com.android.virtualization.terminal">
 
+    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
     <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" />
@@ -12,15 +13,18 @@
     <uses-feature android:name="android.software.virtualization_framework" android:required="true" />
 
     <application
-	android:label="@string/app_name"
+        android:label="@string/app_name"
         android:icon="@mipmap/ic_launcher"
         android:theme="@style/Theme.Material3.DayNight.NoActionBar"
-        android:usesCleartextTraffic="true">
+        android:usesCleartextTraffic="true"
+        android:enabled="false">
         <activity android:name=".MainActivity"
                   android:configChanges="orientation|screenSize|keyboard|keyboardHidden|navigation|uiMode|screenLayout|smallestScreenSize"
                   android:exported="true">
             <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
                 <action android:name="android.virtualization.VM_TERMINAL" />
+                <category android:name="android.intent.category.LAUNCHER" />
                 <category android:name="android.intent.category.DEFAULT" />
             </intent-filter>
         </activity>
@@ -44,20 +48,15 @@
                 android:name="${applicationId}.SplitInitializer"
                 android:value="androidx.startup" />
         </provider>
-        <activity-alias
-            android:name=".MainActivityAlias"
-            android:targetActivity="com.android.virtualization.terminal.MainActivity"
-            android:exported="true"
-            android:enabled="false" >
+        <activity android:name=".InstallerActivity"
+            android:exported="false">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
-        </activity-alias>
+        </activity>
 
         <service
             android:name="com.android.virtualization.vmlauncher.VmLauncherService"
-            android:enabled="true"
             android:exported="false"
             android:foregroundServiceType="specialUse">
             <property
diff --git a/android/LinuxInstaller/assets/.gitkeep b/android/TerminalApp/assets/.gitkeep
similarity index 100%
rename from android/LinuxInstaller/assets/.gitkeep
rename to android/TerminalApp/assets/.gitkeep
diff --git a/android/TerminalApp/assets/client.p12 b/android/TerminalApp/assets/client.p12
new file mode 100644
index 0000000..f1f5820
--- /dev/null
+++ b/android/TerminalApp/assets/client.p12
Binary files differ
diff --git a/android/LinuxInstaller/generate_assets.sh b/android/TerminalApp/generate_assets.sh
similarity index 86%
rename from android/LinuxInstaller/generate_assets.sh
rename to android/TerminalApp/generate_assets.sh
index 8e70cb0..ff7444e 100755
--- a/android/LinuxInstaller/generate_assets.sh
+++ b/android/TerminalApp/generate_assets.sh
@@ -3,6 +3,7 @@
 
 if [ "$#" -ne 1 ]; then
     echo "$0 <image.raw path>"
+    echo "image.raw can be built with packages/modules/Virtualization/build/debian/build.sh"
     exit 1
 fi
 pushd $(dirname $0) > /dev/null
diff --git a/android/LinuxInstaller/java/com/android/virtualization/linuxinstaller/MainActivity.java b/android/TerminalApp/java/com/android/virtualization/terminal/InstallerActivity.java
similarity index 76%
rename from android/LinuxInstaller/java/com/android/virtualization/linuxinstaller/MainActivity.java
rename to android/TerminalApp/java/com/android/virtualization/terminal/InstallerActivity.java
index 0351f97..1c739e2 100644
--- a/android/LinuxInstaller/java/com/android/virtualization/linuxinstaller/MainActivity.java
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/InstallerActivity.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2024 The Android Open Source Project
+ * Copyright 2024 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,15 +14,10 @@
  * limitations under the License.
  */
 
-package com.android.virtualization.linuxinstaller;
+package com.android.virtualization.terminal;
 
 import android.annotation.WorkerThread;
 import android.app.Activity;
-import android.content.ComponentName;
-import android.content.Intent;
-import android.content.pm.ActivityInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
 import android.os.Bundle;
 import android.os.Environment;
 import android.os.SystemProperties;
@@ -39,13 +34,11 @@
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.StandardCopyOption;
-import java.util.List;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 
-public class MainActivity extends Activity {
+public class InstallerActivity extends Activity {
     private static final String TAG = "LinuxInstaller";
-    private static final String ACTION_VM_TERMINAL = "android.virtualization.VM_TERMINAL";
 
     private static final Path DEST_DIR =
             Path.of(Environment.getExternalStorageDirectory().getPath(), "linux");
@@ -59,20 +52,17 @@
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
-        setContentView(R.layout.activity_main);
+        setResult(RESULT_CANCELED);
+
+        setContentView(R.layout.activity_installer);
 
         executorService.execute(this::installLinuxImage);
     }
 
     private void installLinuxImage() {
-        ComponentName vmTerminalComponent = resolve(getPackageManager(), ACTION_VM_TERMINAL);
-        if (vmTerminalComponent == null) {
-            updateStatus("Failed to resolve VM terminal");
-            return;
-        }
-
         if (!hasLocalAssets()) {
             updateStatus("No local assets");
+            setResult(RESULT_CANCELED, null);
             return;
         }
         try {
@@ -81,13 +71,9 @@
             Log.e(TAG, "failed to update image", e);
             return;
         }
-        updateStatus("Enabling terminal app...");
-        getPackageManager()
-                .setComponentEnabledSetting(
-                        vmTerminalComponent,
-                        PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
-                        PackageManager.DONT_KILL_APP);
         updateStatus("Done.");
+        setResult(RESULT_OK);
+        finish();
     }
 
     @WorkerThread
@@ -190,18 +176,4 @@
                     statusView.append(line + "\n");
                 });
     }
-
-    private ComponentName resolve(PackageManager pm, String action) {
-        Intent intent = new Intent(action);
-        List<ResolveInfo> resolveInfos = pm.queryIntentActivities(intent, PackageManager.MATCH_ALL);
-        if (resolveInfos.size() != 1) {
-            Log.w(
-                    TAG,
-                    "Failed to resolve activity, action=" + action + ", resolved=" + resolveInfos);
-            return null;
-        }
-        ActivityInfo activityInfo = resolveInfos.getFirst().activityInfo;
-        // MainActivityAlias shows in Launcher
-        return new ComponentName(activityInfo.packageName, activityInfo.name + "Alias");
-    }
 }
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.java b/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.java
index 1fabf8d..bd35f51 100644
--- a/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.java
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.java
@@ -19,6 +19,7 @@
 import android.content.ClipboardManager;
 import android.content.Context;
 import android.content.Intent;
+import android.net.http.SslError;
 import android.os.Bundle;
 import android.system.ErrnoException;
 import android.system.Os;
@@ -27,6 +28,8 @@
 import android.view.MenuItem;
 import android.view.View;
 import android.view.accessibility.AccessibilityManager;
+import android.webkit.ClientCertRequest;
+import android.webkit.SslErrorHandler;
 import android.webkit.WebChromeClient;
 import android.webkit.WebResourceError;
 import android.webkit.WebResourceRequest;
@@ -44,11 +47,17 @@
 import java.io.FileDescriptor;
 import java.io.FileNotFoundException;
 import java.io.IOException;
+import java.io.InputStream;
 import java.io.RandomAccessFile;
 import java.net.InetAddress;
 import java.net.MalformedURLException;
 import java.net.URL;
 import java.net.UnknownHostException;
+import java.security.Key;
+import java.security.KeyStore;
+import java.security.PrivateKey;
+import java.security.cert.Certificate;
+import java.security.cert.X509Certificate;
 
 public class MainActivity extends AppCompatActivity
         implements VmLauncherServices.VmLauncherServiceCallback,
@@ -57,6 +66,10 @@
     private static final String TAG = "VmTerminalApp";
     private static final String VM_ADDR = "192.168.0.2";
     private static final int TTYD_PORT = 7681;
+    private static final int REQUEST_CODE_INSTALLER = 0x33;
+
+    private X509Certificate[] mCertificates;
+    private PrivateKey mPrivateKey;
     private WebView mWebView;
     private AccessibilityManager mAccessibilityManager;
 
@@ -64,6 +77,7 @@
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
 
+        checkForUpdate();
         try {
             // No resize for now.
             long newSizeInBytes = 0;
@@ -74,10 +88,6 @@
                     .show();
         }
 
-        Toast.makeText(this, R.string.vm_creation_message, Toast.LENGTH_SHORT).show();
-        android.os.Trace.beginAsyncSection("executeTerminal", 0);
-        VmLauncherServices.startVmLauncherService(this, this);
-
         setContentView(R.layout.activity_headless);
 
         MaterialToolbar toolbar = (MaterialToolbar) findViewById(R.id.toolbar);
@@ -92,6 +102,7 @@
         mAccessibilityManager.addTouchExplorationStateChangeListener(this);
 
         connectToTerminalService();
+        readClientCertificate();
     }
 
     private URL getTerminalServiceUrl() {
@@ -100,13 +111,35 @@
         String query = needsAccessibility ? "?screenReaderMode=true" : "";
 
         try {
-            return new URL("http", VM_ADDR, TTYD_PORT, file + query);
+            return new URL("https", VM_ADDR, TTYD_PORT, file + query);
         } catch (MalformedURLException e) {
             // this cannot happen
             return null;
         }
     }
 
+    private void readClientCertificate() {
+        // TODO(b/363235314): instead of using the key in asset, it should be generated in runtime
+        // and then provisioned in the vm via virtio-fs
+        try (InputStream keystoreFileStream =
+                getClass().getResourceAsStream("/assets/client.p12")) {
+            KeyStore keyStore = KeyStore.getInstance("PKCS12");
+            String password = "1234";
+            String alias = "1";
+
+            keyStore.load(keystoreFileStream, password != null ? password.toCharArray() : null);
+            Key key = keyStore.getKey(alias, password.toCharArray());
+            if (key instanceof PrivateKey) {
+                mPrivateKey = (PrivateKey) key;
+                Certificate cert = keyStore.getCertificate(alias);
+                mCertificates = new X509Certificate[1];
+                mCertificates[0] = (X509Certificate) cert;
+            }
+        } catch (Exception e) {
+            Log.e(TAG, e.getMessage());
+        }
+    }
+
     private void connectToTerminalService() {
         Log.i(TAG, "URL=" + getTerminalServiceUrl().toString());
         mWebView.setWebViewClient(
@@ -146,6 +179,23 @@
                             view.setVisibility(View.VISIBLE);
                         }
                     }
+
+                    @Override
+                    public void onReceivedClientCertRequest(
+                            WebView view, ClientCertRequest request) {
+                        if (mPrivateKey != null && mCertificates != null) {
+                            request.proceed(mPrivateKey, mCertificates);
+                            return;
+                        }
+                        super.onReceivedClientCertRequest(view, request);
+                    }
+
+                    @Override
+                    public void onReceivedSslError(
+                            WebView view, SslErrorHandler handler, SslError error) {
+                        // ttyd uses self-signed certificate
+                        handler.proceed();
+                    }
                 });
         new Thread(
                         () -> {
@@ -318,4 +368,28 @@
     public void onTouchExplorationStateChanged(boolean enabled) {
         connectToTerminalService();
     }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        super.onActivityResult(requestCode, resultCode, data);
+
+        if (requestCode == REQUEST_CODE_INSTALLER) {
+            if (resultCode != RESULT_OK) {
+                Log.e(TAG, "Failed to start VM. Installer returned error.");
+                finish();
+            }
+            startVm();
+        }
+    }
+
+    private void checkForUpdate() {
+        Intent intent = new Intent(this, InstallerActivity.class);
+        startActivityForResult(intent, REQUEST_CODE_INSTALLER);
+    }
+
+    private void startVm() {
+        Toast.makeText(this, R.string.vm_creation_message, Toast.LENGTH_SHORT).show();
+        android.os.Trace.beginAsyncSection("executeTerminal", 0);
+        VmLauncherServices.startVmLauncherService(this, this);
+    }
 }
diff --git a/android/LinuxInstaller/linux_vm_setup.rc b/android/TerminalApp/linux_vm_setup.rc
similarity index 92%
rename from android/LinuxInstaller/linux_vm_setup.rc
rename to android/TerminalApp/linux_vm_setup.rc
index 9264d96..ac91532 100644
--- a/android/LinuxInstaller/linux_vm_setup.rc
+++ b/android/TerminalApp/linux_vm_setup.rc
@@ -12,7 +12,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-service linux_vm_setup /system_ext/bin/linux_vm_setup
+service linux_vm_setup /system/bin/linux_vm_setup
     user shell
     group shell media_rw
     disabled
diff --git a/android/LinuxInstaller/linux_vm_setup.sh b/android/TerminalApp/linux_vm_setup.sh
similarity index 100%
rename from android/LinuxInstaller/linux_vm_setup.sh
rename to android/TerminalApp/linux_vm_setup.sh
diff --git a/android/LinuxInstaller/res/layout/activity_main.xml b/android/TerminalApp/res/layout/activity_installer.xml
similarity index 100%
rename from android/LinuxInstaller/res/layout/activity_main.xml
rename to android/TerminalApp/res/layout/activity_installer.xml
diff --git a/android/TerminalApp/res/values/strings.xml b/android/TerminalApp/res/values/strings.xml
index 821a2ef..6ae5b7b 100644
--- a/android/TerminalApp/res/values/strings.xml
+++ b/android/TerminalApp/res/values/strings.xml
@@ -16,25 +16,45 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name">Terminal</string>
-    <string name="vm_creation_message">Preparing terminal.</string>
-    <string name="vm_stop_message">Stopping terminal.</string>
-    <string name="vm_error_message">Terminal crashed.</string>
 
+    <!-- Application name of this terminal app shown in the launcher. This app provides computer terminal to connect to virtual machine. [CHAR LIMIT=16] -->
+    <string name="app_name">Terminal</string>
+
+    <!-- Toast message to notify that preparing terminal to start [CHAR LIMIT=none] -->
+    <string name="vm_creation_message">Preparing terminal</string>
+    <!-- Toast message to notify that terminal is stopping [CHAR LIMIT=none] -->
+    <string name="vm_stop_message">Stopping terminal</string>
+    <!-- Toast message to notify that terminal is crashed [CHAR LIMIT=none] -->
+    <string name="vm_error_message">Terminal crashed</string>
+
+    <!-- Settings memu title for resizing disk of the virtual machine. [CHAR LIMIT=none] -->
     <string name="settings_disk_resize_title">Disk Resize</string>
+    <!-- Settings memu subtitle for resizing disk of the virtual machine. [CHAR LIMIT=none] -->
     <string name="settings_disk_resize_sub_title">Resize / Rootfs</string>
+    <!-- Toast message after new disk size is set. [CHAR LIMIT=none] -->
     <string name="settings_disk_resize_resize_message">Disk size set</string>
+    <!-- Settings menu option description for the current disk size, followed by a text box with the actual number [CHAR LIMIT=none] -->
     <string name="settings_disk_resize_resize_gb_assigned">GB Assigned</string>
+    <!-- Settings menu option description for the maximum resizable disk size. [CHAR LIMIT=none] -->
     <string name="settings_disk_resize_resize_gb_total">256 GB total</string>
+    <!-- Settings menu button to cancel disk resize. [CHAR LIMIT=32] -->
     <string name="settings_disk_resize_resize_cancel">Cancel</string>
+    <!-- Settings menu button to apply change that requires to restart VM (abbrev of virtual machine). [CHAR LIMIT=64] -->
     <string name="settings_disk_resize_resize_restart_vm_to_apply">Restart VM to apply</string>
 
+    <!-- Settings menu title for 'port forwarding' [CHAR LIMIT=none] -->
     <string name="settings_port_forwarding_title">Port Forwarding</string>
+    <!-- Settings menu subtitle for 'port forwarding' [CHAR LIMIT=none] -->
     <string name="settings_port_forwarding_sub_title">Configure port forwarding</string>
 
+    <!-- Settings menu title for recoverying image [CHAR LIMIT=none] -->
     <string name="settings_recovery_title">Recovery</string>
+    <!-- Settings menu subtitle for recoverying image [CHAR LIMIT=none] -->
     <string name="settings_recovery_sub_title">Partition Recovery options</string>
+    <!-- Settings menu title for resetting the virtual machine image [CHAR LIMIT=none] -->
     <string name="settings_recovery_reset_title">Change to Initial version</string>
+    <!-- Settings menu subtitle for resetting the virtual machine image [CHAR LIMIT=none] -->
     <string name="settings_recovery_reset_sub_title">Remove all</string>
+    <!-- Toast message for reset is completed [CHAR LIMIT=none] -->
     <string name="settings_recovery_reset_message">VM reset</string>
 </resources>
diff --git a/android/LinuxInstaller/vm_config.json b/android/TerminalApp/vm_config.json
similarity index 100%
rename from android/LinuxInstaller/vm_config.json
rename to android/TerminalApp/vm_config.json
diff --git a/build/apex/Android.bp b/build/apex/Android.bp
index f794239..4b69660 100644
--- a/build/apex/Android.bp
+++ b/build/apex/Android.bp
@@ -175,6 +175,10 @@
         "true": ["virtualizationservice.xml"],
         default: unset,
     }),
+    required: select(release_flag("RELEASE_AVF_SUPPORT_CUSTOM_VM_WITH_PARAVIRTUALIZED_DEVICES"), {
+        true: ["linux_vm_setup"],
+        default: [],
+    }),
 }
 
 apex_defaults {
diff --git a/build/apex/product_packages.mk b/build/apex/product_packages.mk
index c678693..0646e67 100644
--- a/build/apex/product_packages.mk
+++ b/build/apex/product_packages.mk
@@ -73,7 +73,3 @@
     $(error RELEASE_AVF_ENABLE_EARLY_VM can only be enabled in trunk_staging until b/357025924 is fixed)
   endif
 endif
-
-ifdef RELEASE_AVF_SUPPORT_CUSTOM_VM_WITH_PARAVIRTUALIZED_DEVICES
-  PRODUCT_PACKAGES += LinuxInstallerAppStub
-endif
diff --git a/build/debian/build.sh b/build/debian/build.sh
index 97d9373..71a9d3b 100755
--- a/build/debian/build.sh
+++ b/build/debian/build.sh
@@ -103,6 +103,17 @@
 	wget -O - "${url}" | tar xz -C "${outdir}" --strip-components=1
 }
 
+build_rust_binary_and_copy() {
+	pushd "$(dirname "$0")/../../guest/$1" > /dev/null
+	RUSTFLAGS="-C linker=${arch}-linux-gnu-gcc" cargo build \
+		--target "${arch}-unknown-linux-gnu" \
+		--target-dir "${workdir}/$1"
+	mkdir -p "${dst}/files/usr/local/bin/$1"
+	cp "${workdir}/$1/${arch}-unknown-linux-gnu/debug/$1" "${dst}/files/usr/local/bin/$1/AVF"
+	chmod 777 "${dst}/files/usr/local/bin/$1/AVF"
+	popd > /dev/null
+}
+
 copy_android_config() {
 	local src="$(dirname "$0")/fai_config"
 	local dst="${config_space}"
@@ -116,23 +127,9 @@
 	wget "${url}" -O "${dst}/files/usr/local/bin/ttyd/AVF"
 	chmod 777 "${dst}/files/usr/local/bin/ttyd/AVF"
 
-	pushd "$(dirname "$0")/../../guest/forwarder_guest" > /dev/null
-	RUSTFLAGS="-C linker=${arch}-linux-gnu-gcc" cargo build \
-		--target "${arch}-unknown-linux-gnu" \
-		--target-dir "${workdir}/forwarder_guest"
-	mkdir -p "${dst}/files/usr/local/bin/forwarder_guest"
-	cp "${workdir}/forwarder_guest/${arch}-unknown-linux-gnu/debug/forwarder_guest" "${dst}/files/usr/local/bin/forwarder_guest/AVF"
-	chmod 777 "${dst}/files/usr/local/bin/forwarder_guest/AVF"
-	popd > /dev/null
-
-	pushd $(dirname $0)/../../guest/ip_addr_reporter > /dev/null
-	RUSTFLAGS="-C linker=aarch64-linux-gnu-gcc" cargo build \
-		--target aarch64-unknown-linux-gnu \
-		--target-dir ${workdir}/ip_addr_reporter
-	mkdir -p ${dst}/files/usr/local/bin/ip_addr_reporter
-	cp ${workdir}/ip_addr_reporter/aarch64-unknown-linux-gnu/debug/ip_addr_reporter ${dst}/files/usr/local/bin/ip_addr_reporter/AVF
-	chmod 777 ${dst}/files/usr/local/bin/ip_addr_reporter/AVF
-	popd > /dev/null
+	build_rust_binary_and_copy forwarder_guest
+	build_rust_binary_and_copy forwarder_guest_launcher
+	build_rust_binary_and_copy ip_addr_reporter
 }
 
 run_fai() {
diff --git a/build/debian/fai_config/files/etc/systemd/system/ttyd.service/AVF b/build/debian/fai_config/files/etc/systemd/system/ttyd.service/AVF
index 6b932e9..0aab770 100644
--- a/build/debian/fai_config/files/etc/systemd/system/ttyd.service/AVF
+++ b/build/debian/fai_config/files/etc/systemd/system/ttyd.service/AVF
@@ -3,7 +3,7 @@
 After=syslog.target
 After=network.target
 [Service]
-ExecStart=/usr/local/bin/ttyd -W screen -aAxR -S main login -f droid
+ExecStart=/usr/local/bin/ttyd --ssl --ssl-cert /etc/ttyd/server.crt --ssl-key /etc/ttyd/server.key --ssl-ca /etc/ttyd/ca.crt -W screen -aAxR -S main login -f droid
 Type=simple
 Restart=always
 User=root
diff --git a/build/debian/fai_config/files/etc/ttyd/ca.crt/AVF b/build/debian/fai_config/files/etc/ttyd/ca.crt/AVF
new file mode 100644
index 0000000..90d8c0e
--- /dev/null
+++ b/build/debian/fai_config/files/etc/ttyd/ca.crt/AVF
@@ -0,0 +1,21 @@
+-----BEGIN CERTIFICATE-----
+MIIDhzCCAm+gAwIBAgIUQkvURjf6sU5aJ7oK9usHnJHsc/owDQYJKoZIhvcNAQEL
+BQAwUzELMAkGA1UEBhMCQ04xCzAJBgNVBAgMAkdEMQswCQYDVQQHDAJTWjETMBEG
+A1UECgwKQWNtZSwgSW5jLjEVMBMGA1UEAwwMQWNtZSBSb290IENBMB4XDTI0MTAx
+NDAxMjgzN1oXDTI1MTAxNDAxMjgzN1owUzELMAkGA1UEBhMCQ04xCzAJBgNVBAgM
+AkdEMQswCQYDVQQHDAJTWjETMBEGA1UECgwKQWNtZSwgSW5jLjEVMBMGA1UEAwwM
+QWNtZSBSb290IENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtjgS
+ePtWI6xARLzM1bUMvqtWwY4ci4TzcOcfLfV5Eqbb135NSBKQ+Q2IAguc2Bl3ZVRE
+08GhQ9XJOo+mp2SUY/8+SJpCVhVlWvF6LwXd8X5pZ9GCem0FXY02kMr5ZiTs/CN2
+LZIyJKgXCT/5208on+BbiNp0pk2Pz1nDOdpxvkDJ8UKRWLwqCAEM/rcN1Lc00aln
+N/Rfi/CQE+MDAmhuy/nxr37ldqhkN+xM4bhNs1bjyVposKtbmFUY/SD3ca5CMawU
+E3l5hZ5kfua7lelEPVhvNYJcxffVO0fPNEbUKr1WsPLrnidqegcU8bml1BoCphgA
+qzoxD0rZniqMsom/vwIDAQABo1MwUTAdBgNVHQ4EFgQUZOHF7/arn8ODqEj1Wifk
+dEA5TFkwHwYDVR0jBBgwFoAUZOHF7/arn8ODqEj1WifkdEA5TFkwDwYDVR0TAQH/
+BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAVets3IybnIycAtajxpJygdji/95t
+ikdyWbi8lrszC0E5bCR9XPQKnqx/svKYrEVQNihH/nZ6TlTv0f3b77+92sVlmQfl
+a3KKI6qIgcqNEO2lHYsS+cPeBmaM6WXcEPe6gEnan1i5N16B9g9ntY4lOg8Z4roR
+2lVVCCNwabyBxb5oQDsN1IDeJ7JRRZqGGduDSZTvdd36GqNhMvXQjluyJCCFd1Hv
+IwwJmAR2GMUQU8Eoa+zGzW1Inf1YJytTu8SeQ+hYy2QCG88vZigJdifmhETDDz9Q
+xQjp1SCNIBxFHY2voqtiJtfupN5pVieECZS42pbVHMIAUOk7BmNcEWnSKw==
+-----END CERTIFICATE-----
diff --git a/build/debian/fai_config/files/etc/ttyd/server.crt/AVF b/build/debian/fai_config/files/etc/ttyd/server.crt/AVF
new file mode 100644
index 0000000..b4ca829
--- /dev/null
+++ b/build/debian/fai_config/files/etc/ttyd/server.crt/AVF
@@ -0,0 +1,21 @@
+-----BEGIN CERTIFICATE-----
+MIIDiTCCAnGgAwIBAgIUasfD1K/4tJHwNRXL2kdSD9VbeSwwDQYJKoZIhvcNAQEL
+BQAwUzELMAkGA1UEBhMCQ04xCzAJBgNVBAgMAkdEMQswCQYDVQQHDAJTWjETMBEG
+A1UECgwKQWNtZSwgSW5jLjEVMBMGA1UEAwwMQWNtZSBSb290IENBMB4XDTI0MTAx
+NDAxMjgzOFoXDTI1MTAxNDAxMjgzOFowUDELMAkGA1UEBhMCQ04xCzAJBgNVBAgM
+AkdEMQswCQYDVQQHDAJTWjETMBEGA1UECgwKQWNtZSwgSW5jLjESMBAGA1UEAwwJ
+bG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3+uVF4TP
+jUjfL8vJlECAN1rLFK8lDuOUv52VCrW7MXMfGYlA4nk1OKDjygnZIpET6I9cTfCG
+Xiwad6bU6Oqy4MZ2i338F+eERrGpkitSQ7QRqZannjBIDFxXZvJpMTJDIWNCmz+P
+K2VcvCh8im2tJA66wJogUcVmJBugNqleqxFcxPvXOdBdWBK7JYOcb4J643eLX6+D
+X6v2QTlKXfihouVC8wAzbw9HHmOVb7ono1rV7xpcFrOyBiDGVSgEteiB8l26iXA9
+fExkb0rUzHjlgvb/l8/nGAaQHd0eE+/SGd4tXvs9KHX6XJh/PI0ExTsDIBDcuVOt
+2YzXeuM6zzrKLQIDAQABo1gwVjAUBgNVHREEDTALgglsb2NhbGhvc3QwHQYDVR0O
+BBYEFHpFYqFC/AEOfWfdZmpy5YBZfgR2MB8GA1UdIwQYMBaAFGThxe/2q5/Dg6hI
+9Von5HRAOUxZMA0GCSqGSIb3DQEBCwUAA4IBAQBQspP3wo3yzcPWuFk4lRyo7zpF
+JfBBX0UU1Z0MQfIGxLC2YtRvxobRqwLcKUKQjBqUuRdukleOaVVFeXb/HI9vY3ji
+9PfUb2UJ4O3z3pdSK0EwXbkCidtUflRLvPG6dgBrXyLOqxBqA5lWR2ds5HRAMRAi
+eXfDkJTmNOAQAnPgM+35FBgmhh6axG+bUudvvVoA8ca+zW9i1R6/vblxYJ6bhmw0
+8s+uoAX6FXcZ0YFOGdhcpJmnbiRd3D0VVacjc3b9pjFOI8d3bh9pR47p0kVOaRsh
+aAG3gZhyMPOgbYceCjfzND5YhycDI+MzPo/JOYdhHGGJawoh1nP94QNPan6J
+-----END CERTIFICATE-----
diff --git a/build/debian/fai_config/files/etc/ttyd/server.key/AVF b/build/debian/fai_config/files/etc/ttyd/server.key/AVF
new file mode 100644
index 0000000..37933b2
--- /dev/null
+++ b/build/debian/fai_config/files/etc/ttyd/server.key/AVF
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDf65UXhM+NSN8v
+y8mUQIA3WssUryUO45S/nZUKtbsxcx8ZiUDieTU4oOPKCdkikRPoj1xN8IZeLBp3
+ptTo6rLgxnaLffwX54RGsamSK1JDtBGplqeeMEgMXFdm8mkxMkMhY0KbP48rZVy8
+KHyKba0kDrrAmiBRxWYkG6A2qV6rEVzE+9c50F1YErslg5xvgnrjd4tfr4Nfq/ZB
+OUpd+KGi5ULzADNvD0ceY5VvuiejWtXvGlwWs7IGIMZVKAS16IHyXbqJcD18TGRv
+StTMeOWC9v+Xz+cYBpAd3R4T79IZ3i1e+z0odfpcmH88jQTFOwMgENy5U63ZjNd6
+4zrPOsotAgMBAAECggEAARJYlD12ch5WM2aDrPOGOAtREOfP7CCwWcMiOfBP72iR
+Y9Vipxmuz16nwTJ22F7HvPsdPOUo1cFtWhim2Aqr/ZxuT4Ce9oVrk6iDwRdeuYdY
+cIhtChvJi+p0ggMcuyzp90+3AYXxynsOlCufMjSNGaqvYUsNEXnJFSgiKr7mgbIO
+J0VU1Wrquw7N58RKL+T3xEvE7uO3QpLOim2MbfRSVq/JGNxqAGw0/uxtjFs7Vtf9
+z44e/ULeYDS7zMj6cMggxQp5nfzcboGoNVUEDgYjOzqXCe4cG0n3XfN7GJhaS1ZF
+tPd8l4Ch0IrT4hs5uVFaMdFbj+er7mvmqfTVytrRmQKBgQD2kVB53EKhqxgvz2N7
+bAJglOLd6FWKsWlLMSdER0/4dRVRMIBxnYWgQ0gaRc4TM7oyKOl3MDF9jdDne5KJ
+cnfzFoH2GD6VBQRr0mFmV1UV6oHEjDJBasMo/1Vw3TJ4oZgZpYpJjrDmPWZqHUs4
+I79TdvJrNFSmk3MGVFjatLIq5QKBgQDofHpHfBeRCn2Z3OOkiAN5V53n5deZl6Jt
+lGTsrXKpEzRTre4LWZojoB9hiGjptZkXHA2HW90RiV9OHhTa8W9ZntLnOnWc5RUn
+Tzh14KupjsBQm/gE8SuqHSDx1mxTnIUo0W28d/Beecri5KfaoEY+wxZXOeQy5JFR
+ec/AhU4FqQKBgGhVzUwDnF502+M/SsVrSwY7elSUf74UnI2o2wjVdE2anc6hS3jI
+Q0cxsU0MxMrzVJLtJP2+cvLCE+ggLj3jJkbC+3N7ht/gI6LMf1KjGeoQNaFKAeoU
+l0i94xXDRBwvpQEVP5MowkprKO82PiIfXlKfPq2Gk1t5gW7oOkExvULRAoGBAK7R
+051nec0uJ06I5IE3ae1X7jyP//TWKmTeHpo+vybWcxWth3/va9H4OUC9M67ySGEx
+ThcIBA+IzirOwf31aTbqEEuiEQje1m5NyvYQ8OS6nHDBJ9qHg78S0lAoXiLtYtBT
+04HSauSQDvlY2cOzm77cMjN7K9b9Oy0aPRfW5dmpAoGAGesq4Ojky4crpi0H1O7n
+cMuIAzaPozsMx7iSrhUe69fwVFiMkEKR6ems01DmjYwPb6DtxCieaRlGbd9E8oIZ
+y6n+Uh9Qbc5sDhPMsys6NyKOv/A6rkn49/etr40f0Z5g9g/d2+qtwoAXjo3sSPuW
+7iqbruRjbKUaJKzdpIqOKD0=
+-----END PRIVATE KEY-----
diff --git a/build/debian/fai_config/scripts/AVF/10-systemd b/build/debian/fai_config/scripts/AVF/10-systemd
index 09d1bd1..6a106c6 100755
--- a/build/debian/fai_config/scripts/AVF/10-systemd
+++ b/build/debian/fai_config/scripts/AVF/10-systemd
@@ -1,7 +1,8 @@
 #!/bin/bash
 
 chmod +x $target/usr/local/bin/forwarder_guest
+chmod +x $target/usr/local/bin/forwarder_guest_launcher
 chmod +x $target/usr/local/bin/ip_addr_reporter
 chmod +x $target/usr/local/bin/ttyd
 ln -s /etc/systemd/system/ttyd.service $target/etc/systemd/system/multi-user.target.wants/ttyd.service
-ln -s /etc/systemd/system/ip_addr_reporter.service $target/etc/systemd/system/multi-user.target.wants/ip_addr_reporter.service
\ No newline at end of file
+ln -s /etc/systemd/system/ip_addr_reporter.service $target/etc/systemd/system/multi-user.target.wants/ip_addr_reporter.service
diff --git a/build/debian/fai_config/scripts/AVF/20-useradd b/build/debian/fai_config/scripts/AVF/20-useradd
index b5887c5..9fbcd43 100755
--- a/build/debian/fai_config/scripts/AVF/20-useradd
+++ b/build/debian/fai_config/scripts/AVF/20-useradd
@@ -1,4 +1,4 @@
 #!/bin/bash
 
-$ROOTCMD useradd -m -u 1000 -g 1000 -N -G sudo droid
+$ROOTCMD useradd -m -u 1000 -N -G sudo droid
 $ROOTCMD echo 'droid ALL=(ALL) NOPASSWD:ALL' >> $target/etc/sudoers
\ No newline at end of file
diff --git a/docs/vm_remote_attestation.md b/docs/vm_remote_attestation.md
index ee20591..2ee0fae 100644
--- a/docs/vm_remote_attestation.md
+++ b/docs/vm_remote_attestation.md
@@ -126,7 +126,7 @@
 
 To support VM remote attestation, vendors must include an RKP VM marker in their
 DICE certificates. This marker should be present from the early boot stage
-within the TEE and continue through to the last DICE certificate before
+within the TEE and continue through to the leaf DICE certificate before
 [pvmfw][pvmfw] takes over.
 
 ![RKP VM DICE chain][rkpvm-dice-chain]
@@ -140,6 +140,20 @@
 server because it will lack the RKP VM marker that pvmfw would have added in a
 genuine RKP VM boot process.
 
+### Testing
+
+To ensure the correct implementation and usage of RKP VM markers, we've
+incorporated comprehensive checks into various xTS tests (e.g.,
+`VtsHalRemotelyProvisionedComponentTargetTest`).
+
+These tests validate the following conditions:
+
+- The RKP VM DICE chain must have a continuous presence of at least two RKP VM
+  markers, extending to the leaf DICE certificate.
+- Non-RKP VM DICE chains must not have a continuous presence of two or more RKP
+  VM markers, preventing non-RKP VM chains from being incorrectly identified as
+  RKP VM chains.
+
 [pvmfw]: ../guest/pvmfw/README.md
 [rkpvm-dice-chain]: img/rkpvm-dice-chain.png
 
diff --git a/guest/forwarder_guest_launcher/Cargo.toml b/guest/forwarder_guest_launcher/Cargo.toml
new file mode 100644
index 0000000..bf0c0ed
--- /dev/null
+++ b/guest/forwarder_guest_launcher/Cargo.toml
@@ -0,0 +1,13 @@
+[package]
+name = "forwarder_guest_launcher"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
+clap = { version = "4.5.20", features = ["derive"] }
+prost = "0.13.3"
+tokio = { version = "1.40.0", features = ["rt-multi-thread"] }
+tonic = "0.12.3"
+
+[build-dependencies]
+tonic-build = "0.12.3"
diff --git a/guest/forwarder_guest_launcher/build.rs b/guest/forwarder_guest_launcher/build.rs
new file mode 100644
index 0000000..c923747
--- /dev/null
+++ b/guest/forwarder_guest_launcher/build.rs
@@ -0,0 +1,18 @@
+// Copyright 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+fn main() -> Result<(), Box<dyn std::error::Error>> {
+    tonic_build::compile_protos("../../libs/debian_service/proto/DebianService.proto")?;
+    Ok(())
+}
diff --git a/guest/forwarder_guest_launcher/src/main.rs b/guest/forwarder_guest_launcher/src/main.rs
new file mode 100644
index 0000000..4042fe5
--- /dev/null
+++ b/guest/forwarder_guest_launcher/src/main.rs
@@ -0,0 +1,50 @@
+// Copyright 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! Launcher of forwarder_guest
+
+use clap::Parser;
+use debian_service::debian_service_client::DebianServiceClient;
+use debian_service::Empty;
+use tonic::transport::Endpoint;
+use tonic::Request;
+
+mod debian_service {
+    tonic::include_proto!("com.android.virtualization.vmlauncher.proto");
+}
+
+#[derive(Parser)]
+/// Flags for running command
+pub struct Args {
+    /// Host IP address
+    #[arg(long)]
+    #[arg(alias = "host")]
+    host_addr: String,
+}
+
+#[tokio::main]
+async fn main() -> Result<(), Box<dyn std::error::Error>> {
+    let args = Args::parse();
+    let addr = format!("https://{}:12000", args.host_addr);
+
+    let channel = Endpoint::from_shared(addr)?.connect().await?;
+    let mut client = DebianServiceClient::new(channel);
+    let mut res_stream =
+        client.open_forwarding_request_queue(Request::new(Empty {})).await?.into_inner();
+
+    while let Some(response) = res_stream.message().await? {
+        println!("Response from the host: {:?}", response);
+    }
+    Ok(())
+}
diff --git a/libs/debian_service/proto/DebianService.proto b/libs/debian_service/proto/DebianService.proto
index 5adf615..5e3286a 100644
--- a/libs/debian_service/proto/DebianService.proto
+++ b/libs/debian_service/proto/DebianService.proto
@@ -23,12 +23,20 @@
 
 service DebianService {
   rpc ReportVmIpAddr (IpAddr) returns (ReportVmIpAddrResponse) {}
+  rpc OpenForwardingRequestQueue (Empty) returns (stream ForwardingRequestItem) {}
 }
 
+message Empty {}
+
 message IpAddr {
   string addr = 1;
 }
 
 message ReportVmIpAddrResponse {
-    bool success = 1;
-}
\ No newline at end of file
+  bool success = 1;
+}
+
+message ForwardingRequestItem {
+  int32 guest_tcp_port = 1;
+  int32 vsock_port = 2;
+}
diff --git a/libs/vm_launcher_lib/java/com/android/virtualization/vmlauncher/DebianServiceImpl.java b/libs/vm_launcher_lib/java/com/android/virtualization/vmlauncher/DebianServiceImpl.java
index 2f7666a..ccc0ed6 100644
--- a/libs/vm_launcher_lib/java/com/android/virtualization/vmlauncher/DebianServiceImpl.java
+++ b/libs/vm_launcher_lib/java/com/android/virtualization/vmlauncher/DebianServiceImpl.java
@@ -19,6 +19,8 @@
 import android.util.Log;
 
 import com.android.virtualization.vmlauncher.proto.DebianServiceGrpc;
+import com.android.virtualization.vmlauncher.proto.Empty;
+import com.android.virtualization.vmlauncher.proto.ForwardingRequestItem;
 import com.android.virtualization.vmlauncher.proto.IpAddr;
 import com.android.virtualization.vmlauncher.proto.ReportVmIpAddrResponse;
 
@@ -43,6 +45,16 @@
         responseObserver.onCompleted();
     }
 
+    @Override
+    public void openForwardingRequestQueue(
+            Empty request, StreamObserver<ForwardingRequestItem> responseObserver) {
+        Log.d(DebianServiceImpl.TAG, "OpenForwardingRequestQueue");
+
+        // TODO(b/340126051): Bring information from forwarder_host.
+
+        responseObserver.onCompleted();
+    }
+
     protected interface DebianServiceCallback {
         void onIpAddressAvailable(String ipAddr);
     }