Support multi-touch screen

Bug: 331179939
Test: pinch zoom
Change-Id: I76bb46a67607df8af0c83dce3113b6611e4fae9f
diff --git a/java/framework/src/android/system/virtualmachine/VirtualMachine.java b/java/framework/src/android/system/virtualmachine/VirtualMachine.java
index d1321f0..a203358 100644
--- a/java/framework/src/android/system/virtualmachine/VirtualMachine.java
+++ b/java/framework/src/android/system/virtualmachine/VirtualMachine.java
@@ -903,11 +903,11 @@
             if (vmConfig.getCustomImageConfig().useTouch()) {
                 ParcelFileDescriptor[] pfds = ParcelFileDescriptor.createSocketPair();
                 mTouchSock = pfds[0];
-                InputDevice.SingleTouch t = new InputDevice.SingleTouch();
+                InputDevice.MultiTouch t = new InputDevice.MultiTouch();
                 t.width = rawConfig.displayConfig.width;
                 t.height = rawConfig.displayConfig.height;
                 t.pfd = pfds[1];
-                inputDevices.add(InputDevice.singleTouch(t));
+                inputDevices.add(InputDevice.multiTouch(t));
             }
             if (vmConfig.getCustomImageConfig().useKeyboard()) {
                 ParcelFileDescriptor[] pfds = ParcelFileDescriptor.createSocketPair();
@@ -1061,7 +1061,7 @@
     }
 
     /** @hide */
-    public boolean sendSingleTouchEvent(MotionEvent event) {
+    public boolean sendMultiTouchEvent(MotionEvent event) {
         if (mTouchSock == null) {
             Log.d(TAG, "mTouchSock == null");
             return false;
@@ -1074,17 +1074,56 @@
         short ABS_X = 0x00;
         short ABS_Y = 0x01;
         short SYN_REPORT = 0x00;
+        short ABS_MT_SLOT = 0x2f;
+        short ABS_MT_POSITION_X = 0x35;
+        short ABS_MT_POSITION_Y = 0x36;
+        short ABS_MT_TRACKING_ID = 0x39;
 
-        int x = (int) event.getX();
-        int y = (int) event.getY();
-        boolean down = event.getAction() != MotionEvent.ACTION_UP;
+        switch (event.getActionMasked()) {
+            case MotionEvent.ACTION_MOVE:
+                List<InputEvent> events =
+                        new ArrayList<>(
+                                event.getPointerCount() * 6 /*InputEvent per a pointer*/
+                                        + 1 /*SYN*/);
+                for (int actionIdx = 0; actionIdx < event.getPointerCount(); actionIdx++) {
+                    int pointerId = event.getPointerId(actionIdx);
+                    int x = (int) event.getRawX(actionIdx);
+                    int y = (int) event.getRawY(actionIdx);
+                    events.add(new InputEvent(EV_ABS, ABS_MT_SLOT, pointerId));
+                    events.add(new InputEvent(EV_ABS, ABS_MT_TRACKING_ID, pointerId));
+                    events.add(new InputEvent(EV_ABS, ABS_MT_POSITION_X, x));
+                    events.add(new InputEvent(EV_ABS, ABS_MT_POSITION_Y, y));
+                    events.add(new InputEvent(EV_ABS, ABS_X, x));
+                    events.add(new InputEvent(EV_ABS, ABS_Y, y));
+                }
+                events.add(new InputEvent(EV_SYN, SYN_REPORT, 0));
+                return writeEventsToSock(mTouchSock, events);
+            case MotionEvent.ACTION_DOWN:
+            case MotionEvent.ACTION_POINTER_DOWN:
+            case MotionEvent.ACTION_UP:
+            case MotionEvent.ACTION_POINTER_UP:
+                break;
+            default:
+                return false;
+        }
 
+        boolean down =
+                event.getActionMasked() == MotionEvent.ACTION_DOWN
+                        || event.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN;
+        int actionIdx = event.getActionIndex();
+        int pointerId = event.getPointerId(actionIdx);
+        int x = (int) event.getRawX(actionIdx);
+        int y = (int) event.getRawY(actionIdx);
         return writeEventsToSock(
                 mTouchSock,
                 Arrays.asList(
+                        new InputEvent(EV_KEY, BTN_TOUCH, down ? 1 : 0),
+                        new InputEvent(EV_ABS, ABS_MT_SLOT, pointerId),
+                        new InputEvent(EV_ABS, ABS_MT_TRACKING_ID, down ? pointerId : -1),
+                        new InputEvent(EV_ABS, ABS_MT_POSITION_X, x),
+                        new InputEvent(EV_ABS, ABS_MT_POSITION_Y, y),
                         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)));
     }
 
diff --git a/virtualizationmanager/src/aidl.rs b/virtualizationmanager/src/aidl.rs
index 425beed..026834f 100644
--- a/virtualizationmanager/src/aidl.rs
+++ b/virtualizationmanager/src/aidl.rs
@@ -814,6 +814,12 @@
             width: u32::try_from(trackpad.width)?,
             name: if !trackpad.name.is_empty() { Some(trackpad.name.clone()) } else { None },
         },
