Add mouse handling APIs
Bug: 202273865 Test: atest FrameworksCoreTests:android.hardware.input CTS-Coverage-Bug: 208247880 Change-Id: Ic70f5f9e914492cb512095abb5687727b3bb3f09
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<IBinder, Integer> mInputDeviceFds = new ArrayMap<>();
|
||||
final Map<IBinder, InputDeviceDescriptor> 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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<String, Integer> mRuntimeAssociations = new ArrayMap<String, Integer>();
|
||||
@GuardedBy("mAssociationLock")
|
||||
private final Map<String, String> 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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -286,6 +286,7 @@ public:
|
||||
void requestPointerCapture(const sp<IBinder>& 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<NativeInputManager*>(ptr);
|
||||
im->notifyPointerDisplayIdChanged();
|
||||
}
|
||||
|
||||
static void nativeSetDisplayEligibilityForPointerCapture(JNIEnv* env, jclass /* clazz */, jlong ptr,
|
||||
jint displayId, jboolean isEligible) {
|
||||
NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
|
||||
im->getInputManager()->getDispatcher().setDisplayEligibilityForPointerCapture(displayId,
|
||||
isEligible);
|
||||
}
|
||||
|
||||
static void nativeChangeUniqueIdAssociation(JNIEnv* env, jclass /* clazz */, jlong ptr) {
|
||||
NativeInputManager* im = reinterpret_cast<NativeInputManager*>(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},
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
|
||||
Reference in New Issue
Block a user