Support trackpad in VM

It handles single touch and tap event for now.

Bug: 347253952
Test: add trackpad, and then check if it works
Change-Id: Ifec4219036b4baa66c84faffa9d3b22f99fdddd4
diff --git a/java/framework/src/android/system/virtualmachine/VirtualMachine.java b/java/framework/src/android/system/virtualmachine/VirtualMachine.java
index f5ce102..d54bd44 100644
--- a/java/framework/src/android/system/virtualmachine/VirtualMachine.java
+++ b/java/framework/src/android/system/virtualmachine/VirtualMachine.java
@@ -173,6 +173,7 @@
     private ParcelFileDescriptor mKeySock;
     private ParcelFileDescriptor mMouseSock;
     private ParcelFileDescriptor mSwitchesSock;
+    private ParcelFileDescriptor mTrackpadSock;
 
     /**
      * Status of a virtual machine
@@ -929,6 +930,16 @@
                 s.pfd = pfds[1];
                 inputDevices.add(InputDevice.switches(s));
             }
+            if (vmConfig.getCustomImageConfig().useTrackpad()) {
+                ParcelFileDescriptor[] pfds = ParcelFileDescriptor.createSocketPair();
+                mTrackpadSock = pfds[0];
+                InputDevice.Trackpad t = new InputDevice.Trackpad();
+                // TODO(b/347253952): make it configurable
+                t.width = 2380;
+                t.height = 1369;
+                t.pfd = pfds[1];
+                inputDevices.add(InputDevice.trackpad(t));
+            }
         }
         rawConfig.inputDevices = inputDevices.toArray(new InputDevice[0]);
 
@@ -1096,6 +1107,65 @@
                         new InputEvent(EV_SYN, SYN_REPORT, 0)));
     }
 
+    /** @hide */
+    public boolean sendTrackpadEvent(MotionEvent event) {
+        if (mTrackpadSock == null) {
+            Log.d(TAG, "mTrackpadSock == null");
+            return false;
+        }
+        // from include/uapi/linux/input-event-codes.h in the kernel.
+        short EV_SYN = 0x00;
+        short EV_ABS = 0x03;
+        short EV_KEY = 0x01;
+        short BTN_TOUCH = 0x14a;
+        short BTN_TOOL_FINGER = 0x145;
+        short ABS_X = 0x00;
+        short ABS_Y = 0x01;
+        short SYN_REPORT = 0x00;
+        short ABS_MT_SLOT = 0x2f;
+        short ABS_MT_TOUCH_MAJOR = 0x30;
+        short ABS_MT_TOUCH_MINOR = 0x31;
+        short ABS_MT_WIDTH_MAJOR = 0x32;
+        short ABS_MT_WIDTH_MINOR = 0x33;
+        short ABS_MT_ORIENTATION = 0x34;
+        short ABS_MT_POSITION_X = 0x35;
+        short ABS_MT_POSITION_Y = 0x36;
+        short ABS_MT_TOOL_TYPE = 0x37;
+        short ABS_MT_BLOB_ID = 0x38;
+        short ABS_MT_TRACKING_ID = 0x39;
+        short ABS_MT_PRESSURE = 0x3a;
+        short ABS_MT_DISTANCE = 0x3b;
+        short ABS_MT_TOOL_X = 0x3c;
+        short ABS_MT_TOOL_Y = 0x3d;
+        short ABS_PRESSURE = 0x18;
+        short ABS_TOOL_WIDTH = 0x1c;
+
+        int x = (int) event.getRawX();
+        int y = (int) event.getRawY();
+        boolean down = event.getAction() != MotionEvent.ACTION_UP;
+
+        // TODO(b/347253952): support multi-touch and button click
+        return writeEventsToSock(
+                mTrackpadSock,
+                Arrays.asList(
+                        new InputEvent(EV_KEY, BTN_TOUCH, down ? 1 : 0),
+                        new InputEvent(EV_KEY, BTN_TOOL_FINGER, down ? 1 : 0),
+                        new InputEvent(EV_ABS, ABS_MT_SLOT, 0),
+                        new InputEvent(
+                                EV_ABS, ABS_MT_TRACKING_ID, down ? event.getPointerId(0) : -1),
+                        new InputEvent(EV_ABS, ABS_MT_TOOL_TYPE, 0 /* MT_TOOL_FINGER */),
+                        new InputEvent(EV_ABS, ABS_MT_POSITION_X, x),
+                        new InputEvent(EV_ABS, ABS_MT_POSITION_Y, y),
+                        new InputEvent(EV_ABS, ABS_MT_TOUCH_MAJOR, (short) event.getTouchMajor()),
+                        new InputEvent(EV_ABS, ABS_MT_TOUCH_MINOR, (short) event.getTouchMinor()),
+                        new InputEvent(EV_ABS, ABS_X, x),
+                        new InputEvent(EV_ABS, ABS_Y, y),
+                        new InputEvent(EV_ABS, ABS_PRESSURE, (short) (255 * event.getPressure())),
+                        new InputEvent(
+                                EV_ABS, ABS_MT_PRESSURE, (short) (255 * event.getPressure())),
+                        new InputEvent(EV_SYN, SYN_REPORT, 0)));
+    }
+
     private boolean writeEventsToSock(ParcelFileDescriptor sock, List<InputEvent> evtList) {
         ByteBuffer byteBuffer =
                 ByteBuffer.allocate(8 /* (type: u16 + code: u16 + value: i32) */ * evtList.size());
diff --git a/java/framework/src/android/system/virtualmachine/VirtualMachineCustomImageConfig.java b/java/framework/src/android/system/virtualmachine/VirtualMachineCustomImageConfig.java
index 2a571ff..3a1c784 100644
--- a/java/framework/src/android/system/virtualmachine/VirtualMachineCustomImageConfig.java
+++ b/java/framework/src/android/system/virtualmachine/VirtualMachineCustomImageConfig.java
@@ -44,6 +44,7 @@
     private static final String KEY_NETWORK = "network";
     private static final String KEY_GPU = "gpu";
     private static final String KEY_AUDIO_CONFIG = "audio_config";
+    private static final String KEY_TRACKPAD = "trackpad";
 
     @Nullable private final String name;
     @Nullable private final String kernelPath;
@@ -59,6 +60,7 @@
     private final boolean switches;
     private final boolean network;
     @Nullable private final GpuConfig gpuConfig;
+    private final boolean trackpad;
 
     @Nullable
     public Disk[] getDisks() {
@@ -106,6 +108,10 @@
         return switches;
     }
 
+    public boolean useTrackpad() {
+        return mouse;
+    }
+
     public boolean useNetwork() {
         return network;
     }
@@ -125,7 +131,8 @@
             boolean switches,
             boolean network,
             GpuConfig gpuConfig,
-            AudioConfig audioConfig) {
+            AudioConfig audioConfig,
+            boolean trackpad) {
         this.name = name;
         this.kernelPath = kernelPath;
         this.initrdPath = initrdPath;
@@ -140,6 +147,7 @@
         this.network = network;
         this.gpuConfig = gpuConfig;
         this.audioConfig = audioConfig;
+        this.trackpad = trackpad;
     }
 
     static VirtualMachineCustomImageConfig from(PersistableBundle customImageConfigBundle) {
@@ -190,6 +198,7 @@
         PersistableBundle audioConfigPb =
                 customImageConfigBundle.getPersistableBundle(KEY_AUDIO_CONFIG);
         builder.setAudioConfig(AudioConfig.from(audioConfigPb));
+        builder.useTrackpad(customImageConfigBundle.getBoolean(KEY_TRACKPAD));
         return builder.build();
     }
 
