Merge "pvmfw: Expect proper CPU compatible "arm,armv8"" into main
diff --git a/apex/Android.bp b/apex/Android.bp
index e6c809c..48b7b1f 100644
--- a/apex/Android.bp
+++ b/apex/Android.bp
@@ -32,6 +32,13 @@
     },
 }
 
+soong_config_string_variable {
+    name: "avf_microdroid_guest_gki_version",
+    values: [
+        "android14_61_pkvm_experimental",
+    ],
+}
+
 soong_config_module_type {
     name: "avf_flag_aware_apex_defaults",
     module_type: "apex_defaults",
@@ -44,6 +51,9 @@
         "release_avf_enable_virt_cpufreq",
         "release_avf_support_custom_vm_with_paravirtualized_devices",
     ],
+    variables: [
+        "avf_microdroid_guest_gki_version",
+    ],
     properties: [
         "androidManifest",
         "arch",
@@ -154,6 +164,16 @@
         "EmptyPayloadApp",
     ],
     soong_config_variables: {
+        avf_microdroid_guest_gki_version: {
+            android14_61_pkvm_experimental: {
+                prebuilts: [
+                    "microdroid_gki-android14-6.1-pkvm_experimental_initrd_debuggable",
+                    "microdroid_gki-android14-6.1-pkvm_experimental_initrd_normal",
+                    "microdroid_gki-android14-6.1-pkvm_experimental_kernel",
+                    "microdroid_gki-android14-6.1-pkvm_experimental.json",
+                ],
+            },
+        },
         release_avf_enable_device_assignment: {
             prebuilts: [
                 "com.android.virt.vfio_handler.rc",
@@ -170,14 +190,6 @@
         release_avf_enable_llpvm_changes: {
             androidManifest: "AndroidManifest.xml",
         },
-        release_avf_enable_vendor_modules: {
-            prebuilts: [
-                "microdroid_gki-android14-6.1_initrd_debuggable",
-                "microdroid_gki-android14-6.1_initrd_normal",
-                "microdroid_gki-android14-6.1_kernel",
-                "microdroid_gki-android14-6.1.json",
-            ],
-        },
         release_avf_enable_remote_attestation: {
             vintf_fragments: [
                 "virtualizationservice.xml",
diff --git a/apex/sign_virt_apex.py b/apex/sign_virt_apex.py
index 7c59b54..55fe6d2 100644
--- a/apex/sign_virt_apex.py
+++ b/apex/sign_virt_apex.py
@@ -498,7 +498,7 @@
     RunCommand(args, cmd)
 
 
-gki_versions = ['android14-6.1']
+gki_versions = ['android14-6.1-pkvm_experimental']
 
 # dict of (key, file) for re-sign/verification. keys are un-versioned for readability.
 virt_apex_non_gki_files = {
diff --git a/compos/apex/Android.bp b/compos/apex/Android.bp
index 55cc446..9996e4e 100644
--- a/compos/apex/Android.bp
+++ b/compos/apex/Android.bp
@@ -51,6 +51,13 @@
         "compsvc",
     ],
 
+    native_shared_libs: [
+        // b/334192594: compsvc has a transitive dependency to libminijail.
+        // Adding it explicitly here is required because the existence of
+        // it in Microdroid cannot be guaranteed.
+        "libminijail",
+    ],
+
     systemserverclasspath_fragments: ["com.android.compos-systemserverclasspath-fragment"],
 
     apps: [
diff --git a/java/framework/src/android/system/virtualmachine/VirtualMachine.java b/java/framework/src/android/system/virtualmachine/VirtualMachine.java
index 51b91ca..1076219 100644
--- a/java/framework/src/android/system/virtualmachine/VirtualMachine.java
+++ b/java/framework/src/android/system/virtualmachine/VirtualMachine.java
@@ -63,6 +63,7 @@
 import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
 import android.os.ServiceSpecificException;
+import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.system.virtualizationcommon.DeathReason;
 import android.system.virtualizationcommon.ErrorCode;
@@ -102,6 +103,7 @@
 import java.nio.file.Path;
 import java.nio.file.SimpleFileVisitor;
 import java.nio.file.attribute.BasicFileAttributes;
+import java.util.Arrays;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
@@ -161,6 +163,7 @@
     public static final long MAX_VSOCK_PORT = (1L << 32) - 1;
 
     private ParcelFileDescriptor mTouchSock;
+    private ParcelFileDescriptor mKeySock;
 
     /**
      * Status of a virtual machine
@@ -861,25 +864,48 @@
         // Handle input devices here
         List<InputDevice> inputDevices = new ArrayList<>();
         if (vmConfig.getCustomImageConfig() != null
-                && vmConfig.getCustomImageConfig().useTouch()
                 && rawConfig.displayConfig != null) {
-            ParcelFileDescriptor[] pfds = ParcelFileDescriptor.createSocketPair();
-            mTouchSock = pfds[0];
-            InputDevice.SingleTouch t = new InputDevice.SingleTouch();
-            t.width = rawConfig.displayConfig.width;
-            t.height = rawConfig.displayConfig.height;
-            t.pfd = pfds[1];
-            inputDevices.add(InputDevice.singleTouch(t));
+            if (vmConfig.getCustomImageConfig().useTouch()) {
+                ParcelFileDescriptor[] pfds = ParcelFileDescriptor.createSocketPair();
+                mTouchSock = pfds[0];
+                InputDevice.SingleTouch t = new InputDevice.SingleTouch();
+                t.width = rawConfig.displayConfig.width;
+                t.height = rawConfig.displayConfig.height;
+                t.pfd = pfds[1];
+                inputDevices.add(InputDevice.singleTouch(t));
+            }
+            if (vmConfig.getCustomImageConfig().useKeyboard()) {
+                ParcelFileDescriptor[] pfds = ParcelFileDescriptor.createSocketPair();
+                mKeySock = pfds[0];
+                InputDevice.Keyboard k = new InputDevice.Keyboard();
+                k.pfd = pfds[1];
+                inputDevices.add(InputDevice.keyboard(k));
+            }
         }
         rawConfig.inputDevices = inputDevices.toArray(new InputDevice[0]);
 
         return android.system.virtualizationservice.VirtualMachineConfig.rawConfig(rawConfig);
     }
 
-    private void addInputEvent(ByteBuffer buffer, short type, short code, int value) {
-        buffer.putShort(type);
-        buffer.putShort(code);
-        buffer.putInt(value);
+    private static record InputEvent(short type, short code, int value) {}
+
+    /** @hide */
+    public boolean sendKeyEvent(KeyEvent event) {
+        if (mKeySock == null) {
+            Log.d(TAG, "mKeySock == null");
+            return false;
+        }
+        // from include/uapi/linux/input-event-codes.h in the kernel.
+        short EV_SYN = 0x00;
+        short EV_KEY = 0x01;
+        short SYN_REPORT = 0x00;
+        boolean down = event.getAction() != MotionEvent.ACTION_UP;
+
+        return writeEventsToSock(
+                mKeySock,
+                Arrays.asList(
+                        new InputEvent(EV_KEY, (short) event.getScanCode(), down ? 1 : 0),
+                        new InputEvent(EV_SYN, SYN_REPORT, 0)));
     }
 
     /** @hide */
@@ -901,24 +927,30 @@
         int y = (int) event.getY();
         boolean down = event.getAction() != MotionEvent.ACTION_UP;
 
+        return writeEventsToSock(
+                mTouchSock,
+                Arrays.asList(
+                        new InputEvent(EV_ABS, ABS_X, x),
+                        new InputEvent(EV_ABS, ABS_Y, y),
+                        new InputEvent(EV_KEY, BTN_TOUCH, down ? 1 : 0),
+                        new InputEvent(EV_SYN, SYN_REPORT, 0)));
+    }
+
+    private boolean writeEventsToSock(ParcelFileDescriptor sock, List<InputEvent> evtList) {
         ByteBuffer byteBuffer =
-                ByteBuffer.allocate(32 /* (type: u16 + code: u16 + value: i32) * 4 */);
+                ByteBuffer.allocate(8 /* (type: u16 + code: u16 + value: i32) */ * evtList.size());
         byteBuffer.clear();
         byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
-
-        addInputEvent(byteBuffer, EV_ABS, ABS_X, x);
-        addInputEvent(byteBuffer, EV_ABS, ABS_Y, y);
-        addInputEvent(byteBuffer, EV_KEY, BTN_TOUCH, down ? 1 : 0);
-        addInputEvent(byteBuffer, EV_SYN, SYN_REPORT, 0);
-
+        for (InputEvent e : evtList) {
+            byteBuffer.putShort(e.type);
+            byteBuffer.putShort(e.code);
+            byteBuffer.putInt(e.value);
+        }
         try {
             IoBridge.write(
-                    mTouchSock.getFileDescriptor(),
-                    byteBuffer.array(),
-                    0,
-                    byteBuffer.array().length);
+                    sock.getFileDescriptor(), byteBuffer.array(), 0, byteBuffer.array().length);
         } catch (IOException e) {
-            Log.d(TAG, "cannot send touch evt", e);
+            Log.d(TAG, "cannot send event", e);
             return false;
         }
         return true;
diff --git a/java/framework/src/android/system/virtualmachine/VirtualMachineCustomImageConfig.java b/java/framework/src/android/system/virtualmachine/VirtualMachineCustomImageConfig.java
index 7cf5893..8d294fd 100644
--- a/java/framework/src/android/system/virtualmachine/VirtualMachineCustomImageConfig.java
+++ b/java/framework/src/android/system/virtualmachine/VirtualMachineCustomImageConfig.java
@@ -35,6 +35,7 @@
     private static final String KEY_DISK_IMAGES = "disk_images";
     private static final String KEY_DISPLAY_CONFIG = "display_config";
     private static final String KEY_TOUCH = "touch";
+    private static final String KEY_KEYBOARD = "keyboard";
 
     @Nullable private final String name;
     @NonNull private final String kernelPath;
@@ -44,6 +45,7 @@
     @Nullable private final Disk[] disks;
     @Nullable private final DisplayConfig displayConfig;
     private final boolean touch;
+    private final boolean keyboard;
 
     @Nullable
     public Disk[] getDisks() {
@@ -79,6 +81,10 @@
         return touch;
     }
 
+    public boolean useKeyboard() {
+        return keyboard;
+    }
+
     /** @hide */
     public VirtualMachineCustomImageConfig(
             String name,
@@ -88,7 +94,8 @@
             String[] params,
             Disk[] disks,
             DisplayConfig displayConfig,
-            boolean touch) {
+            boolean touch,
+            boolean keyboard) {
         this.name = name;
         this.kernelPath = kernelPath;
         this.initrdPath = initrdPath;
@@ -97,6 +104,7 @@
         this.disks = disks;
         this.displayConfig = displayConfig;
         this.touch = touch;
+        this.keyboard = keyboard;
     }
 
     static VirtualMachineCustomImageConfig from(PersistableBundle customImageConfigBundle) {
@@ -125,6 +133,7 @@
                 customImageConfigBundle.getPersistableBundle(KEY_DISPLAY_CONFIG);
         builder.setDisplayConfig(DisplayConfig.from(displayConfigPb));
         builder.useTouch(customImageConfigBundle.getBoolean(KEY_TOUCH));
+        builder.useKeyboard(customImageConfigBundle.getBoolean(KEY_KEYBOARD));
         return builder.build();
     }
 
@@ -154,6 +163,7 @@
                         .map(dc -> dc.toPersistableBundle())
                         .orElse(null));
         pb.putBoolean(KEY_TOUCH, touch);
+        pb.putBoolean(KEY_KEYBOARD, keyboard);
         return pb;
     }
 
@@ -203,6 +213,7 @@
         private List<Disk> disks = new ArrayList<>();
         private DisplayConfig displayConfig;
         private boolean touch;
+        private boolean keyboard;
 
         /** @hide */
         public Builder() {}
@@ -256,6 +267,12 @@
         }
 
         /** @hide */
+        public Builder useKeyboard(boolean keyboard) {
+            this.keyboard = keyboard;
+            return this;
+        }
+
+        /** @hide */
         public VirtualMachineCustomImageConfig build() {
             return new VirtualMachineCustomImageConfig(
                     this.name,
@@ -265,7 +282,8 @@
                     this.params.toArray(new String[0]),
                     this.disks.toArray(new Disk[0]),
                     displayConfig,
-                    touch);
+                    touch,
+                    keyboard);
         }
     }
 
