Support cursor inside vm in VmLauncherApp

1. cursor size is always 64x64 according to virtio-gpu spec
2. create a surfaceview of which size is 64x64
3. pass the surfaceview to crosvm as a cursor surface
4. receive set_position event, and then move the view properly

Bug: 327559087
Test: check cursor in vm
Change-Id: I862a90d1703f0eaf068fbc91d66ea35f2645e2aa
diff --git a/libs/android_display_backend/aidl/android/crosvm/ICrosvmAndroidDisplayService.aidl b/libs/android_display_backend/aidl/android/crosvm/ICrosvmAndroidDisplayService.aidl
index c7bfc80..e42cdd1 100644
--- a/libs/android_display_backend/aidl/android/crosvm/ICrosvmAndroidDisplayService.aidl
+++ b/libs/android_display_backend/aidl/android/crosvm/ICrosvmAndroidDisplayService.aidl
@@ -16,6 +16,7 @@
 
 package android.crosvm;
 
+import android.os.ParcelFileDescriptor;
 import android.view.Surface;
 
 /**
@@ -23,7 +24,7 @@
  * display.
  */
 interface ICrosvmAndroidDisplayService {
-    void setSurface(inout Surface surface);
-
-    void removeSurface();
+    void setSurface(inout Surface surface, boolean forCursor);
+    void setCursorStream(in ParcelFileDescriptor stream);
+    void removeSurface(boolean forCursor);
 }
diff --git a/libs/android_display_backend/crosvm_android_display_client.cpp b/libs/android_display_backend/crosvm_android_display_client.cpp
index 66320f3..0557127 100644
--- a/libs/android_display_backend/crosvm_android_display_client.cpp
+++ b/libs/android_display_backend/crosvm_android_display_client.cpp
@@ -35,34 +35,54 @@
     DisplayService() = default;
     virtual ~DisplayService() = default;
 