@@ -248,6 +257,7 @@
         pb.putPersistableBundle(
                 KEY_AUDIO_CONFIG,
                 Optional.ofNullable(audioConfig).map(ac -> ac.toPersistableBundle()).orElse(null));
+        pb.putBoolean(KEY_TRACKPAD, trackpad);
         return pb;
     }
 
@@ -341,6 +351,7 @@
         private boolean switches;
         private boolean network;
         private GpuConfig gpuConfig;
+        private boolean trackpad;
 
         /** @hide */
         public Builder() {}
@@ -418,6 +429,12 @@
         }
 
         /** @hide */
+        public Builder useTrackpad(boolean trackpad) {
+            this.trackpad = trackpad;
+            return this;
+        }
+
+        /** @hide */
         public Builder useNetwork(boolean network) {
             this.network = network;
             return this;
@@ -445,7 +462,8 @@
                     switches,
                     network,
                     gpuConfig,
-                    audioConfig);
+                    audioConfig,
+                    trackpad);
         }
     }
 
diff --git a/virtualizationmanager/src/aidl.rs b/virtualizationmanager/src/aidl.rs
index 671a012..425beed 100644
--- a/virtualizationmanager/src/aidl.rs
+++ b/virtualizationmanager/src/aidl.rs
@@ -808,6 +808,12 @@
         InputDevice::Switches(switches) => InputDeviceOption::Switches(clone_file(
             switches.pfd.as_ref().ok_or(anyhow!("pfd should have value"))?,
         )?),
