From 488d915febd4ff156786a4b8d301d92f9a115659 Mon Sep 17 00:00:00 2001 From: Christine Franks Date: Mon, 29 Nov 2021 12:08:12 -0800 Subject: [PATCH] Add mouse handling APIs Bug: 202273865 Test: atest FrameworksCoreTests:android.hardware.input CTS-Coverage-Bug: 208247880 Change-Id: Ic70f5f9e914492cb512095abb5687727b3bb3f09 --- core/api/system-current.txt | 1 + .../companion/virtual/IVirtualDevice.aidl | 2 + .../virtual/VirtualDeviceManager.java | 4 +- .../hardware/input/InputManagerInternal.java | 17 ++ .../android/hardware/input/VirtualMouse.java | 23 ++ .../companion/virtual/InputController.java | 254 +++++++++++++++--- .../companion/virtual/VirtualDeviceImpl.java | 24 +- .../server/input/InputManagerService.java | 45 ++++ .../server/wm/InputManagerCallback.java | 6 + .../server/wm/WindowManagerService.java | 8 + ...droid_server_input_InputManagerService.cpp | 28 ++ .../virtual/InputControllerTest.java | 84 ++++++ .../VirtualDeviceManagerServiceTest.java | 89 +++++- 13 files changed, 535 insertions(+), 50 deletions(-) create mode 100644 services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 88b0086b9fdaf..e4c5681933aa6 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -4074,6 +4074,7 @@ package android.hardware.input { public class VirtualMouse implements java.io.Closeable { method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void close(); + method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.graphics.PointF getCursorPosition(); method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void sendButtonEvent(@NonNull android.hardware.input.VirtualMouseButtonEvent); method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void sendRelativeEvent(@NonNull android.hardware.input.VirtualMouseRelativeEvent); method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void sendScrollEvent(@NonNull android.hardware.input.VirtualMouseScrollEvent); diff --git a/core/java/android/companion/virtual/IVirtualDevice.aidl b/core/java/android/companion/virtual/IVirtualDevice.aidl index 85855bedfbeb5..339e9a2ff1bc9 100644 --- a/core/java/android/companion/virtual/IVirtualDevice.aidl +++ b/core/java/android/companion/virtual/IVirtualDevice.aidl @@ -18,6 +18,7 @@ package android.companion.virtual; import android.app.PendingIntent; import android.graphics.Point; +import android.graphics.PointF; import android.hardware.input.VirtualKeyEvent; import android.hardware.input.VirtualMouseButtonEvent; import android.hardware.input.VirtualMouseRelativeEvent; @@ -75,4 +76,5 @@ interface IVirtualDevice { */ void launchPendingIntent( int displayId, in PendingIntent pendingIntent, in ResultReceiver resultReceiver); + PointF getCursorPosition(IBinder token); } diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java index 8ab668873f334..1ca83fc09c242 100644 --- a/core/java/android/companion/virtual/VirtualDeviceManager.java +++ b/core/java/android/companion/virtual/VirtualDeviceManager.java @@ -179,7 +179,9 @@ public final class VirtualDeviceManager { /** * Creates a virtual display for this virtual device. All displays created on the same - * device belongs to the same display group. + * device belongs to the same display group. Requires the ADD_TRUSTED_DISPLAY permission + * to create a virtual display which is not in the default DisplayGroup, and to create + * trusted displays. * * @param width The width of the virtual display in pixels, must be greater than 0. * @param height The height of the virtual display in pixels, must be greater than 0. diff --git a/core/java/android/hardware/input/InputManagerInternal.java b/core/java/android/hardware/input/InputManagerInternal.java index 1173c311bd267..f866a2e1c6917 100644 --- a/core/java/android/hardware/input/InputManagerInternal.java +++ b/core/java/android/hardware/input/InputManagerInternal.java @@ -17,6 +17,7 @@ package android.hardware.input; import android.annotation.NonNull; +import android.graphics.PointF; import android.hardware.display.DisplayViewport; import android.os.IBinder; import android.view.InputEvent; @@ -79,6 +80,22 @@ public abstract class InputManagerInternal { public abstract boolean transferTouchFocus(@NonNull IBinder fromChannelToken, @NonNull IBinder toChannelToken); + /** + * Sets the display id that the MouseCursorController will be forced to target. Pass + * {@link android.view.Display#INVALID_DISPLAY} to clear the override. + */ + public abstract void setVirtualMousePointerDisplayId(int pointerDisplayId); + + /** Gets the current position of the mouse cursor. */ + public abstract PointF getCursorPosition(); + + /** + * Sets the eligibility of windows on a given display for pointer capture. If a display is + * marked ineligible, requests to enable pointer capture for windows on that display will be + * ignored. + */ + public abstract void setDisplayEligibilityForPointerCapture(int displayId, boolean isEligible); + /** Registers the {@link LidSwitchCallback} to begin receiving notifications. */ public abstract void registerLidSwitchCallback(@NonNull LidSwitchCallback callbacks); diff --git a/core/java/android/hardware/input/VirtualMouse.java b/core/java/android/hardware/input/VirtualMouse.java index 6599dd2e28eb0..6e2b56a2b5bc4 100644 --- a/core/java/android/hardware/input/VirtualMouse.java +++ b/core/java/android/hardware/input/VirtualMouse.java @@ -20,6 +20,7 @@ import android.annotation.NonNull; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.companion.virtual.IVirtualDevice; +import android.graphics.PointF; import android.os.IBinder; import android.os.RemoteException; import android.view.MotionEvent; @@ -61,6 +62,8 @@ public class VirtualMouse implements Closeable { * Send a mouse button event to the system. * * @param event the event + * @throws IllegalStateException if the display this mouse is associated with is not currently + * targeted */ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void sendButtonEvent(@NonNull VirtualMouseButtonEvent event) { @@ -76,6 +79,8 @@ public class VirtualMouse implements Closeable { * {@link MotionEvent#AXIS_SCROLL}. * * @param event the event + * @throws IllegalStateException if the display this mouse is associated with is not currently + * targeted */ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void sendScrollEvent(@NonNull VirtualMouseScrollEvent event) { @@ -90,6 +95,8 @@ public class VirtualMouse implements Closeable { * Sends a relative movement event to the system. * * @param event the event + * @throws IllegalStateException if the display this mouse is associated with is not currently + * targeted */ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void sendRelativeEvent(@NonNull VirtualMouseRelativeEvent event) { @@ -99,4 +106,20 @@ public class VirtualMouse implements Closeable { throw e.rethrowFromSystemServer(); } } + + /** + * Gets the current cursor position. + * + * @return the position, expressed as x and y coordinates + * @throws IllegalStateException if the display this mouse is associated with is not currently + * targeted + */ + @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) + public @NonNull PointF getCursorPosition() { + try { + return mVirtualDevice.getCursorPosition(mToken); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } } diff --git a/services/companion/java/com/android/server/companion/virtual/InputController.java b/services/companion/java/com/android/server/companion/virtual/InputController.java index 067edcc0b08db..ae39d7ef0b83d 100644 --- a/services/companion/java/com/android/server/companion/virtual/InputController.java +++ b/services/companion/java/com/android/server/companion/virtual/InputController.java @@ -16,8 +16,11 @@ package com.android.server.companion.virtual; +import android.annotation.IntDef; import android.annotation.NonNull; import android.graphics.Point; +import android.graphics.PointF; +import android.hardware.input.InputManagerInternal; import android.hardware.input.VirtualKeyEvent; import android.hardware.input.VirtualMouseButtonEvent; import android.hardware.input.VirtualMouseRelativeEvent; @@ -26,25 +29,40 @@ import android.hardware.input.VirtualTouchEvent; import android.os.IBinder; import android.os.RemoteException; import android.util.ArrayMap; +import android.util.Slog; +import android.view.Display; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import com.android.server.LocalServices; import java.io.PrintWriter; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.Map; +import java.util.concurrent.atomic.AtomicLong; /** Controls virtual input devices, including device lifecycle and event dispatch. */ class InputController { + private static final String TAG = "VirtualInputController"; + private final Object mLock; /* Token -> file descriptor associations. */ @VisibleForTesting @GuardedBy("mLock") - final Map mInputDeviceFds = new ArrayMap<>(); + final Map mInputDeviceDescriptors = new ArrayMap<>(); private final NativeWrapper mNativeWrapper; + /** + * Because the pointer is a singleton, it can only be targeted at one display at a time. Because + * multiple mice could be concurrently registered, mice that are associated with a different + * display than the current target display should not be allowed to affect the current target. + */ + @VisibleForTesting int mActivePointerDisplayId; + InputController(@NonNull Object lock) { this(lock, new NativeWrapper()); } @@ -53,32 +71,39 @@ class InputController { InputController(@NonNull Object lock, @NonNull NativeWrapper nativeWrapper) { mLock = lock; mNativeWrapper = nativeWrapper; + mActivePointerDisplayId = Display.INVALID_DISPLAY; } void close() { synchronized (mLock) { - for (int fd : mInputDeviceFds.values()) { - mNativeWrapper.closeUinput(fd); + for (InputDeviceDescriptor inputDeviceDescriptor : mInputDeviceDescriptors.values()) { + mNativeWrapper.closeUinput(inputDeviceDescriptor.getFileDescriptor()); } - mInputDeviceFds.clear(); + mInputDeviceDescriptors.clear(); + resetMouseValuesLocked(); } } void createKeyboard(@NonNull String deviceName, int vendorId, int productId, - @NonNull IBinder deviceToken) { + @NonNull IBinder deviceToken, + int displayId) { final int fd = mNativeWrapper.openUinputKeyboard(deviceName, vendorId, productId); if (fd < 0) { throw new RuntimeException( "A native error occurred when creating keyboard: " + -fd); } + final BinderDeathRecipient binderDeathRecipient = new BinderDeathRecipient(deviceToken); synchronized (mLock) { - mInputDeviceFds.put(deviceToken, fd); + mInputDeviceDescriptors.put(deviceToken, + new InputDeviceDescriptor(fd, binderDeathRecipient, + InputDeviceDescriptor.TYPE_KEYBOARD, displayId)); } try { - deviceToken.linkToDeath(new BinderDeathRecipient(deviceToken), /* flags= */ 0); + deviceToken.linkToDeath(binderDeathRecipient, /* flags= */ 0); } catch (RemoteException e) { + // TODO(b/215608394): remove and close InputDeviceDescriptor throw new RuntimeException("Could not create virtual keyboard", e); } } @@ -86,18 +111,27 @@ class InputController { void createMouse(@NonNull String deviceName, int vendorId, int productId, - @NonNull IBinder deviceToken) { + @NonNull IBinder deviceToken, + int displayId) { final int fd = mNativeWrapper.openUinputMouse(deviceName, vendorId, productId); if (fd < 0) { throw new RuntimeException( "A native error occurred when creating mouse: " + -fd); } + final BinderDeathRecipient binderDeathRecipient = new BinderDeathRecipient(deviceToken); synchronized (mLock) { - mInputDeviceFds.put(deviceToken, fd); + mInputDeviceDescriptors.put(deviceToken, + new InputDeviceDescriptor(fd, binderDeathRecipient, + InputDeviceDescriptor.TYPE_MOUSE, displayId)); + final InputManagerInternal inputManagerInternal = + LocalServices.getService(InputManagerInternal.class); + inputManagerInternal.setVirtualMousePointerDisplayId(displayId); + mActivePointerDisplayId = displayId; } try { - deviceToken.linkToDeath(new BinderDeathRecipient(deviceToken), /* flags= */ 0); + deviceToken.linkToDeath(binderDeathRecipient, /* flags= */ 0); } catch (RemoteException e) { + // TODO(b/215608394): remove and close InputDeviceDescriptor throw new RuntimeException("Could not create virtual mouse", e); } } @@ -106,6 +140,7 @@ class InputController { int vendorId, int productId, @NonNull IBinder deviceToken, + int displayId, @NonNull Point screenSize) { final int fd = mNativeWrapper.openUinputTouchscreen(deviceName, vendorId, productId, screenSize.y, screenSize.x); @@ -113,93 +148,177 @@ class InputController { throw new RuntimeException( "A native error occurred when creating touchscreen: " + -fd); } + final BinderDeathRecipient binderDeathRecipient = new BinderDeathRecipient(deviceToken); synchronized (mLock) { - mInputDeviceFds.put(deviceToken, fd); + mInputDeviceDescriptors.put(deviceToken, + new InputDeviceDescriptor(fd, binderDeathRecipient, + InputDeviceDescriptor.TYPE_TOUCHSCREEN, displayId)); } try { - deviceToken.linkToDeath(new BinderDeathRecipient(deviceToken), /* flags= */ 0); + deviceToken.linkToDeath(binderDeathRecipient, /* flags= */ 0); } catch (RemoteException e) { + // TODO(b/215608394): remove and close InputDeviceDescriptor throw new RuntimeException("Could not create virtual touchscreen", e); } } void unregisterInputDevice(@NonNull IBinder token) { synchronized (mLock) { - final Integer fd = mInputDeviceFds.remove(token); - if (fd == null) { + final InputDeviceDescriptor inputDeviceDescriptor = mInputDeviceDescriptors.remove( + token); + if (inputDeviceDescriptor == null) { throw new IllegalArgumentException( "Could not unregister input device for given token"); } - mNativeWrapper.closeUinput(fd); + token.unlinkToDeath(inputDeviceDescriptor.getDeathRecipient(), /* flags= */ 0); + mNativeWrapper.closeUinput(inputDeviceDescriptor.getFileDescriptor()); + + // Reset values to the default if all virtual mice are unregistered, or set display + // id if there's another mouse (choose the most recent). + if (inputDeviceDescriptor.isMouse()) { + updateMouseValuesLocked(); + } } } + @GuardedBy("mLock") + private void updateMouseValuesLocked() { + InputDeviceDescriptor mostRecentlyCreatedMouse = null; + for (InputDeviceDescriptor otherInputDeviceDescriptor : + mInputDeviceDescriptors.values()) { + if (otherInputDeviceDescriptor.isMouse()) { + if (mostRecentlyCreatedMouse == null + || (otherInputDeviceDescriptor.getCreationOrderNumber() + > mostRecentlyCreatedMouse.getCreationOrderNumber())) { + mostRecentlyCreatedMouse = otherInputDeviceDescriptor; + } + } + } + if (mostRecentlyCreatedMouse != null) { + final InputManagerInternal inputManagerInternal = + LocalServices.getService(InputManagerInternal.class); + inputManagerInternal.setVirtualMousePointerDisplayId( + mostRecentlyCreatedMouse.getDisplayId()); + mActivePointerDisplayId = mostRecentlyCreatedMouse.getDisplayId(); + } else { + // All mice have been unregistered; reset all values. + resetMouseValuesLocked(); + } + } + + private void resetMouseValuesLocked() { + final InputManagerInternal inputManagerInternal = + LocalServices.getService(InputManagerInternal.class); + inputManagerInternal.setVirtualMousePointerDisplayId(Display.INVALID_DISPLAY); + mActivePointerDisplayId = Display.INVALID_DISPLAY; + } + boolean sendKeyEvent(@NonNull IBinder token, @NonNull VirtualKeyEvent event) { synchronized (mLock) { - final Integer fd = mInputDeviceFds.get(token); - if (fd == null) { + final InputDeviceDescriptor inputDeviceDescriptor = mInputDeviceDescriptors.get( + token); + if (inputDeviceDescriptor == null) { throw new IllegalArgumentException( "Could not send key event to input device for given token"); } - return mNativeWrapper.writeKeyEvent(fd, event.getKeyCode(), event.getAction()); + return mNativeWrapper.writeKeyEvent(inputDeviceDescriptor.getFileDescriptor(), + event.getKeyCode(), event.getAction()); } } boolean sendButtonEvent(@NonNull IBinder token, @NonNull VirtualMouseButtonEvent event) { synchronized (mLock) { - final Integer fd = mInputDeviceFds.get(token); - if (fd == null) { + final InputDeviceDescriptor inputDeviceDescriptor = mInputDeviceDescriptors.get( + token); + if (inputDeviceDescriptor == null) { throw new IllegalArgumentException( "Could not send button event to input device for given token"); } - return mNativeWrapper.writeButtonEvent(fd, event.getButtonCode(), event.getAction()); + if (inputDeviceDescriptor.getDisplayId() != mActivePointerDisplayId) { + throw new IllegalStateException( + "Display id associated with this mouse is not currently targetable"); + } + return mNativeWrapper.writeButtonEvent(inputDeviceDescriptor.getFileDescriptor(), + event.getButtonCode(), event.getAction()); } } boolean sendTouchEvent(@NonNull IBinder token, @NonNull VirtualTouchEvent event) { synchronized (mLock) { - final Integer fd = mInputDeviceFds.get(token); - if (fd == null) { + final InputDeviceDescriptor inputDeviceDescriptor = mInputDeviceDescriptors.get( + token); + if (inputDeviceDescriptor == null) { throw new IllegalArgumentException( "Could not send touch event to input device for given token"); } - return mNativeWrapper.writeTouchEvent(fd, event.getPointerId(), event.getToolType(), - event.getAction(), event.getX(), event.getY(), event.getPressure(), - event.getMajorAxisSize()); + return mNativeWrapper.writeTouchEvent(inputDeviceDescriptor.getFileDescriptor(), + event.getPointerId(), event.getToolType(), event.getAction(), event.getX(), + event.getY(), event.getPressure(), event.getMajorAxisSize()); } } boolean sendRelativeEvent(@NonNull IBinder token, @NonNull VirtualMouseRelativeEvent event) { synchronized (mLock) { - final Integer fd = mInputDeviceFds.get(token); - if (fd == null) { + final InputDeviceDescriptor inputDeviceDescriptor = mInputDeviceDescriptors.get( + token); + if (inputDeviceDescriptor == null) { throw new IllegalArgumentException( "Could not send relative event to input device for given token"); } - return mNativeWrapper.writeRelativeEvent(fd, event.getRelativeX(), - event.getRelativeY()); + if (inputDeviceDescriptor.getDisplayId() != mActivePointerDisplayId) { + throw new IllegalStateException( + "Display id associated with this mouse is not currently targetable"); + } + return mNativeWrapper.writeRelativeEvent(inputDeviceDescriptor.getFileDescriptor(), + event.getRelativeX(), event.getRelativeY()); } } boolean sendScrollEvent(@NonNull IBinder token, @NonNull VirtualMouseScrollEvent event) { synchronized (mLock) { - final Integer fd = mInputDeviceFds.get(token); - if (fd == null) { + final InputDeviceDescriptor inputDeviceDescriptor = mInputDeviceDescriptors.get( + token); + if (inputDeviceDescriptor == null) { throw new IllegalArgumentException( "Could not send scroll event to input device for given token"); } - return mNativeWrapper.writeScrollEvent(fd, event.getXAxisMovement(), - event.getYAxisMovement()); + if (inputDeviceDescriptor.getDisplayId() != mActivePointerDisplayId) { + throw new IllegalStateException( + "Display id associated with this mouse is not currently targetable"); + } + return mNativeWrapper.writeScrollEvent(inputDeviceDescriptor.getFileDescriptor(), + event.getXAxisMovement(), event.getYAxisMovement()); + } + } + + public PointF getCursorPosition(@NonNull IBinder token) { + synchronized (mLock) { + final InputDeviceDescriptor inputDeviceDescriptor = mInputDeviceDescriptors.get( + token); + if (inputDeviceDescriptor == null) { + throw new IllegalArgumentException( + "Could not get cursor position for input device for given token"); + } + if (inputDeviceDescriptor.getDisplayId() != mActivePointerDisplayId) { + throw new IllegalStateException( + "Display id associated with this mouse is not currently targetable"); + } + return LocalServices.getService(InputManagerInternal.class).getCursorPosition(); } } public void dump(@NonNull PrintWriter fout) { fout.println(" InputController: "); synchronized (mLock) { - fout.println(" Active file descriptors: "); - for (int inputDeviceFd : mInputDeviceFds.values()) { - fout.println(inputDeviceFd); + fout.println(" Active descriptors: "); + for (InputDeviceDescriptor inputDeviceDescriptor : mInputDeviceDescriptors.values()) { + fout.println(" fd: " + inputDeviceDescriptor.getFileDescriptor()); + fout.println(" displayId: " + inputDeviceDescriptor.getDisplayId()); + fout.println(" creationOrder: " + + inputDeviceDescriptor.getCreationOrderNumber()); + fout.println(" type: " + inputDeviceDescriptor.getType()); } + fout.println(" Active mouse display id: " + mActivePointerDisplayId); } } @@ -267,6 +386,63 @@ class InputController { } } + @VisibleForTesting static final class InputDeviceDescriptor { + + static final int TYPE_KEYBOARD = 1; + static final int TYPE_MOUSE = 2; + static final int TYPE_TOUCHSCREEN = 3; + @IntDef(prefix = { "TYPE_" }, value = { + TYPE_KEYBOARD, + TYPE_MOUSE, + TYPE_TOUCHSCREEN, + }) + @Retention(RetentionPolicy.SOURCE) + @interface Type { + } + + private static final AtomicLong sNextCreationOrderNumber = new AtomicLong(1); + + private final int mFd; + private final IBinder.DeathRecipient mDeathRecipient; + private final @Type int mType; + private final int mDisplayId; + // Monotonically increasing number; devices with lower numbers were created earlier. + private final long mCreationOrderNumber; + + InputDeviceDescriptor(int fd, IBinder.DeathRecipient deathRecipient, + @Type int type, int displayId) { + mFd = fd; + mDeathRecipient = deathRecipient; + mType = type; + mDisplayId = displayId; + mCreationOrderNumber = sNextCreationOrderNumber.getAndIncrement(); + } + + public int getFileDescriptor() { + return mFd; + } + + public int getType() { + return mType; + } + + public boolean isMouse() { + return mType == TYPE_MOUSE; + } + + public IBinder.DeathRecipient getDeathRecipient() { + return mDeathRecipient; + } + + public int getDisplayId() { + return mDisplayId; + } + + public long getCreationOrderNumber() { + return mCreationOrderNumber; + } + } + private final class BinderDeathRecipient implements IBinder.DeathRecipient { private final IBinder mDeviceToken; @@ -277,6 +453,10 @@ class InputController { @Override public void binderDied() { + // All callers are expected to call {@link VirtualDevice#unregisterInputDevice} before + // quitting, which removes this death recipient. If this is invoked, the remote end + // died, or they disposed of the object without properly unregistering. + Slog.e(TAG, "Virtual input controller binder died"); unregisterInputDevice(mDeviceToken); } } diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java index 59c9d8c625b52..6ab8e754e2b31 100644 --- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java +++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java @@ -32,7 +32,9 @@ import android.companion.virtual.IVirtualDevice; import android.companion.virtual.VirtualDeviceParams; import android.content.Context; import android.graphics.Point; +import android.graphics.PointF; import android.hardware.display.DisplayManager; +import android.hardware.input.InputManagerInternal; import android.hardware.input.VirtualKeyEvent; import android.hardware.input.VirtualMouseButtonEvent; import android.hardware.input.VirtualMouseRelativeEvent; @@ -50,6 +52,7 @@ import android.util.SparseArray; import android.window.DisplayWindowPolicyController; import com.android.internal.annotations.VisibleForTesting; +import com.android.server.LocalServices; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -201,7 +204,8 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub } final long token = Binder.clearCallingIdentity(); try { - mInputController.createKeyboard(deviceName, vendorId, productId, deviceToken); + mInputController.createKeyboard(deviceName, vendorId, productId, deviceToken, + displayId); } finally { Binder.restoreCallingIdentity(token); } @@ -226,7 +230,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub } final long token = Binder.clearCallingIdentity(); try { - mInputController.createMouse(deviceName, vendorId, productId, deviceToken); + mInputController.createMouse(deviceName, vendorId, productId, deviceToken, displayId); } finally { Binder.restoreCallingIdentity(token); } @@ -253,7 +257,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub final long token = Binder.clearCallingIdentity(); try { mInputController.createTouchscreen(deviceName, vendorId, productId, - deviceToken, screenSize); + deviceToken, displayId, screenSize); } finally { Binder.restoreCallingIdentity(token); } @@ -323,6 +327,16 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub } } + @Override // Binder call + public PointF getCursorPosition(IBinder token) { + final long binderToken = Binder.clearCallingIdentity(); + try { + return mInputController.getCursorPosition(token); + } finally { + Binder.restoreCallingIdentity(binderToken); + } + } + @Override protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) { fout.println(" VirtualDevice: "); @@ -343,6 +357,8 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub "Virtual device already have a virtual display with ID " + displayId); } mVirtualDisplayIds.add(displayId); + LocalServices.getService(InputManagerInternal.class).setDisplayEligibilityForPointerCapture( + displayId, false); final GenericWindowPolicyController dwpc = new GenericWindowPolicyController(FLAG_SECURE, SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS, getAllowedUserHandles(), @@ -376,6 +392,8 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub "Virtual device doesn't have a virtual display with ID " + displayId); } mVirtualDisplayIds.remove(displayId); + LocalServices.getService(InputManagerInternal.class).setDisplayEligibilityForPointerCapture( + displayId, true); mWindowPolicyControllers.remove(displayId); } diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java index 261aa32f093eb..4de39bcd24e0b 100644 --- a/services/core/java/com/android/server/input/InputManagerService.java +++ b/services/core/java/com/android/server/input/InputManagerService.java @@ -38,6 +38,7 @@ import android.content.res.Resources.NotFoundException; import android.content.res.TypedArray; import android.content.res.XmlResourceParser; import android.database.ContentObserver; +import android.graphics.PointF; import android.graphics.Rect; import android.hardware.display.DisplayManager; import android.hardware.display.DisplayViewport; @@ -269,6 +270,10 @@ public class InputManagerService extends IInputManager.Stub private final Map mRuntimeAssociations = new ArrayMap(); @GuardedBy("mAssociationLock") private final Map mUniqueIdAssociations = new ArrayMap<>(); + private final Object mPointerDisplayIdLock = new Object(); + // Forces the MouseCursorController to target a specific display id. + @GuardedBy("mPointerDisplayIdLock") + private int mOverriddenPointerDisplayId = Display.INVALID_DISPLAY; private static native long nativeInit(InputManagerService service, Context context, MessageQueue messageQueue); @@ -341,6 +346,9 @@ public class InputManagerService extends IInputManager.Stub private static native boolean nativeCanDispatchToDisplay(long ptr, int deviceId, int displayId); private static native void nativeNotifyPortAssociationsChanged(long ptr); private static native void nativeChangeUniqueIdAssociation(long ptr); + private static native void nativeNotifyPointerDisplayIdChanged(long ptr); + private static native void nativeSetDisplayEligibilityForPointerCapture(long ptr, int displayId, + boolean enabled); private static native void nativeSetMotionClassifierEnabled(long ptr, boolean enabled); private static native InputSensorInfo[] nativeGetSensorList(long ptr, int deviceId); private static native boolean nativeFlushSensor(long ptr, int deviceId, int sensorType); @@ -1902,6 +1910,18 @@ public class InputManagerService extends IInputManager.Stub return result; } + private void setVirtualMousePointerDisplayId(int displayId) { + synchronized (mPointerDisplayIdLock) { + mOverriddenPointerDisplayId = displayId; + } + // TODO(b/215597605): trigger MousePositionTracker update + nativeNotifyPointerDisplayIdChanged(mPtr); + } + + private void setDisplayEligibilityForPointerCapture(int displayId, boolean isEligible) { + nativeSetDisplayEligibilityForPointerCapture(mPtr, displayId, isEligible); + } + private static class VibrationInfo { private final long[] mPattern; private final int[] mAmplitudes; @@ -2575,6 +2595,7 @@ public class InputManagerService extends IInputManager.Stub synchronized (mInputFilterLock) { } synchronized (mAssociationsLock) { /* Test if blocked by associations lock. */} synchronized (mLidSwitchLock) { /* Test if blocked by lid switch lock. */ } + synchronized (mPointerDisplayIdLock) { /* Test if blocked by pointer display id lock */ } nativeMonitor(mPtr); } @@ -2965,6 +2986,12 @@ public class InputManagerService extends IInputManager.Stub // Native callback. private int getPointerDisplayId() { + synchronized (mPointerDisplayIdLock) { + // Prefer the override to all other displays. + if (mOverriddenPointerDisplayId != Display.INVALID_DISPLAY) { + return mOverriddenPointerDisplayId; + } + } return mWindowManagerCallbacks.getPointerDisplayId(); } @@ -3109,6 +3136,9 @@ public class InputManagerService extends IInputManager.Stub int getPointerDisplayId(); + /** Gets the x and y coordinates of the cursor's current position. */ + PointF getCursorPosition(); + /** * Notifies window manager that a {@link android.view.MotionEvent#ACTION_DOWN} pointer event * occurred on a window that did not have focus. @@ -3426,6 +3456,21 @@ public class InputManagerService extends IInputManager.Stub return InputManagerService.this.transferTouchFocus(fromChannelToken, toChannelToken); } + @Override + public void setVirtualMousePointerDisplayId(int pointerDisplayId) { + InputManagerService.this.setVirtualMousePointerDisplayId(pointerDisplayId); + } + + @Override + public PointF getCursorPosition() { + return mWindowManagerCallbacks.getCursorPosition(); + } + + @Override + public void setDisplayEligibilityForPointerCapture(int displayId, boolean isEligible) { + InputManagerService.this.setDisplayEligibilityForPointerCapture(displayId, isEligible); + } + @Override public void registerLidSwitchCallback(LidSwitchCallback callbacks) { registerLidSwitchCallbackInternal(callbacks); diff --git a/services/core/java/com/android/server/wm/InputManagerCallback.java b/services/core/java/com/android/server/wm/InputManagerCallback.java index e02e7c5ab15d4..f91969b2c5587 100644 --- a/services/core/java/com/android/server/wm/InputManagerCallback.java +++ b/services/core/java/com/android/server/wm/InputManagerCallback.java @@ -24,6 +24,7 @@ import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import static com.android.server.wm.WindowManagerService.H.ON_POINTER_DOWN_OUTSIDE_FOCUS; import android.annotation.NonNull; +import android.graphics.PointF; import android.os.Debug; import android.os.IBinder; import android.util.Slog; @@ -218,6 +219,11 @@ final class InputManagerCallback implements InputManagerService.WindowManagerCal } } + @Override + public PointF getCursorPosition() { + return mService.getLatestMousePosition(); + } + @Override public void onPointerDownOutsideFocus(IBinder touchedToken) { mService.mH.obtainMessage(ON_POINTER_DOWN_OUTSIDE_FOCUS, touchedToken).sendToTarget(); diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 2f0ef4a8ee1b4..1bcc1dbae58d5 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -172,6 +172,7 @@ import android.database.ContentObserver; import android.graphics.Bitmap; import android.graphics.Insets; import android.graphics.Point; +import android.graphics.PointF; import android.graphics.Rect; import android.graphics.Region; import android.hardware.configstore.V1_0.OptionalBool; @@ -7066,6 +7067,13 @@ public class WindowManagerService extends IWindowManager.Stub } } + PointF getLatestMousePosition() { + synchronized (mMousePositionTracker) { + return new PointF(mMousePositionTracker.mLatestMouseX, + mMousePositionTracker.mLatestMouseY); + } + } + /** * Update a tap exclude region in the window identified by the provided id. Touches down on this * region will not: diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp index 3cd4e5ee82cf1..4524bb71d218e 100644 --- a/services/core/jni/com_android_server_input_InputManagerService.cpp +++ b/services/core/jni/com_android_server_input_InputManagerService.cpp @@ -286,6 +286,7 @@ public: void requestPointerCapture(const sp& windowToken, bool enabled); void setCustomPointerIcon(const SpriteIcon& icon); void setMotionClassifierEnabled(bool enabled); + void notifyPointerDisplayIdChanged(); /* --- InputReaderPolicyInterface implementation --- */ @@ -1494,6 +1495,18 @@ void NativeInputManager::setMotionClassifierEnabled(bool enabled) { mInputManager->getClassifier().setMotionClassifierEnabled(enabled); } +void NativeInputManager::notifyPointerDisplayIdChanged() { + int32_t pointerDisplayId = getPointerDisplayId(); + + { // acquire lock + AutoMutex _l(mLock); + mLocked.pointerDisplayId = pointerDisplayId; + } // release lock + + mInputManager->getReader().requestRefreshConfiguration( + InputReaderConfiguration::CHANGE_DISPLAY_INFO); +} + // ---------------------------------------------------------------------------- static jlong nativeInit(JNIEnv* env, jclass /* clazz */, @@ -2186,6 +2199,18 @@ static void nativeNotifyPortAssociationsChanged(JNIEnv* env, jclass /* clazz */, InputReaderConfiguration::CHANGE_DISPLAY_INFO); } +static void nativeNotifyPointerDisplayIdChanged(JNIEnv* env, jclass /* clazz */, jlong ptr) { + NativeInputManager* im = reinterpret_cast(ptr); + im->notifyPointerDisplayIdChanged(); +} + +static void nativeSetDisplayEligibilityForPointerCapture(JNIEnv* env, jclass /* clazz */, jlong ptr, + jint displayId, jboolean isEligible) { + NativeInputManager* im = reinterpret_cast(ptr); + im->getInputManager()->getDispatcher().setDisplayEligibilityForPointerCapture(displayId, + isEligible); +} + static void nativeChangeUniqueIdAssociation(JNIEnv* env, jclass /* clazz */, jlong ptr) { NativeInputManager* im = reinterpret_cast(ptr); im->getInputManager()->getReader().requestRefreshConfiguration( @@ -2370,6 +2395,9 @@ static const JNINativeMethod gInputManagerMethods[] = { {"nativeCanDispatchToDisplay", "(JII)Z", (void*)nativeCanDispatchToDisplay}, {"nativeNotifyPortAssociationsChanged", "(J)V", (void*)nativeNotifyPortAssociationsChanged}, {"nativeChangeUniqueIdAssociation", "(J)V", (void*)nativeChangeUniqueIdAssociation}, + {"nativeNotifyPointerDisplayIdChanged", "(J)V", (void*)nativeNotifyPointerDisplayIdChanged}, + {"nativeSetDisplayEligibilityForPointerCapture", "(JIZ)V", + (void*)nativeSetDisplayEligibilityForPointerCapture}, {"nativeSetMotionClassifierEnabled", "(JZ)V", (void*)nativeSetMotionClassifierEnabled}, {"nativeGetSensorList", "(JI)[Landroid/hardware/input/InputSensorInfo;", (void*)nativeGetSensorList}, diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java new file mode 100644 index 0000000000000..53468c81a1e26 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.companion.virtual; + +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.verify; + +import android.hardware.input.InputManagerInternal; +import android.os.Binder; +import android.os.IBinder; +import android.platform.test.annotations.Presubmit; +import android.view.Display; + +import androidx.test.runner.AndroidJUnit4; + +import com.android.server.LocalServices; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@Presubmit +@RunWith(AndroidJUnit4.class) +public class InputControllerTest { + + @Mock + private InputManagerInternal mInputManagerInternalMock; + @Mock + private InputController.NativeWrapper mNativeWrapperMock; + + private InputController mInputController; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + doNothing().when(mInputManagerInternalMock).setVirtualMousePointerDisplayId(anyInt()); + LocalServices.removeServiceForTest(InputManagerInternal.class); + LocalServices.addService(InputManagerInternal.class, mInputManagerInternalMock); + + mInputController = new InputController(new Object(), mNativeWrapperMock); + } + + @Test + public void unregisterInputDevice_allMiceUnregistered_unsetValues() { + final IBinder deviceToken = new Binder(); + mInputController.createMouse("name", /*vendorId= */ 1, /*productId= */ 1, deviceToken, + /* displayId= */ 1); + mInputController.unregisterInputDevice(deviceToken); + verify(mInputManagerInternalMock).setVirtualMousePointerDisplayId( + eq(Display.INVALID_DISPLAY)); + } + + @Test + public void unregisterInputDevice_anotherMouseExists_setPointerDisplayIdOverride() { + final IBinder deviceToken = new Binder(); + mInputController.createMouse("name", /*vendorId= */ 1, /*productId= */ 1, deviceToken, + /* displayId= */ 1); + verify(mInputManagerInternalMock).setVirtualMousePointerDisplayId(eq(1)); + mInputController.createMouse("name", /*vendorId= */ 1, /*productId= */ 1, deviceToken, + /* displayId= */ 2); + verify(mInputManagerInternalMock).setVirtualMousePointerDisplayId(eq(2)); + mInputController.unregisterInputDevice(deviceToken); + verify(mInputManagerInternalMock).setVirtualMousePointerDisplayId(eq(1)); + } +} diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java index a6b4aecf1cb60..fe7d34a55852b 100644 --- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java @@ -18,6 +18,7 @@ package com.android.server.companion.virtual; import static com.google.common.truth.Truth.assertWithMessage; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doCallRealMethod; @@ -33,6 +34,7 @@ import android.content.Context; import android.content.ContextWrapper; import android.graphics.Point; import android.hardware.display.DisplayManagerInternal; +import android.hardware.input.InputManagerInternal; import android.hardware.input.VirtualKeyEvent; import android.hardware.input.VirtualMouseButtonEvent; import android.hardware.input.VirtualMouseRelativeEvent; @@ -77,6 +79,8 @@ public class VirtualDeviceManagerServiceTest { private VirtualDeviceImpl.PendingTrampolineCallback mPendingTrampolineCallback; @Mock private DevicePolicyManager mDevicePolicyManagerMock; + @Mock + private InputManagerInternal mInputManagerInternalMock; @Before public void setUp() { @@ -85,6 +89,10 @@ public class VirtualDeviceManagerServiceTest { LocalServices.removeServiceForTest(DisplayManagerInternal.class); LocalServices.addService(DisplayManagerInternal.class, mDisplayManagerInternalMock); + doNothing().when(mInputManagerInternalMock).setVirtualMousePointerDisplayId(anyInt()); + LocalServices.removeServiceForTest(InputManagerInternal.class); + LocalServices.addService(InputManagerInternal.class, mInputManagerInternalMock); + mContext = Mockito.spy(new ContextWrapper(InstrumentationRegistry.getTargetContext())); doNothing().when(mContext).enforceCallingOrSelfPermission( eq(Manifest.permission.CREATE_VIRTUAL_DEVICE), anyString()); @@ -169,7 +177,7 @@ public class VirtualDeviceManagerServiceTest { mDeviceImpl.createVirtualKeyboard(DISPLAY_ID, DEVICE_NAME, VENDOR_ID, PRODUCT_ID, BINDER); assertWithMessage("Virtual keyboard should register fd when the display matches") - .that(mInputController.mInputDeviceFds).isNotEmpty(); + .that(mInputController.mInputDeviceDescriptors).isNotEmpty(); verify(mNativeWrapperMock).openUinputKeyboard(DEVICE_NAME, VENDOR_ID, PRODUCT_ID); } @@ -179,7 +187,7 @@ public class VirtualDeviceManagerServiceTest { mDeviceImpl.createVirtualMouse(DISPLAY_ID, DEVICE_NAME, VENDOR_ID, PRODUCT_ID, BINDER); assertWithMessage("Virtual keyboard should register fd when the display matches") - .that(mInputController.mInputDeviceFds).isNotEmpty(); + .that(mInputController.mInputDeviceDescriptors).isNotEmpty(); verify(mNativeWrapperMock).openUinputMouse(DEVICE_NAME, VENDOR_ID, PRODUCT_ID); } @@ -189,7 +197,7 @@ public class VirtualDeviceManagerServiceTest { mDeviceImpl.createVirtualTouchscreen(DISPLAY_ID, DEVICE_NAME, VENDOR_ID, PRODUCT_ID, BINDER, new Point(WIDTH, HEIGHT)); assertWithMessage("Virtual keyboard should register fd when the display matches") - .that(mInputController.mInputDeviceFds).isNotEmpty(); + .that(mInputController.mInputDeviceDescriptors).isNotEmpty(); verify(mNativeWrapperMock).openUinputTouchscreen(DEVICE_NAME, VENDOR_ID, PRODUCT_ID, HEIGHT, WIDTH); } @@ -209,7 +217,9 @@ public class VirtualDeviceManagerServiceTest { final int fd = 1; final int keyCode = KeyEvent.KEYCODE_A; final int action = VirtualKeyEvent.ACTION_UP; - mInputController.mInputDeviceFds.put(BINDER, fd); + mInputController.mInputDeviceDescriptors.put(BINDER, + new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 1, + /* displayId= */ 1)); mDeviceImpl.sendKeyEvent(BINDER, new VirtualKeyEvent.Builder().setKeyCode(keyCode) .setAction(action).build()); verify(mNativeWrapperMock).writeKeyEvent(fd, keyCode, action); @@ -232,13 +242,32 @@ public class VirtualDeviceManagerServiceTest { final int fd = 1; final int buttonCode = VirtualMouseButtonEvent.BUTTON_BACK; final int action = VirtualMouseButtonEvent.ACTION_BUTTON_PRESS; - mInputController.mInputDeviceFds.put(BINDER, fd); + mInputController.mInputDeviceDescriptors.put(BINDER, + new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 2, + /* displayId= */ 1)); + mInputController.mActivePointerDisplayId = 1; mDeviceImpl.sendButtonEvent(BINDER, new VirtualMouseButtonEvent.Builder() .setButtonCode(buttonCode) .setAction(action).build()); verify(mNativeWrapperMock).writeButtonEvent(fd, buttonCode, action); } + @Test + public void sendButtonEvent_hasFd_wrongDisplay_throwsIllegalStateException() { + final int fd = 1; + final int buttonCode = VirtualMouseButtonEvent.BUTTON_BACK; + final int action = VirtualMouseButtonEvent.ACTION_BUTTON_PRESS; + mInputController.mInputDeviceDescriptors.put(BINDER, + new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 2, + /* displayId= */ 1)); + assertThrows( + IllegalStateException.class, + () -> + mDeviceImpl.sendButtonEvent(BINDER, new VirtualMouseButtonEvent.Builder() + .setButtonCode(buttonCode) + .setAction(action).build())); + } + @Test public void sendRelativeEvent_noFd() { assertThrows( @@ -254,12 +283,31 @@ public class VirtualDeviceManagerServiceTest { final int fd = 1; final float x = -0.2f; final float y = 0.7f; - mInputController.mInputDeviceFds.put(BINDER, fd); + mInputController.mInputDeviceDescriptors.put(BINDER, + new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 2, + /* displayId= */ 1)); + mInputController.mActivePointerDisplayId = 1; mDeviceImpl.sendRelativeEvent(BINDER, new VirtualMouseRelativeEvent.Builder() .setRelativeX(x).setRelativeY(y).build()); verify(mNativeWrapperMock).writeRelativeEvent(fd, x, y); } + @Test + public void sendRelativeEvent_hasFd_wrongDisplay_throwsIllegalStateException() { + final int fd = 1; + final float x = -0.2f; + final float y = 0.7f; + mInputController.mInputDeviceDescriptors.put(BINDER, + new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 2, + /* displayId= */ 1)); + assertThrows( + IllegalStateException.class, + () -> + mDeviceImpl.sendRelativeEvent(BINDER, + new VirtualMouseRelativeEvent.Builder() + .setRelativeX(x).setRelativeY(y).build())); + } + @Test public void sendScrollEvent_noFd() { assertThrows( @@ -276,13 +324,32 @@ public class VirtualDeviceManagerServiceTest { final int fd = 1; final float x = 0.5f; final float y = 1f; - mInputController.mInputDeviceFds.put(BINDER, fd); + mInputController.mInputDeviceDescriptors.put(BINDER, + new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 2, + /* displayId= */ 1)); + mInputController.mActivePointerDisplayId = 1; mDeviceImpl.sendScrollEvent(BINDER, new VirtualMouseScrollEvent.Builder() .setXAxisMovement(x) .setYAxisMovement(y).build()); verify(mNativeWrapperMock).writeScrollEvent(fd, x, y); } + @Test + public void sendScrollEvent_hasFd_wrongDisplay_throwsIllegalStateException() { + final int fd = 1; + final float x = 0.5f; + final float y = 1f; + mInputController.mInputDeviceDescriptors.put(BINDER, + new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 2, + /* displayId= */ 1)); + assertThrows( + IllegalStateException.class, + () -> + mDeviceImpl.sendScrollEvent(BINDER, new VirtualMouseScrollEvent.Builder() + .setXAxisMovement(x) + .setYAxisMovement(y).build())); + } + @Test public void sendTouchEvent_noFd() { assertThrows( @@ -305,7 +372,9 @@ public class VirtualDeviceManagerServiceTest { final float x = 100.5f; final float y = 200.5f; final int action = VirtualTouchEvent.ACTION_UP; - mInputController.mInputDeviceFds.put(BINDER, fd); + mInputController.mInputDeviceDescriptors.put(BINDER, + new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 3, + /* displayId= */ 1)); mDeviceImpl.sendTouchEvent(BINDER, new VirtualTouchEvent.Builder().setX(x) .setY(y).setAction(action).setPointerId(pointerId).setToolType(toolType).build()); verify(mNativeWrapperMock).writeTouchEvent(fd, pointerId, toolType, action, x, y, Float.NaN, @@ -322,7 +391,9 @@ public class VirtualDeviceManagerServiceTest { final int action = VirtualTouchEvent.ACTION_UP; final float pressure = 1.0f; final float majorAxisSize = 10.0f; - mInputController.mInputDeviceFds.put(BINDER, fd); + mInputController.mInputDeviceDescriptors.put(BINDER, + new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 3, + /* displayId= */ 1)); mDeviceImpl.sendTouchEvent(BINDER, new VirtualTouchEvent.Builder().setX(x) .setY(y).setAction(action).setPointerId(pointerId).setToolType(toolType) .setPressure(pressure).setMajorAxisSize(majorAxisSize).build());