-    ndk::ScopedAStatus setSurface(Surface* surface) override {
+    ndk::ScopedAStatus setSurface(Surface* surface, bool forCursor) override {
         {
             std::lock_guard lk(mSurfaceReadyMutex);
-            mSurface = std::make_unique<Surface>(surface->release());
+            if (forCursor) {
+                mCursorSurface = std::make_unique<Surface>(surface->release());
+            } else {
+                mSurface = std::make_unique<Surface>(surface->release());
+            }
         }
-        mSurfaceReady.notify_one();
+        mSurfaceReady.notify_all();
         return ::ndk::ScopedAStatus::ok();
     }
 
-    ndk::ScopedAStatus removeSurface() override {
+    ndk::ScopedAStatus removeSurface(bool forCursor) override {
         {
             std::lock_guard lk(mSurfaceReadyMutex);
-            mSurface = nullptr;
+            if (forCursor) {
+                mCursorSurface = nullptr;
+            } else {
+                mSurface = nullptr;
+            }
         }
-        mSurfaceReady.notify_one();
+        mSurfaceReady.notify_all();
         return ::ndk::ScopedAStatus::ok();
     }
 
-    Surface* getSurface() {
+    Surface* getSurface(bool forCursor) {
         std::unique_lock lk(mSurfaceReadyMutex);
-        mSurfaceReady.wait(lk, [this] { return mSurface != nullptr; });
-        return mSurface.get();
+        if (forCursor) {
+            mSurfaceReady.wait(lk, [this] { return mCursorSurface != nullptr; });
+            return mCursorSurface.get();
+        } else {
+            mSurfaceReady.wait(lk, [this] { return mSurface != nullptr; });
+            return mSurface.get();
+        }
+    }
+    ndk::ScopedFileDescriptor& getCursorStream() { return mCursorStream; }
+    ndk::ScopedAStatus setCursorStream(const ndk::ScopedFileDescriptor& in_stream) {
+        mCursorStream = ndk::ScopedFileDescriptor(dup(in_stream.get()));
+        return ::ndk::ScopedAStatus::ok();
     }
 
 private:
     std::condition_variable mSurfaceReady;
     std::mutex mSurfaceReadyMutex;
     std::unique_ptr<Surface> mSurface;
+    std::unique_ptr<Surface> mCursorSurface;
+    ndk::ScopedFileDescriptor mCursorStream;
 };
 
 } // namespace
@@ -130,7 +150,7 @@
 }
 
 extern "C" ANativeWindow* create_android_surface(struct AndroidDisplayContext* ctx, uint32_t width,
-                                                 uint32_t height) {
+                                                 uint32_t height, bool for_cursor) {
     if (ctx->disp_service == nullptr) {
         ctx->errorf("Display service was not created");
         return nullptr;
@@ -139,7 +159,7 @@
     // where the SetScanoutBlob command is handled. Let's use BGRA not BGRX with a hope that we will
     // need alpha blending for the cursor surface.
     int format = HAL_PIXEL_FORMAT_BGRA_8888;
-    ANativeWindow* surface = ctx->disp_service->getSurface()->get(); // this can block
+    ANativeWindow* surface = ctx->disp_service->getSurface(for_cursor)->get(); // this can block
     if (ANativeWindow_setBuffersGeometry(surface, width, height, format) != 0) {
         ctx->errorf("Failed to set buffer gemoetry");
         return nullptr;
@@ -168,6 +188,21 @@
     return true;
 }
 
+extern "C" void set_android_surface_position(struct AndroidDisplayContext* ctx, uint32_t x,
+                                             uint32_t y) {
+    if (ctx->disp_service == nullptr) {
+        ctx->errorf("Display service was not created");
+        return;
+    }
+    auto fd = ctx->disp_service->getCursorStream().get();
+    if (fd == -1) {
+        ctx->errorf("Invalid fd");
+        return;
+    }
+    uint32_t pos[] = {x, y};
+    write(fd, pos, sizeof(pos));
+}
+
 extern "C" void post_android_surface_buffer(struct AndroidDisplayContext* ctx,
                                             ANativeWindow* surface) {
     if (ANativeWindow_unlockAndPost(surface) != 0) {
diff --git a/vmlauncher_app/java/com/android/virtualization/vmlauncher/MainActivity.java b/vmlauncher_app/java/com/android/virtualization/vmlauncher/MainActivity.java
index 521e2f1..4d79235 100644
--- a/vmlauncher_app/java/com/android/virtualization/vmlauncher/MainActivity.java
+++ b/vmlauncher_app/java/com/android/virtualization/vmlauncher/MainActivity.java
@@ -20,8 +20,10 @@
 
 import android.app.Activity;
 import android.crosvm.ICrosvmAndroidDisplayService;
+import android.graphics.PixelFormat;
 import android.graphics.Rect;
 import android.os.Bundle;
+import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.system.virtualizationservice_internal.IVirtualizationServiceInternal;
@@ -50,12 +52,16 @@
 import org.json.JSONException;
 import org.json.JSONObject;
 
+import libcore.io.IoBridge;
+
 import java.io.BufferedOutputStream;
 import java.io.BufferedReader;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.Arrays;
@@ -68,6 +74,7 @@
     private static final boolean DEBUG = true;
     private ExecutorService mExecutorService;
     private VirtualMachine mVirtualMachine;
+    private ParcelFileDescriptor mCursorStream;
 
     private VirtualMachineConfig createVirtualMachineConfig(String jsonPath) {
         VirtualMachineConfig.Builder configBuilder =
@@ -255,6 +262,8 @@
         }
 
         SurfaceView surfaceView = findViewById(R.id.surface_view);
+        SurfaceView cursorSurfaceView = findViewById(R.id.cursor_surface_view);
+        cursorSurfaceView.setZOrderMediaOverlay(true);
         View backgroundTouchView = findViewById(R.id.background_touch_view);
         backgroundTouchView.setOnTouchListener(
                 (v, event) -> {
@@ -288,7 +297,10 @@
                                                 + holder.getSurface()
                                                 + ")");
                                 runWithDisplayService(
-                                        (service) -> service.setSurface(holder.getSurface()));
+                                        (service) ->
+                                                service.setSurface(
+                                                        holder.getSurface(),
+                                                        false /* forCursor */));
                             }
 
                             @Override
@@ -300,7 +312,52 @@
                             @Override
                             public void surfaceDestroyed(SurfaceHolder holder) {
                                 Log.d(TAG, "ICrosvmAndroidDisplayService.removeSurface()");
-                                runWithDisplayService((service) -> service.removeSurface());
+                                runWithDisplayService(
+                                        (service) -> service.removeSurface(false /* forCursor */));
+                            }
+                        });
+        cursorSurfaceView.getHolder().setFormat(PixelFormat.RGBA_8888);
+        cursorSurfaceView
+                .getHolder()
+                .addCallback(
+                        new SurfaceHolder.Callback() {
+                            @Override
+                            public void surfaceCreated(SurfaceHolder holder) {
+                                try {
+                                    ParcelFileDescriptor[] pfds =
+                                            ParcelFileDescriptor.createSocketPair();
+                                    mExecutorService.execute(
+                                            new CursorHandler(cursorSurfaceView, pfds[0]));
+                                    mCursorStream = pfds[0];
+                                    runWithDisplayService(
+                                            (service) -> service.setCursorStream(pfds[1]));
+                                } catch (Exception e) {
+                                    Log.d("TAG", "failed to run cursor stream handler", e);
+                                }
+                                runWithDisplayService(
+                                        (service) ->
+                                                service.setSurface(
+                                                        holder.getSurface(), true /* forCursor */));
+                            }
+
+                            @Override
+                            public void surfaceChanged(
+                                    SurfaceHolder holder, int format, int width, int height) {
+                                Log.d(TAG, "width: " + width + ", height: " + height);
+                            }
+
+                            @Override
+                            public void surfaceDestroyed(SurfaceHolder holder) {
+                                Log.d(TAG, "ICrosvmAndroidDisplayService.removeSurface()");
+                                runWithDisplayService(
+                                        (service) -> service.removeSurface(true /* forCursor */));
+                                if (mCursorStream != null) {
+                                    try {
+                                        mCursorStream.close();
+                                    } catch (IOException e) {
+                                        Log.d(TAG, "failed to close fd", e);
+                                    }
+                                }
                             }
                         });
         getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
@@ -353,6 +410,43 @@
         }
     }
 