+        InputDevice::Trackpad(trackpad) => InputDeviceOption::MultiTouchTrackpad {
+            file: clone_file(trackpad.pfd.as_ref().ok_or(anyhow!("pfd should have value"))?)?,
+            height: u32::try_from(trackpad.height)?,
+            width: u32::try_from(trackpad.width)?,
+            name: if !trackpad.name.is_empty() { Some(trackpad.name.clone()) } else { None },
+        },
     })
 }
 /// 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 a9a91fe..1998ef7 100644
--- a/virtualizationmanager/src/crosvm.rs
+++ b/virtualizationmanager/src/crosvm.rs
@@ -222,6 +222,7 @@
     Keyboard(File),
     Mouse(File),
     Switches(File),
+    MultiTouchTrackpad { file: File, width: u32, height: u32, name: Option<String> },
 }
 
 type VfioDevice = Strong<dyn IBoundDevice>;
@@ -1154,6 +1155,13 @@
                 InputDeviceOption::Switches(file) => {
                     format!("switches[path={}]", add_preserved_fd(&mut preserved_fds, file))
                 }
+                InputDeviceOption::MultiTouchTrackpad { file, width, height, name } => format!(
+                    "multi-touch-trackpad[path={},width={},height={}{}]",
+                    add_preserved_fd(&mut preserved_fds, file),
+                    width,
+                    height,
+                    name.as_ref().map_or("".into(), |n| format!(",name={}", n))
+                ),
             });
         }
     }
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice/InputDevice.aidl b/virtualizationservice/aidl/android/system/virtualizationservice/InputDevice.aidl
index 5a7ed4a..e998d02 100644
--- a/virtualizationservice/aidl/android/system/virtualizationservice/InputDevice.aidl
+++ b/virtualizationservice/aidl/android/system/virtualizationservice/InputDevice.aidl
@@ -44,9 +44,17 @@
         ParcelFileDescriptor pfd;
     }
 
+    parcelable Trackpad {
+        ParcelFileDescriptor pfd;
+        // Default values come from https://crosvm.dev/book/devices/input.html#trackpad
+        int width = 1280;
+        int height = 1080;
+        @utf8InCpp String name = "";
+    }
     SingleTouch singleTouch;
     EvDev evDev;
     Keyboard keyboard;
     Mouse mouse;
     Switches switches;
+    Trackpad trackpad;
 }
diff --git a/vmlauncher_app/java/com/android/virtualization/vmlauncher/MainActivity.java b/vmlauncher_app/java/com/android/virtualization/vmlauncher/MainActivity.java
index ef94be5..c3dc189 100644
--- a/vmlauncher_app/java/com/android/virtualization/vmlauncher/MainActivity.java
+++ b/vmlauncher_app/java/com/android/virtualization/vmlauncher/MainActivity.java
@@ -219,6 +219,7 @@
             customImageConfigBuilder.useKeyboard(true);
             customImageConfigBuilder.useMouse(true);
             customImageConfigBuilder.useSwitches(true);
+            customImageConfigBuilder.useTrackpad(true);
             customImageConfigBuilder.useNetwork(true);
 
             AudioConfig.Builder audioConfigBuilder = new AudioConfig.Builder();
@@ -359,6 +360,10 @@
                     if (mVirtualMachine == null) {
                         return false;
                     }
+                    int eventSource = event.getSource();
+                    if ((eventSource & InputDevice.SOURCE_CLASS_POSITION) != 0) {
+                        return mVirtualMachine.sendTrackpadEvent(event);
+                    }
                     return mVirtualMachine.sendMouseEvent(event);
                 });
         surfaceView