+        InputDevice::MultiTouch(multi_touch) => InputDeviceOption::MultiTouch {
+            file: clone_file(multi_touch.pfd.as_ref().ok_or(anyhow!("pfd should have value"))?)?,
+            height: u32::try_from(multi_touch.height)?,
+            width: u32::try_from(multi_touch.width)?,
+            name: if !multi_touch.name.is_empty() { Some(multi_touch.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 379a498..6af84b6 100644
--- a/virtualizationmanager/src/crosvm.rs
+++ b/virtualizationmanager/src/crosvm.rs
@@ -223,6 +223,7 @@
     Mouse(File),
     Switches(File),
     MultiTouchTrackpad { file: File, width: u32, height: u32, name: Option<String> },
+    MultiTouch { file: File, width: u32, height: u32, name: Option<String> },
 }
 
 type VfioDevice = Strong<dyn IBoundDevice>;
@@ -1164,6 +1165,13 @@
                     height,
                     name.as_ref().map_or("".into(), |n| format!(",name={}", n))
                 ),
+                InputDeviceOption::MultiTouch { file, width, height, name } => format!(
+                    "multi-touch[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 e998d02..bb06fff 100644
--- a/virtualizationservice/aidl/android/system/virtualizationservice/InputDevice.aidl
+++ b/virtualizationservice/aidl/android/system/virtualizationservice/InputDevice.aidl
@@ -51,10 +51,20 @@
         int height = 1080;
         @utf8InCpp String name = "";
     }
+
+    parcelable MultiTouch {
+        ParcelFileDescriptor pfd;
+        // Default values come from https://crosvm.dev/book/devices/input.html#multi-touch
+        int width = 1280;
+        int height = 1080;
+        @utf8InCpp String name = "";
+    }
+
     SingleTouch singleTouch;
     EvDev evDev;
     Keyboard keyboard;
     Mouse mouse;
     Switches switches;
     Trackpad trackpad;
+    MultiTouch multiTouch;
 }
diff --git a/vmlauncher_app/java/com/android/virtualization/vmlauncher/MainActivity.java b/vmlauncher_app/java/com/android/virtualization/vmlauncher/MainActivity.java
index 7a69c07..6f85c42 100644
--- a/vmlauncher_app/java/com/android/virtualization/vmlauncher/MainActivity.java
+++ b/vmlauncher_app/java/com/android/virtualization/vmlauncher/MainActivity.java
@@ -352,7 +352,7 @@
                     if (mVirtualMachine == null) {
                         return false;
                     }
-                    return mVirtualMachine.sendSingleTouchEvent(event);
+                    return mVirtualMachine.sendMultiTouchEvent(event);
                 });
         surfaceView.requestUnbufferedDispatch(InputDevice.SOURCE_ANY);
         surfaceView.setOnCapturedPointerListener(