+    static class CursorHandler implements Runnable {
+        private final SurfaceView mSurfaceView;
+        private final ParcelFileDescriptor mStream;
+
+        CursorHandler(SurfaceView s, ParcelFileDescriptor stream) {
+            mSurfaceView = s;
+            mStream = stream;
+        }
+
+        @Override
+        public void run() {
+            Log.d(TAG, "CursorHandler");
+            try {
+                ByteBuffer byteBuffer = ByteBuffer.allocate(8 /* (x: u32, y: u32) */);
+                byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
+                while (true) {
+                    byteBuffer.clear();
+                    int bytes =
+                            IoBridge.read(
+                                    mStream.getFileDescriptor(),
+                                    byteBuffer.array(),
+                                    0,
+                                    byteBuffer.array().length);
+                    float x = (float) (byteBuffer.getInt() & 0xFFFFFFFF);
+                    float y = (float) (byteBuffer.getInt() & 0xFFFFFFFF);
+                    mSurfaceView.post(
+                            () -> {
+                                mSurfaceView.setTranslationX(x);
+                                mSurfaceView.setTranslationY(y);
+                            });
+                }
+            } catch (IOException e) {
+                Log.e(TAG, e.getMessage());
+            }
+        }
+    }
+
     /** Reads data from an input stream and posts it to the output data */
     static class Reader implements Runnable {
         private final String mName;
diff --git a/vmlauncher_app/res/layout/activity_main.xml b/vmlauncher_app/res/layout/activity_main.xml
index e52dfcd..a80ece0 100644
--- a/vmlauncher_app/res/layout/activity_main.xml
+++ b/vmlauncher_app/res/layout/activity_main.xml
@@ -11,7 +11,7 @@
       android:layout_height="match_parent"
     />
   <SurfaceView
-        android:id="@+id/surface_view"
+      android:id="@+id/surface_view"
       android:layout_width="match_parent"
       android:layout_height="match_parent"
       android:focusable="true"
@@ -20,5 +20,11 @@
       android:defaultFocusHighlightEnabled="true">
     <requestFocus />
   </SurfaceView>
+  <!-- A cursor size in virtio-gpu spec is always 64x64 -->
+  <SurfaceView
+      android:id="@+id/cursor_surface_view"
+      android:layout_width="64px"
+      android:layout_height="64px">
+  </SurfaceView>
 
 </merge>