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>