Implement basic keyboard input

It doesn't support some key combination, but basically works.
It might be an interim solution for a while until evdev is avilable.
And also refactored the code about sending evts to the socket

Bug: 325930433
Bug: 331191129
Test: connect keyboard
Change-Id: I4fb0f62fd705b52b6557737a70575f9769f864e0
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/virtualizationmanager/src/aidl.rs b/virtualizationmanager/src/aidl.rs
index c5f1ab7..99a0078 100644
--- a/virtualizationmanager/src/aidl.rs
+++ b/virtualizationmanager/src/aidl.rs
@@ -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.
diff --git a/virtualizationmanager/src/crosvm.rs b/virtualizationmanager/src/crosvm.rs
index 4be48a5..d5b9e03 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>;
@@ -982,6 +983,9 @@
                 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/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/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 {