diff --git a/libs/vmconfig/src/lib.rs b/libs/vmconfig/src/lib.rs
index 907e0d3..7c917b0 100644
--- a/libs/vmconfig/src/lib.rs
+++ b/libs/vmconfig/src/lib.rs
@@ -18,6 +18,8 @@
     aidl::android::system::virtualizationservice::CpuTopology::CpuTopology,
     aidl::android::system::virtualizationservice::DiskImage::DiskImage as AidlDiskImage,
     aidl::android::system::virtualizationservice::Partition::Partition as AidlPartition,
+    aidl::android::system::virtualizationservice::VirtualMachineAppConfig::DebugLevel::DebugLevel,
+    aidl::android::system::virtualizationservice::VirtualMachineConfig::VirtualMachineConfig,
     aidl::android::system::virtualizationservice::VirtualMachineRawConfig::VirtualMachineRawConfig,
     binder::ParcelFileDescriptor,
 };
@@ -127,6 +129,14 @@
     }
 }
 
+/// Returns the debug level of the VM from its configuration.
+pub fn get_debug_level(config: &VirtualMachineConfig) -> Option<DebugLevel> {
+    match config {
+        VirtualMachineConfig::AppConfig(config) => Some(config.debugLevel),
+        VirtualMachineConfig::RawConfig(_) => None,
+    }
+}
+
 /// A disk image to be made available to the VM.
 #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
 pub struct DiskImage {
diff --git a/microdroid/Android.bp b/microdroid/Android.bp
index 33d98dc..98a541f 100644
--- a/microdroid/Android.bp
+++ b/microdroid/Android.bp
@@ -582,39 +582,39 @@
 }
 
 ///////////////////////////////////////
