Implement single touch for custom vm

Bug: 331191129
Test: check if touch works
Change-Id: I485d9be6741165a9a03f55c96be50a5d64790d8d
diff --git a/java/framework/src/android/system/virtualmachine/VirtualMachine.java b/java/framework/src/android/system/virtualmachine/VirtualMachine.java
index cc126eb..51b91ca 100644
--- a/java/framework/src/android/system/virtualmachine/VirtualMachine.java
+++ b/java/framework/src/android/system/virtualmachine/VirtualMachine.java
@@ -63,11 +63,13 @@
 import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
 import android.os.ServiceSpecificException;
+import android.view.MotionEvent;
 import android.system.virtualizationcommon.DeathReason;
 import android.system.virtualizationcommon.ErrorCode;
 import android.system.virtualizationservice.IVirtualMachine;
 import android.system.virtualizationservice.IVirtualMachineCallback;
 import android.system.virtualizationservice.IVirtualizationService;
+import android.system.virtualizationservice.InputDevice;
 import android.system.virtualizationservice.MemoryTrimLevel;
 import android.system.virtualizationservice.PartitionType;
 import android.system.virtualizationservice.VirtualMachineAppConfig;
@@ -79,6 +81,8 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.system.virtualmachine.flags.Flags;
 
+import libcore.io.IoBridge;
+
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
@@ -89,6 +93,8 @@
 import java.io.OutputStream;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
 import java.nio.channels.FileChannel;
 import java.nio.file.FileAlreadyExistsException;
 import java.nio.file.FileVisitResult;
@@ -154,6 +160,8 @@
     @SuppressLint("MinMaxConstant") // Won't change: see man 7 vsock.
     public static final long MAX_VSOCK_PORT = (1L << 32) - 1;
 
+    private ParcelFileDescriptor mTouchSock;
+
     /**
      * Status of a virtual machine
      *
@@ -849,9 +857,73 @@
             createVirtualMachineConfigForRawFrom(VirtualMachineConfig vmConfig)
                     throws IllegalStateException, IOException {
         VirtualMachineRawConfig rawConfig = vmConfig.toVsRawConfig();
+
+        // 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));
+        }
+        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);
+    }
+
+    /** @hide */
+    public boolean sendSingleTouchEvent(MotionEvent event) {
+        if (mTouchSock == null) {
+            Log.d(TAG, "mTouchSock == 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 ABS_X = 0x00;
+        short ABS_Y = 0x01;
+        short SYN_REPORT = 0x00;
+
+        int x = (int) event.getX();
+        int y = (int) event.getY();
+        boolean down = event.getAction() != MotionEvent.ACTION_UP;
+
+        ByteBuffer byteBuffer =
+                ByteBuffer.allocate(32 /* (type: u16 + code: u16 + value: i32) * 4 */);
+        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);
+
+        try {
+            IoBridge.write(
+                    mTouchSock.getFileDescriptor(),
+                    byteBuffer.array(),
+                    0,
+                    byteBuffer.array().length);
+        } catch (IOException e) {
+            Log.d(TAG, "cannot send touch evt", e);
+            return false;
+        }
+        return true;
+    }
+
     private android.system.virtualizationservice.VirtualMachineConfig
             createVirtualMachineConfigForAppFrom(
                     VirtualMachineConfig vmConfig, IVirtualizationService service)
diff --git a/java/framework/src/android/system/virtualmachine/VirtualMachineConfig.java b/java/framework/src/android/system/virtualmachine/VirtualMachineConfig.java
index d267763..a8f318c 100644
--- a/java/framework/src/android/system/virtualmachine/VirtualMachineConfig.java
+++ b/java/framework/src/android/system/virtualmachine/VirtualMachineConfig.java
@@ -40,7 +40,6 @@
 import android.sysprop.HypervisorProperties;
 import android.system.virtualizationservice.DiskImage;
 import android.system.virtualizationservice.Partition;
-import android.system.virtualizationservice.InputDevice;
 import android.system.virtualizationservice.VirtualMachineAppConfig;
 import android.system.virtualizationservice.VirtualMachinePayloadConfig;
 import android.system.virtualizationservice.VirtualMachineRawConfig;
@@ -651,7 +650,6 @@
         config.cpuTopology = (byte) this.mCpuTopology;
         config.devices = EMPTY_STRING_ARRAY;
         config.platformVersion = "~1.0";
-        config.inputDevices = new InputDevice[0];
         return config;
     }
 
