Add mouse handling APIs

Bug: 202273865
Test: atest FrameworksCoreTests:android.hardware.input
CTS-Coverage-Bug: 208247880
Change-Id: Ic70f5f9e914492cb512095abb5687727b3bb3f09
This commit is contained in:
Christine Franks
2021-11-29 12:08:12 -08:00
parent c84373611f
commit 488d915feb
13 changed files with 535 additions and 50 deletions

View File

@@ -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);

View File

@@ -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);
}

View File

@@ -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.

View File

@@ -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);

View File

@@ -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();
}
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}

View File

@@ -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);

View File

@@ -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();

View File

@@ -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:

View File

@@ -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},

View File

@@ -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));
}
}

View File

@@ -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());