-// GKI-android14-6.1 modules
+// GKI-android14-6.1-pkvm_experimental modules
 ///////////////////////////////////////
 prebuilt_etc {
-    name: "microdroid_gki-android14-6.1.json",
-    src: "microdroid_gki-android14-6.1.json",
+    name: "microdroid_gki-android14-6.1-pkvm_experimental.json",
+    src: "microdroid_gki-android14-6.1-pkvm_experimental.json",
 }
 
 avb_add_hash_footer {
-    name: "microdroid_gki-android14-6.1_kernel_signed",
+    name: "microdroid_gki-android14-6.1-pkvm_experimental_kernel_signed",
     defaults: ["microdroid_kernel_signed_defaults"],
-    filename: "microdroid_gki-android14-6.1_kernel_signed",
+    filename: "microdroid_gki-android14-6.1-pkvm_experimental_kernel_signed",
     arch: {
         arm64: {
-            src: ":microdroid_gki_kernel_prebuilts-6.1-arm64",
+            src: ":microdroid_gki_kernel_prebuilts-6.1-pkvm_experimental-arm64",
         },
         x86_64: {
-            src: ":microdroid_gki_kernel_prebuilts-6.1-x86_64",
+            src: ":microdroid_gki_kernel_prebuilts-6.1-pkvm_experimental-x86_64",
         },
     },
     include_descriptors_from_images: [
-        ":microdroid_gki-android14-6.1_initrd_normal_hashdesc",
-        ":microdroid_gki-android14-6.1_initrd_debug_hashdesc",
+        ":microdroid_gki-android14-6.1-pkvm_experimental_initrd_normal_hashdesc",
+        ":microdroid_gki-android14-6.1-pkvm_experimental_initrd_debug_hashdesc",
     ],
 }
 
 // HACK: use cc_genrule for arch-specific properties
 cc_genrule {
-    name: "microdroid_gki-android14-6.1_kernel_signed-lz4",
-    out: ["microdroid_gki-android14-6.1_kernel_signed-lz4"],
+    name: "microdroid_gki-android14-6.1-pkvm_experimental_kernel_signed-lz4",
+    out: ["microdroid_gki-android14-6.1-pkvm_experimental_kernel_signed-lz4"],
     srcs: [":empty_file"],
     arch: {
         arm64: {
-            srcs: [":microdroid_gki-android14-6.1_kernel_signed"],
+            srcs: [":microdroid_gki-android14-6.1-pkvm_experimental_kernel_signed"],
             exclude_srcs: [":empty_file"],
         },
     },
@@ -623,30 +623,30 @@
 }
 
 prebuilt_etc {
-    name: "microdroid_gki-android14-6.1_kernel",
-    filename: "microdroid_gki-android14-6.1_kernel",
+    name: "microdroid_gki-android14-6.1-pkvm_experimental_kernel",
+    filename: "microdroid_gki-android14-6.1-pkvm_experimental_kernel",
     src: ":empty_file",
     relative_install_path: "fs",
     arch: {
         arm64: {
-            src: ":microdroid_gki-android14-6.1_kernel_signed-lz4",
+            src: ":microdroid_gki-android14-6.1-pkvm_experimental_kernel_signed-lz4",
         },
         x86_64: {
-            src: ":microdroid_gki-android14-6.1_kernel_signed",
+            src: ":microdroid_gki-android14-6.1-pkvm_experimental_kernel_signed",
         },
     },
 }
 
 avb_gen_vbmeta_image {
-    name: "microdroid_gki-android14-6.1_initrd_normal_hashdesc",
+    name: "microdroid_gki-android14-6.1-pkvm_experimental_initrd_normal_hashdesc",
     defaults: ["microdroid_initrd_normal_defaults"],
-    src: ":microdroid_gki-android14-6.1_initrd_normal",
+    src: ":microdroid_gki-android14-6.1-pkvm_experimental_initrd_normal",
 }
 
 avb_gen_vbmeta_image {
-    name: "microdroid_gki-android14-6.1_initrd_debug_hashdesc",
+    name: "microdroid_gki-android14-6.1-pkvm_experimental_initrd_debug_hashdesc",
     defaults: ["microdroid_initrd_debug_defaults"],
-    src: ":microdroid_gki-android14-6.1_initrd_debuggable",
+    src: ":microdroid_gki-android14-6.1-pkvm_experimental_initrd_debuggable",
 }
 
 python_binary_host {
@@ -660,10 +660,10 @@
     srcs: [":microdroid_kernel"],
     arch: {
         arm64: {
-            srcs: [":microdroid_gki-android14-6.1_kernel_signed"],
+            srcs: [":microdroid_gki-android14-6.1-pkvm_experimental_kernel_signed"],
         },
         x86_64: {
-            srcs: [":microdroid_gki-android14-6.1_kernel_signed"],
+            srcs: [":microdroid_gki-android14-6.1-pkvm_experimental_kernel_signed"],
         },
     },
     out: ["lib.rs"],
diff --git a/microdroid/initrd/Android.bp b/microdroid/initrd/Android.bp
index 544a3ff..7246f04 100644
--- a/microdroid/initrd/Android.bp
+++ b/microdroid/initrd/Android.bp
@@ -41,22 +41,22 @@
 }
 
 genrule {
-    name: "microdroid_gki-android14-6.1_initrd_gen_arm64",
+    name: "microdroid_gki-android14-6.1-pkvm_experimental_initrd_gen_arm64",
     srcs: [
         ":microdroid_ramdisk",
         ":microdroid_first_stage_ramdisk",
-        ":microdroid_gki_modules-6.1-arm64",
+        ":microdroid_gki_modules-6.1-pkvm_experimental-arm64",
     ],
     out: ["microdroid_initrd.img"],
     cmd: "cat $(in) > $(out)",
 }
 
 genrule {
-    name: "microdroid_gki-android14-6.1_initrd_gen_x86_64",
+    name: "microdroid_gki-android14-6.1-pkvm_experimental_initrd_gen_x86_64",
     srcs: [
         ":microdroid_ramdisk",
         ":microdroid_first_stage_ramdisk",
-        ":microdroid_gki_modules-6.1-x86_64",
+        ":microdroid_gki_modules-6.1-pkvm_experimental-x86_64",
     ],
     out: ["microdroid_initrd.img"],
     cmd: "cat $(in) > $(out)",
@@ -96,13 +96,13 @@
 }
 
 genrule {
-    name: "microdroid_gki-android14-6.1_initrd_debuggable_arm64",
+    name: "microdroid_gki-android14-6.1-pkvm_experimental_initrd_debuggable_arm64",
     tools: ["initrd_bootconfig"],
     srcs: [
-        ":microdroid_gki-android14-6.1_initrd_gen_arm64",
+        ":microdroid_gki-android14-6.1-pkvm_experimental_initrd_gen_arm64",
         ":microdroid_bootconfig_debuggable_src",
     ] + bootconfigs_arm64,
-    out: ["microdroid_gki-android14-6.1_initrd_debuggable_arm64"],
+    out: ["microdroid_gki-android14-6.1-pkvm_experimental_initrd_debuggable_arm64"],
     cmd: "$(location initrd_bootconfig) attach --output $(out) $(in)",
 }
 