diff --git a/java/framework/src/android/system/virtualmachine/VirtualMachineCustomImageConfig.java b/java/framework/src/android/system/virtualmachine/VirtualMachineCustomImageConfig.java
index 89df1f2..7cf5893 100644
--- a/java/framework/src/android/system/virtualmachine/VirtualMachineCustomImageConfig.java
+++ b/java/framework/src/android/system/virtualmachine/VirtualMachineCustomImageConfig.java
@@ -34,6 +34,7 @@
     private static final String KEY_DISK_WRITABLES = "disk_writables";
     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";
 
     @Nullable private final String name;
     @NonNull private final String kernelPath;
@@ -42,6 +43,7 @@
     @Nullable private final String[] params;
     @Nullable private final Disk[] disks;
     @Nullable private final DisplayConfig displayConfig;
+    private final boolean touch;
 
     @Nullable
     public Disk[] getDisks() {
@@ -73,6 +75,10 @@
         return params;
     }
 
+    public boolean useTouch() {
+        return touch;
+    }
+
     /** @hide */
     public VirtualMachineCustomImageConfig(
             String name,
@@ -81,7 +87,8 @@
             String bootloaderPath,
             String[] params,
             Disk[] disks,
-            DisplayConfig displayConfig) {
+            DisplayConfig displayConfig,
+            boolean touch) {
         this.name = name;
         this.kernelPath = kernelPath;
         this.initrdPath = initrdPath;
@@ -89,6 +96,7 @@
         this.params = params;
         this.disks = disks;
         this.displayConfig = displayConfig;
+        this.touch = touch;
     }
 
     static VirtualMachineCustomImageConfig from(PersistableBundle customImageConfigBundle) {
@@ -116,7 +124,7 @@
         PersistableBundle displayConfigPb =
                 customImageConfigBundle.getPersistableBundle(KEY_DISPLAY_CONFIG);
         builder.setDisplayConfig(DisplayConfig.from(displayConfigPb));
-
+        builder.useTouch(customImageConfigBundle.getBoolean(KEY_TOUCH));
         return builder.build();
     }
 
@@ -145,6 +153,7 @@
                 Optional.ofNullable(displayConfig)
                         .map(dc -> dc.toPersistableBundle())
                         .orElse(null));
+        pb.putBoolean(KEY_TOUCH, touch);
         return pb;
     }
 
@@ -193,6 +202,7 @@
         private List<String> params = new ArrayList<>();
         private List<Disk> disks = new ArrayList<>();
         private DisplayConfig displayConfig;
+        private boolean touch;
 
         /** @hide */
         public Builder() {}
@@ -240,6 +250,12 @@
         }
 
         /** @hide */
+        public Builder useTouch(boolean touch) {
+            this.touch = touch;
+            return this;
+        }
+
+        /** @hide */
         public VirtualMachineCustomImageConfig build() {
             return new VirtualMachineCustomImageConfig(
                     this.name,
@@ -248,7 +264,8 @@
                     this.bootloaderPath,
                     this.params.toArray(new String[0]),
                     this.disks.toArray(new Disk[0]),
-                    displayConfig);
+                    displayConfig,
+                    touch);
         }
     }
 
diff --git a/vmlauncher_app/java/com/android/virtualization/vmlauncher/MainActivity.java b/vmlauncher_app/java/com/android/virtualization/vmlauncher/MainActivity.java
index b5995b8..450f4ed 100644
--- a/vmlauncher_app/java/com/android/virtualization/vmlauncher/MainActivity.java
+++ b/vmlauncher_app/java/com/android/virtualization/vmlauncher/MainActivity.java
@@ -126,6 +126,8 @@
             }
 
             customImageConfigBuilder.setDisplayConfig(displayConfigBuilder.build());
+            customImageConfigBuilder.useTouch(true);
+
             configBuilder.setCustomImageConfig(customImageConfigBuilder.build());
 
         } catch (JSONException | IOException e) {
@@ -224,6 +226,13 @@
         }
 
         SurfaceView surfaceView = findViewById(R.id.surface_view);
+        surfaceView.setOnTouchListener(
+                (v, event) -> {
+                    if (mVirtualMachine == null) {
+                        return false;
+                    }
+                    return mVirtualMachine.sendSingleTouchEvent(event);
+                });
         surfaceView
                 .getHolder()
                 .addCallback(