@@ -118,13 +118,13 @@
 }
 
 genrule {
-    name: "microdroid_gki-android14-6.1_initrd_debuggable_x86_64",
+    name: "microdroid_gki-android14-6.1-pkvm_experimental_initrd_debuggable_x86_64",
     tools: ["initrd_bootconfig"],
     srcs: [
-        ":microdroid_gki-android14-6.1_initrd_gen_x86_64",
+        ":microdroid_gki-android14-6.1-pkvm_experimental_initrd_gen_x86_64",
         ":microdroid_bootconfig_debuggable_src",
     ] + bootconfigs_x86_64,
-    out: ["microdroid_gki-android14-6.1_initrd_debuggable_x86_64"],
+    out: ["microdroid_gki-android14-6.1-pkvm_experimental_initrd_debuggable_x86_64"],
     cmd: "$(location initrd_bootconfig) attach --output $(out) $(in)",
 }
 
@@ -140,13 +140,13 @@
 }
 
 genrule {
-    name: "microdroid_gki-android14-6.1_initrd_normal_arm64",
+    name: "microdroid_gki-android14-6.1-pkvm_experimental_initrd_normal_arm64",
     tools: ["initrd_bootconfig"],
     srcs: [
-        ":microdroid_gki-android14-6.1_initrd_gen_arm64",
+        ":microdroid_gki-android14-6.1-pkvm_experimental_initrd_gen_arm64",
         ":microdroid_bootconfig_normal_src",
     ] + bootconfigs_arm64,
-    out: ["microdroid_gki-android14-6.1_initrd_normal_arm64"],
+    out: ["microdroid_gki-android14-6.1-pkvm_experimental_initrd_normal_arm64"],
     cmd: "$(location initrd_bootconfig) attach --output $(out) $(in)",
 }
 
@@ -162,13 +162,13 @@
 }
 
 genrule {
-    name: "microdroid_gki-android14-6.1_initrd_normal_x86_64",
+    name: "microdroid_gki-android14-6.1-pkvm_experimental_initrd_normal_x86_64",
     tools: ["initrd_bootconfig"],
     srcs: [
-        ":microdroid_gki-android14-6.1_initrd_gen_x86_64",
+        ":microdroid_gki-android14-6.1-pkvm_experimental_initrd_gen_x86_64",
         ":microdroid_bootconfig_normal_src",
     ] + bootconfigs_x86_64,
-    out: ["microdroid_gki-android14-6.1_initrd_normal_x86_64"],
+    out: ["microdroid_gki-android14-6.1-pkvm_experimental_initrd_normal_x86_64"],
     cmd: "$(location initrd_bootconfig) attach --output $(out) $(in)",
 }
 
@@ -188,18 +188,18 @@
 }
 
 prebuilt_etc {
-    name: "microdroid_gki-android14-6.1_initrd_debuggable",
+    name: "microdroid_gki-android14-6.1-pkvm_experimental_initrd_debuggable",
     // We don't have ramdisk for architectures other than x86_64 & arm64
     src: ":empty_file",
     arch: {
         x86_64: {
-            src: ":microdroid_gki-android14-6.1_initrd_debuggable_x86_64",
+            src: ":microdroid_gki-android14-6.1-pkvm_experimental_initrd_debuggable_x86_64",
         },
         arm64: {
-            src: ":microdroid_gki-android14-6.1_initrd_debuggable_arm64",
+            src: ":microdroid_gki-android14-6.1-pkvm_experimental_initrd_debuggable_arm64",
         },
     },
-    filename: "microdroid_gki-android14-6.1_initrd_debuggable.img",
+    filename: "microdroid_gki-android14-6.1-pkvm_experimental_initrd_debuggable.img",
 }
 
 prebuilt_etc {
@@ -218,16 +218,16 @@
 }
 
 prebuilt_etc {
-    name: "microdroid_gki-android14-6.1_initrd_normal",
+    name: "microdroid_gki-android14-6.1-pkvm_experimental_initrd_normal",
     // We don't have ramdisk for architectures other than x86_64 & arm64
     src: ":empty_file",
     arch: {
         x86_64: {
-            src: ":microdroid_gki-android14-6.1_initrd_normal_x86_64",
+            src: ":microdroid_gki-android14-6.1-pkvm_experimental_initrd_normal_x86_64",
         },
         arm64: {
-            src: ":microdroid_gki-android14-6.1_initrd_normal_arm64",
+            src: ":microdroid_gki-android14-6.1-pkvm_experimental_initrd_normal_arm64",
         },
     },
-    filename: "microdroid_gki-android14-6.1_initrd_normal.img",
+    filename: "microdroid_gki-android14-6.1-pkvm_experimental_initrd_normal.img",
 }
diff --git a/microdroid/microdroid_gki-android14-6.1.json b/microdroid/microdroid_gki-android14-6.1-pkvm_experimental.json
similarity index 92%
rename from microdroid/microdroid_gki-android14-6.1.json
rename to microdroid/microdroid_gki-android14-6.1-pkvm_experimental.json
index 9392fae..4e58573 100644
--- a/microdroid/microdroid_gki-android14-6.1.json
+++ b/microdroid/microdroid_gki-android14-6.1-pkvm_experimental.json
@@ -1,5 +1,5 @@
 {
-  "kernel": "/apex/com.android.virt/etc/fs/microdroid_gki-android14-6.1_kernel",
+  "kernel": "/apex/com.android.virt/etc/fs/microdroid_gki-android14-6.1-pkvm_experimental_kernel",
   "disks": [
     {
       "partitions": [
diff --git a/pvmfw/src/entry.rs b/pvmfw/src/entry.rs
index 253604b..72212c3 100644
--- a/pvmfw/src/entry.rs
+++ b/pvmfw/src/entry.rs
@@ -403,12 +403,6 @@
     unsafe { slice::from_raw_parts_mut(range.start.0 as *mut u8, range.end - range.start) }
 }
 
-enum AppendedConfigType {
-    Valid,
-    Invalid,
-    NotFound,
-}
-
 enum AppendedPayload<'a> {
     /// Configuration data.
     Config(config::Config<'a>),
@@ -418,35 +412,29 @@
 
 impl<'a> AppendedPayload<'a> {
     fn new(data: &'a mut [u8]) -> Option<Self> {
-        match Self::guess_config_type(data) {
-            AppendedConfigType::Valid => {
-                let config = config::Config::new(data);
-                Some(Self::Config(config.unwrap()))
-            }
-            AppendedConfigType::NotFound if cfg!(feature = "legacy") => {
+        // The borrow checker gets confused about the ownership of data (see inline comments) so we
+        // intentionally obfuscate it using a raw pointer; see a similar issue (still not addressed
+        // in v1.77) in https://users.rust-lang.org/t/78467.
+        let data_ptr = data as *mut [u8];
+
+        // Config::new() borrows data as mutable ...
+        match config::Config::new(data) {
+            // ... so this branch has a mutable reference to data, from the Ok(Config<'a>). But ...
+            Ok(valid) => Some(Self::Config(valid)),
+            // ... if Config::new(data).is_err(), the Err holds no ref to data. However ...
+            Err(config::Error::InvalidMagic) if cfg!(feature = "legacy") => {
+                // ... the borrow checker still complains about a second mutable ref without this.
+                // SAFETY: Pointer to a valid mut (not accessed elsewhere), 'a lifetime re-used.
+                let data: &'a mut _ = unsafe { &mut *data_ptr };
+
                 const BCC_SIZE: usize = SIZE_4KB;
                 warn!("Assuming the appended data at {:?} to be a raw BCC", data.as_ptr());
                 Some(Self::LegacyBcc(&mut data[..BCC_SIZE]))
             }
-            _ => None,
-        }
-    }
-
-    fn guess_config_type(data: &mut [u8]) -> AppendedConfigType {
-        // This function is necessary to prevent the borrow checker from getting confused
-        // about the ownership of data in new(); see https://users.rust-lang.org/t/78467.
-        let addr = data.as_ptr();
-
-        match config::Config::new(data) {
-            Err(config::Error::InvalidMagic) => {
-                warn!("No configuration data found at {addr:?}");
-                AppendedConfigType::NotFound
-            }
             Err(e) => {
-                error!("Invalid configuration data at {addr:?}: {e}");
-                AppendedConfigType::Invalid
+                error!("Invalid configuration data at {data_ptr:?}: {e}");
+                None
             }
-            Ok(_) => AppendedConfigType::Valid,
         }
     }
 
diff --git a/pvmfw/src/fdt.rs b/pvmfw/src/fdt.rs
index 4d282a1..7e7d641 100644
--- a/pvmfw/src/fdt.rs
+++ b/pvmfw/src/fdt.rs
@@ -199,11 +199,16 @@
     opp_node: FdtNode,
 ) -> libfdt::Result<ArrayVec<[u64; CpuInfo::MAX_OPPTABLES]>> {
     let mut table = ArrayVec::new();
-    for subnode in opp_node.subnodes()? {
+    let mut opp_nodes = opp_node.subnodes()?;
+    for subnode in opp_nodes.by_ref().take(table.capacity()) {
         let prop = subnode.getprop_u64(cstr!("opp-hz"))?.ok_or(FdtError::NotFound)?;
         table.push(prop);
     }
 
+    if opp_nodes.next().is_some() {
+        warn!("OPP table has more than {} entries: discarding extra nodes.", table.capacity());
+    }
+
     Ok(table)
 }
 
diff --git a/service_vm/test_apk/src/java/com/android/virt/rkpd/vm_attestation/testapp/RkpdVmAttestationTest.java b/service_vm/test_apk/src/java/com/android/virt/rkpd/vm_attestation/testapp/RkpdVmAttestationTest.java
index 678e56f..f456cb4 100644
--- a/service_vm/test_apk/src/java/com/android/virt/rkpd/vm_attestation/testapp/RkpdVmAttestationTest.java
+++ b/service_vm/test_apk/src/java/com/android/virt/rkpd/vm_attestation/testapp/RkpdVmAttestationTest.java
@@ -95,6 +95,11 @@
         if (mGki == null) {
             // We don't need this permission to use the microdroid kernel.
             revokePermission(VirtualMachine.USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION);
+        } else {
+            // The permission is needed to use the GKI kernel.
+            // Granting the permission is needed as the microdroid kernel test setup
+            // can revoke the permission before the GKI kernel test.
+            grantPermission(VirtualMachine.USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION);
         }
         prepareTestSetup(true /* protectedVm */, mGki);
         setMaxPerformanceTaskProfile();
diff --git a/tests/helper/src/java/com/android/microdroid/test/device/MicrodroidDeviceTestBase.java b/tests/helper/src/java/com/android/microdroid/test/device/MicrodroidDeviceTestBase.java
index f881909..364e769 100644
--- a/tests/helper/src/java/com/android/microdroid/test/device/MicrodroidDeviceTestBase.java
+++ b/tests/helper/src/java/com/android/microdroid/test/device/MicrodroidDeviceTestBase.java
@@ -67,7 +67,8 @@
 
     protected static final String KERNEL_VERSION = SystemProperties.get("ro.kernel.version");
     protected static final Set<String> SUPPORTED_GKI_VERSIONS =
-            Collections.unmodifiableSet(new HashSet(Arrays.asList("android14-6.1")));
+            Collections.unmodifiableSet(
+                    new HashSet(Arrays.asList("android14-6.1-pkvm_experimental")));
 
     public static boolean isCuttlefish() {
         return getDeviceProperties().isCuttlefish();
diff --git a/tests/hostside/helper/java/com/android/microdroid/test/host/MicrodroidHostTestCaseBase.java b/tests/hostside/helper/java/com/android/microdroid/test/host/MicrodroidHostTestCaseBase.java
index 1e81172..203bcae 100644
--- a/tests/hostside/helper/java/com/android/microdroid/test/host/MicrodroidHostTestCaseBase.java
+++ b/tests/hostside/helper/java/com/android/microdroid/test/host/MicrodroidHostTestCaseBase.java
@@ -69,7 +69,8 @@
                 / MICRODROID_COMMAND_RETRY_INTERVAL_MILLIS);
 
     protected static final Set<String> SUPPORTED_GKI_VERSIONS =
-            Collections.unmodifiableSet(new HashSet(Arrays.asList("android14-6.1")));
+            Collections.unmodifiableSet(
+                    new HashSet(Arrays.asList("android14-6.1-pkvm_experimental")));
 
     /* Keep this sync with AssignableDevice.aidl */
     public static final class AssignableDevice {
diff --git a/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java b/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
index 2b5c564..e7e9ded 100644
--- a/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
+++ b/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
@@ -174,7 +174,7 @@
             boolean updateBootconfigs) {
         File signVirtApex = findTestFile("sign_virt_apex");
 
-        RunUtil runUtil = new RunUtil();
+        RunUtil runUtil = createRunUtil();
         // Set the parent dir on the PATH (e.g. <workdir>/bin)
         String separator = System.getProperty("path.separator");
         String path = signVirtApex.getParentFile().getPath() + separator + System.getenv("PATH");
@@ -409,7 +409,7 @@
                         configPath);
 
         PipedInputStream pis = new PipedInputStream();
-        Process process = RunUtil.getDefault().runCmdInBackground(args, new PipedOutputStream(pis));
+        Process process = createRunUtil().runCmdInBackground(args, new PipedOutputStream(pis));
         return new VmInfo(process);
     }
 
@@ -890,7 +890,7 @@
             File sepolicyAnalyzeBin = findTestFile("sepolicy-analyze");
 
             CommandResult result =
-                    RunUtil.getDefault()
+                    createRunUtil()
                             .runTimedCmd(
                                     10000,
                                     sepolicyAnalyzeBin.getPath(),
@@ -1034,14 +1034,14 @@
     private boolean isLz4(String path) throws Exception {
         File lz4tool = findTestFile("lz4");
         CommandResult result =
-                new RunUtil().runTimedCmd(5000, lz4tool.getAbsolutePath(), "-t", path);
+                createRunUtil().runTimedCmd(5000, lz4tool.getAbsolutePath(), "-t", path);
         return result.getStatus() == CommandStatus.SUCCESS;
     }
 
     private void decompressLz4(String inputPath, String outputPath) throws Exception {
         File lz4tool = findTestFile("lz4");
         CommandResult result =
-                new RunUtil()
+                createRunUtil()
                         .runTimedCmd(
                                 5000, lz4tool.getAbsolutePath(), "-d", "-f", inputPath, outputPath);
         String out = result.getStdout();
@@ -1072,7 +1072,7 @@
         List<String> command =
                 Arrays.asList(avbtool.getAbsolutePath(), "info_image", "--image", image_path);
         CommandResult result =
-                new RunUtil().runTimedCmd(5000, "/bin/bash", "-c", String.join(" ", command));
+                createRunUtil().runTimedCmd(5000, "/bin/bash", "-c", String.join(" ", command));
         String out = result.getStdout();
         String err = result.getStderr();
         assertWithMessage(
@@ -1241,4 +1241,14 @@
         assertThat(androidDevice).isNotNull();
         return androidDevice;
     }
+
+    // The TradeFed Dockerfile sets LD_LIBRARY_PATH to a directory with an older libc++.so, which
+    // breaks binaries that are linked against a newer libc++.so. Binaries commonly use DT_RUNPATH
+    // to find an adjacent libc++.so (e.g. `$ORIGIN/../lib64`), but LD_LIBRARY_PATH overrides
+    // DT_RUNPATH, so clear LD_LIBRARY_PATH. See b/332593805 and b/333782216.
+    private static RunUtil createRunUtil() {
+        RunUtil runUtil = new RunUtil();
+        runUtil.unsetEnvVariable("LD_LIBRARY_PATH");
+        return runUtil;
+    }
 }
diff --git a/virtualizationmanager/src/aidl.rs b/virtualizationmanager/src/aidl.rs
index c5f1ab7..0c4aa7c 100644
--- a/virtualizationmanager/src/aidl.rs
+++ b/virtualizationmanager/src/aidl.rs
@@ -89,7 +89,7 @@
 use std::path::{Path, PathBuf};
 use std::sync::{Arc, Mutex, Weak};
 use vbmeta::VbMetaImage;
-use vmconfig::VmConfig;
+use vmconfig::{VmConfig, get_debug_level};
 use vsock::VsockStream;
 use zip::ZipArchive;
 
@@ -751,6 +751,9 @@
         InputDevice::EvDev(evdev) => InputDeviceOption::EvDev(clone_file(
             evdev.pfd.as_ref().ok_or(anyhow!("pfd should have value"))?,
         )?),
+        InputDevice::Keyboard(keyboard) => InputDeviceOption::Keyboard(clone_file(
+            keyboard.pfd.as_ref().ok_or(anyhow!("pfd should have value"))?,
+        )?),
     })
 }
 /// Given the configuration for a disk image, assembles the `DiskFile` to pass to crosvm.
@@ -1356,17 +1359,12 @@
             .or_binder_exception(ExceptionCode::SECURITY);
     }
 
-    match config {
-        VirtualMachineConfig::RawConfig(_) => Ok(()),
-        VirtualMachineConfig::AppConfig(config) => {
-            if config.debugLevel != DebugLevel::FULL {
-                Err(anyhow!("Can't use gdb with non-debuggable VMs"))
-                    .or_binder_exception(ExceptionCode::SECURITY)
-            } else {
-                Ok(())
-            }
-        }
+    if get_debug_level(config) == Some(DebugLevel::NONE) {
+        return Err(anyhow!("Can't use gdb with non-debuggable VMs"))
+            .or_binder_exception(ExceptionCode::SECURITY);
     }
+
+    Ok(())
 }
 
 fn extract_instance_id(config: &VirtualMachineConfig) -> [u8; 64] {
diff --git a/virtualizationmanager/src/crosvm.rs b/virtualizationmanager/src/crosvm.rs
index 4be48a5..040e552 100644
--- a/virtualizationmanager/src/crosvm.rs
+++ b/virtualizationmanager/src/crosvm.rs
@@ -161,6 +161,7 @@
 pub enum InputDeviceOption {
     EvDev(File),
     SingleTouch { file: File, width: u32, height: u32, name: Option<String> },
+    Keyboard(File),
 }
 
 type VfioDevice = Strong<dyn IBoundDevice>;
@@ -976,12 +977,24 @@
     }
 
     if cfg!(paravirtualized_devices) {
+        // TODO(b/325929096): Need to set up network from the config
+        if rustutils::system_properties::read_bool("ro.crosvm.network.setup.done", false)
+            .unwrap_or(false)
+        {
+            command.arg("--net").arg("tap-name=crosvm_tap");
+        }
+    }
+
+    if cfg!(paravirtualized_devices) {
         for input_device_option in config.input_device_options.iter() {
             command.arg("--input");
             command.arg(match input_device_option {
                 InputDeviceOption::EvDev(file) => {
                     format!("evdev[path={}]", add_preserved_fd(&mut preserved_fds, file))
                 }
+                InputDeviceOption::Keyboard(file) => {
+                    format!("keyboard[path={}]", add_preserved_fd(&mut preserved_fds, file))
+                }
                 InputDeviceOption::SingleTouch { file, width, height, name } => format!(
                     "single-touch[path={},width={},height={}{}]",
                     add_preserved_fd(&mut preserved_fds, file),
diff --git a/virtualizationmanager/src/debug_config.rs b/virtualizationmanager/src/debug_config.rs
index 451d1c6..6b74353 100644
--- a/virtualizationmanager/src/debug_config.rs
+++ b/virtualizationmanager/src/debug_config.rs
@@ -26,6 +26,7 @@
 use std::fs;
 use std::io::ErrorKind;
 use std::path::{Path, PathBuf};
+use vmconfig::get_debug_level;
 
 const CUSTOM_DEBUG_POLICY_OVERLAY_SYSPROP: &str =
     "hypervisor.virtualizationmanager.debug_policy.path";
@@ -147,7 +148,7 @@
 }
 
 /// Debug configurations for both debug level and debug policy
-#[derive(Debug)]
+#[derive(Debug, Default)]
 pub struct DebugConfig {
     pub debug_level: DebugLevel,
     debug_policy_log: bool,
@@ -157,12 +158,15 @@
 
 impl DebugConfig {
     pub fn new(config: &VirtualMachineConfig) -> Self {
-        let debug_level = match config {
-            VirtualMachineConfig::AppConfig(config) => config.debugLevel,
-            _ => DebugLevel::NONE,
-        };
+        let debug_level = get_debug_level(config).unwrap_or(DebugLevel::NONE);
 
-        match system_properties::read(CUSTOM_DEBUG_POLICY_OVERLAY_SYSPROP).unwrap_or_default() {
+        let dp_sysprop = system_properties::read(CUSTOM_DEBUG_POLICY_OVERLAY_SYSPROP);
+        let custom_dp = dp_sysprop.unwrap_or_else(|e| {
+            warn!("Failed to read sysprop {CUSTOM_DEBUG_POLICY_OVERLAY_SYSPROP}: {e}");
+            Default::default()
+        });
+
+        match custom_dp {
             Some(path) if !path.is_empty() => {
                 match Self::from_custom_debug_overlay_policy(debug_level, Path::new(&path)) {
                     Ok(debug_config) => {
@@ -184,11 +188,6 @@
         }
 
         info!("Debug policy is disabled");
-        Self::new_with_debug_level(debug_level)
-    }
-
-    /// Creates a new DebugConfig with debug level. Only use this for test purpose.
-    pub fn new_with_debug_level(debug_level: DebugLevel) -> Self {
         Self {
             debug_level,
             debug_policy_log: false,
@@ -197,6 +196,12 @@
         }
     }
 
+    #[cfg(test)]
+    /// Creates a new DebugConfig with debug level. Only use this for test purpose.
+    pub(crate) fn new_with_debug_level(debug_level: DebugLevel) -> Self {
+        Self { debug_level, ..Default::default() }
+    }
+
     /// Get whether console output should be configred for VM to leave console and adb log.
     /// Caller should create pipe and prepare for receiving VM log with it.
     pub fn should_prepare_console_output(&self) -> bool {
@@ -214,15 +219,15 @@
     }
 
     fn from_custom_debug_overlay_policy(debug_level: DebugLevel, path: &Path) -> Result<Self> {
-        match OwnedFdt::from_overlay_onto_new_fdt(path) {
-            Ok(fdt) => Ok(Self {
-                debug_level,
-                debug_policy_log: get_fdt_prop_bool(fdt.as_fdt(), &DP_LOG_PATH)?,
-                debug_policy_ramdump: get_fdt_prop_bool(fdt.as_fdt(), &DP_RAMDUMP_PATH)?,
-                debug_policy_adb: get_fdt_prop_bool(fdt.as_fdt(), &DP_ADB_PATH)?,
-            }),
-            Err(err) => Err(err),
-        }
+        let owned_fdt = OwnedFdt::from_overlay_onto_new_fdt(path)?;
+        let fdt = owned_fdt.as_fdt();
+
+        Ok(Self {
+            debug_level,
+            debug_policy_log: get_fdt_prop_bool(fdt, &DP_LOG_PATH)?,
+            debug_policy_ramdump: get_fdt_prop_bool(fdt, &DP_RAMDUMP_PATH)?,
+            debug_policy_adb: get_fdt_prop_bool(fdt, &DP_ADB_PATH)?,
+        })
     }
 
     fn from_host(debug_level: DebugLevel) -> Result<Self> {
@@ -318,4 +323,18 @@
 
         Ok(())
     }
+
+    #[test]
+    fn test_new_with_debug_level() -> Result<()> {
+        assert_eq!(
+            DebugConfig::new_with_debug_level(DebugLevel::NONE).debug_level,
+            DebugLevel::NONE
+        );
+        assert_eq!(
+            DebugConfig::new_with_debug_level(DebugLevel::FULL).debug_level,
+            DebugLevel::FULL
+        );
+
+        Ok(())
+    }
 }
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice/InputDevice.aidl b/virtualizationservice/aidl/android/system/virtualizationservice/InputDevice.aidl
index fe12291..712d6a9 100644
--- a/virtualizationservice/aidl/android/system/virtualizationservice/InputDevice.aidl
+++ b/virtualizationservice/aidl/android/system/virtualizationservice/InputDevice.aidl
@@ -30,7 +30,11 @@
     parcelable EvDev {
         ParcelFileDescriptor pfd;
     }
-
+    // Keyboard input
+    parcelable Keyboard {
+        ParcelFileDescriptor pfd;
+    }
     SingleTouch singleTouch;
     EvDev evDev;
+    Keyboard keyboard;
 }
diff --git a/vm/src/run.rs b/vm/src/run.rs
index 07e0276..ca3e857 100644
--- a/vm/src/run.rs
+++ b/vm/src/run.rs
@@ -39,7 +39,7 @@
 use std::os::unix::io::{AsRawFd, FromRawFd};
 use std::path::{Path, PathBuf};
 use vmclient::{ErrorCode, VmInstance};
-use vmconfig::{open_parcel_file, VmConfig};
+use vmconfig::{get_debug_level, open_parcel_file, VmConfig};
 use zip::ZipArchive;
 
 /// Run a VM from the given APK, idsig, and config.
@@ -315,10 +315,8 @@
         .context("Failed to create VM")?;
     vm.start().context("Failed to start VM")?;
 
-    let debug_level = match config {
-        VirtualMachineConfig::AppConfig(config) => config.debugLevel,
-        _ => DebugLevel::NONE,
-    };
+    let debug_level = get_debug_level(config).unwrap_or(DebugLevel::NONE);
+
     println!(
         "Created {} from {} with CID {}, state is {}.",
         if debug_level == DebugLevel::FULL { "debuggable VM" } else { "VM" },
diff --git a/vmlauncher_app/AndroidManifest.xml b/vmlauncher_app/AndroidManifest.xml
index 607a895..d800ec7 100644
--- a/vmlauncher_app/AndroidManifest.xml
+++ b/vmlauncher_app/AndroidManifest.xml
@@ -4,9 +4,11 @@
 
     <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-feature android:name="android.software.virtualization_framework" android:required="true" />
     <application
-        android:label="VmLauncherApp">
+        android:label="VmLauncherApp"
+        android:networkSecurityConfig="@xml/network_security_config">
         <activity android:name=".MainActivity"
                   android:enabled="false"
                   android:screenOrientation="landscape"
diff --git a/vmlauncher_app/README.md b/vmlauncher_app/README.md
index 9175e57..0109f37 100644
--- a/vmlauncher_app/README.md
+++ b/vmlauncher_app/README.md
@@ -1,13 +1,16 @@
 # VM launcher app
 
-## Building & Installing
+## Building
 
-Add `VmLauncherApp` into `PRODUCT_PACKAGES` and then `m`
+This app is now part of the virt APEX.
 
-You can also explicitly grant or revoke the permission, e.g.
+## Enabling
+
+This app is disabled by default. To re-enable it, execute the following command.
+
 ```
-adb shell pm grant com.android.virtualization.vmlauncher android.permission.USE_CUSTOM_VIRTUAL_MACHINE
-adb shell pm grant com.android.virtualization.vmlauncher android.permission.MANAGE_VIRTUAL_MACHINE
+adb root
+adb shell pm enable com.android.virtualization.vmlauncher/.MainActivity
 ```
 
 ## Running
diff --git a/vmlauncher_app/java/com/android/virtualization/vmlauncher/MainActivity.java b/vmlauncher_app/java/com/android/virtualization/vmlauncher/MainActivity.java
index 450f4ed..4c42bb4 100644
--- a/vmlauncher_app/java/com/android/virtualization/vmlauncher/MainActivity.java
+++ b/vmlauncher_app/java/com/android/virtualization/vmlauncher/MainActivity.java
@@ -37,6 +37,7 @@
 import android.view.Display;
 import android.view.SurfaceHolder;
 import android.view.SurfaceView;
+import android.view.KeyEvent;
 import android.view.WindowManager;
 import android.view.WindowInsets;
 import android.view.WindowInsetsController;
@@ -127,6 +128,7 @@
 
             customImageConfigBuilder.setDisplayConfig(displayConfigBuilder.build());
             customImageConfigBuilder.useTouch(true);
+            customImageConfigBuilder.useKeyboard(true);
 
             configBuilder.setCustomImageConfig(customImageConfigBuilder.build());
 
@@ -137,6 +139,22 @@
     }
 
     @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        if (mVirtualMachine == null) {
+            return false;
+        }
+        return mVirtualMachine.sendKeyEvent(event);
+    }
+
+    @Override
+    public boolean onKeyUp(int keyCode, KeyEvent event) {
+        if (mVirtualMachine == null) {
+            return false;
+        }
+        return mVirtualMachine.sendKeyEvent(event);
+    }
+
+    @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         try {
diff --git a/vmlauncher_app/res/xml/network_security_config.xml b/vmlauncher_app/res/xml/network_security_config.xml
new file mode 100644
index 0000000..f27fa56
--- /dev/null
+++ b/vmlauncher_app/res/xml/network_security_config.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+
+<network-security-config>
+    <domain-config cleartextTrafficPermitted="true">
+        <domain includeSubdomains="true">localhost</domain>
+    </domain-config>
+</network-security-config>