From 27a354bf40f28016bee876a0df59b4222ae3dddd Mon Sep 17 00:00:00 2001 From: Christine Franks Date: Tue, 25 Jan 2022 00:23:18 -0800 Subject: [PATCH] Add API to hide cursor for virtual mice Bug: 216244627 Test: atest FrameworksServicesTests:com.android.server.companion.virtual CTS-Coverage-Bug: 208247880 Change-Id: Ie1a2bc526a705f2f9788898afbff87f5a9448f85 --- core/api/system-current.txt | 1 + .../companion/virtual/IVirtualDevice.aidl | 3 + .../virtual/VirtualDeviceManager.java | 16 ++ .../hardware/input/InputManagerInternal.java | 11 +- .../companion/virtual/InputController.java | 94 +++++---- .../companion/virtual/VirtualDeviceImpl.java | 36 +++- .../server/input/InputManagerService.java | 191 ++++++++++++++++-- .../virtual/InputControllerTest.java | 11 +- .../VirtualDeviceManagerServiceTest.java | 26 ++- 9 files changed, 313 insertions(+), 76 deletions(-) diff --git a/core/api/system-current.txt b/core/api/system-current.txt index ab3feeb9cd17b..342e1fcb1535d 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -2742,6 +2742,7 @@ package android.companion.virtual { method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualTouchscreen createVirtualTouchscreen(@NonNull android.hardware.display.VirtualDisplay, @NonNull String, int, int); method public void launchPendingIntent(int, @NonNull android.app.PendingIntent, @NonNull java.util.concurrent.Executor, @NonNull android.companion.virtual.VirtualDeviceManager.LaunchCallback); method public void removeActivityListener(@NonNull android.companion.virtual.VirtualDeviceManager.ActivityListener); + method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void setShowPointerIcon(boolean); } public final class VirtualDeviceParams implements android.os.Parcelable { diff --git a/core/java/android/companion/virtual/IVirtualDevice.aidl b/core/java/android/companion/virtual/IVirtualDevice.aidl index 339e9a2ff1bc9..8fc24fd2d7f22 100644 --- a/core/java/android/companion/virtual/IVirtualDevice.aidl +++ b/core/java/android/companion/virtual/IVirtualDevice.aidl @@ -77,4 +77,7 @@ interface IVirtualDevice { void launchPendingIntent( int displayId, in PendingIntent pendingIntent, in ResultReceiver resultReceiver); PointF getCursorPosition(IBinder token); + + /** Sets whether to show or hide the cursor while this virtual device is active. */ + void setShowPointerIcon(boolean showPointerIcon); } diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java index bb9bb094429a9..69033a686351d 100644 --- a/core/java/android/companion/virtual/VirtualDeviceManager.java +++ b/core/java/android/companion/virtual/VirtualDeviceManager.java @@ -337,6 +337,22 @@ public final class VirtualDeviceManager { } } + /** + * Sets the visibility of the pointer icon for this VirtualDevice's associated displays. + * + * @param showPointerIcon True if the pointer should be shown; false otherwise. The default + * visibility is true. + */ + @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) + @NonNull + public void setShowPointerIcon(boolean showPointerIcon) { + try { + mVirtualDevice.setShowPointerIcon(showPointerIcon); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + /** * Returns the display flags that should be added to a particular virtual display. * Additional device-level flags from {@link diff --git a/core/java/android/hardware/input/InputManagerInternal.java b/core/java/android/hardware/input/InputManagerInternal.java index cc9aeab7de556..3f20002c8028e 100644 --- a/core/java/android/hardware/input/InputManagerInternal.java +++ b/core/java/android/hardware/input/InputManagerInternal.java @@ -87,6 +87,12 @@ public abstract class InputManagerInternal { */ public abstract void setVirtualMousePointerDisplayId(int pointerDisplayId); + /** + * Gets the display id that the MouseCursorController is being forced to target. Returns + * {@link android.view.Display#INVALID_DISPLAY} if there is no override + */ + public abstract int getVirtualMousePointerDisplayId(); + /** Gets the current position of the mouse cursor. */ public abstract PointF getCursorPosition(); @@ -94,7 +100,7 @@ public abstract class InputManagerInternal { * Sets the pointer acceleration. * See {@code frameworks/native/include/input/VelocityControl.h#VelocityControlParameters}. */ - public abstract void setPointerAcceleration(float acceleration); + public abstract void setPointerAcceleration(float acceleration, int displayId); /** * Sets the eligibility of windows on a given display for pointer capture. If a display is @@ -103,6 +109,9 @@ public abstract class InputManagerInternal { */ public abstract void setDisplayEligibilityForPointerCapture(int displayId, boolean isEligible); + /** Sets the visibility of the cursor. */ + public abstract void setPointerIconVisible(boolean visible, int displayId); + /** Registers the {@link LidSwitchCallback} to begin receiving notifications. */ public abstract void registerLidSwitchCallback(@NonNull LidSwitchCallback callbacks); 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 e6bfd1ff7f1aa..9d4b50be41fb1 100644 --- a/services/companion/java/com/android/server/companion/virtual/InputController.java +++ b/services/companion/java/com/android/server/companion/virtual/InputController.java @@ -30,7 +30,6 @@ import android.hardware.input.VirtualMouseRelativeEvent; import android.hardware.input.VirtualMouseScrollEvent; import android.hardware.input.VirtualTouchEvent; import android.os.IBinder; -import android.os.IInputConstants; import android.os.RemoteException; import android.util.ArrayMap; import android.util.Slog; @@ -43,6 +42,7 @@ import com.android.server.LocalServices; import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.Iterator; import java.util.Map; import java.util.concurrent.atomic.AtomicLong; @@ -76,13 +76,6 @@ class InputController { private final DisplayManagerInternal mDisplayManagerInternal; private final InputManagerInternal mInputManagerInternal; - /** - * 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()); } @@ -91,18 +84,21 @@ class InputController { InputController(@NonNull Object lock, @NonNull NativeWrapper nativeWrapper) { mLock = lock; mNativeWrapper = nativeWrapper; - mActivePointerDisplayId = Display.INVALID_DISPLAY; mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class); mInputManagerInternal = LocalServices.getService(InputManagerInternal.class); } void close() { synchronized (mLock) { - for (InputDeviceDescriptor inputDeviceDescriptor : mInputDeviceDescriptors.values()) { - mNativeWrapper.closeUinput(inputDeviceDescriptor.getFileDescriptor()); + final Iterator> iterator = + mInputDeviceDescriptors.entrySet().iterator(); + if (iterator.hasNext()) { + final Map.Entry entry = iterator.next(); + final IBinder token = entry.getKey(); + final InputDeviceDescriptor inputDeviceDescriptor = entry.getValue(); + iterator.remove(); + closeInputDeviceDescriptorLocked(token, inputDeviceDescriptor); } - mInputDeviceDescriptors.clear(); - resetMouseValuesLocked(); } } @@ -150,8 +146,6 @@ class InputController { new InputDeviceDescriptor(fd, binderDeathRecipient, InputDeviceDescriptor.TYPE_MOUSE, displayId, phys)); mInputManagerInternal.setVirtualMousePointerDisplayId(displayId); - mInputManagerInternal.setPointerAcceleration(1); - mActivePointerDisplayId = displayId; } try { deviceToken.linkToDeath(binderDeathRecipient, /* flags= */ 0); @@ -197,23 +191,44 @@ class InputController { throw new IllegalArgumentException( "Could not unregister input device for given token"); } - token.unlinkToDeath(inputDeviceDescriptor.getDeathRecipient(), /* flags= */ 0); - mNativeWrapper.closeUinput(inputDeviceDescriptor.getFileDescriptor()); - InputManager.getInstance().removeUniqueIdAssociation(inputDeviceDescriptor.getPhys()); - - // 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(); - } + closeInputDeviceDescriptorLocked(token, inputDeviceDescriptor); } } @GuardedBy("mLock") - private void updateMouseValuesLocked() { + private void closeInputDeviceDescriptorLocked(IBinder token, + InputDeviceDescriptor inputDeviceDescriptor) { + token.unlinkToDeath(inputDeviceDescriptor.getDeathRecipient(), /* flags= */ 0); + mNativeWrapper.closeUinput(inputDeviceDescriptor.getFileDescriptor()); + InputManager.getInstance().removeUniqueIdAssociation(inputDeviceDescriptor.getPhys()); + + // Reset values to the default if all virtual mice are unregistered, or set display + // id if there's another mouse (choose the most recent). The inputDeviceDescriptor must be + // removed from the mInputDeviceDescriptors instance variable prior to this point. + if (inputDeviceDescriptor.isMouse()) { + if (mInputManagerInternal.getVirtualMousePointerDisplayId() + == inputDeviceDescriptor.getDisplayId()) { + updateActivePointerDisplayIdLocked(); + } + } + } + + void setShowPointerIcon(boolean visible, int displayId) { + mInputManagerInternal.setPointerIconVisible(visible, displayId); + } + + void setPointerAcceleration(float pointerAcceleration, int displayId) { + mInputManagerInternal.setPointerAcceleration(pointerAcceleration, displayId); + } + + void setDisplayEligibilityForPointerCapture(boolean isEligible, int displayId) { + mInputManagerInternal.setDisplayEligibilityForPointerCapture(displayId, isEligible); + } + + @GuardedBy("mLock") + private void updateActivePointerDisplayIdLocked() { InputDeviceDescriptor mostRecentlyCreatedMouse = null; - for (InputDeviceDescriptor otherInputDeviceDescriptor : - mInputDeviceDescriptors.values()) { + for (InputDeviceDescriptor otherInputDeviceDescriptor : mInputDeviceDescriptors.values()) { if (otherInputDeviceDescriptor.isMouse()) { if (mostRecentlyCreatedMouse == null || (otherInputDeviceDescriptor.getCreationOrderNumber() @@ -225,20 +240,12 @@ class InputController { if (mostRecentlyCreatedMouse != null) { mInputManagerInternal.setVirtualMousePointerDisplayId( mostRecentlyCreatedMouse.getDisplayId()); - mActivePointerDisplayId = mostRecentlyCreatedMouse.getDisplayId(); } else { - // All mice have been unregistered; reset all values. - resetMouseValuesLocked(); + // All mice have been unregistered + mInputManagerInternal.setVirtualMousePointerDisplayId(Display.INVALID_DISPLAY); } } - private void resetMouseValuesLocked() { - mInputManagerInternal.setVirtualMousePointerDisplayId(Display.INVALID_DISPLAY); - mInputManagerInternal.setPointerAcceleration( - IInputConstants.DEFAULT_POINTER_ACCELERATION); - mActivePointerDisplayId = Display.INVALID_DISPLAY; - } - private static String createPhys(@PhysType String type) { return String.format("virtual%s:%d", type, sNextPhysId.getAndIncrement()); } @@ -269,7 +276,8 @@ class InputController { throw new IllegalArgumentException( "Could not send button event to input device for given token"); } - if (inputDeviceDescriptor.getDisplayId() != mActivePointerDisplayId) { + if (inputDeviceDescriptor.getDisplayId() + != mInputManagerInternal.getVirtualMousePointerDisplayId()) { throw new IllegalStateException( "Display id associated with this mouse is not currently targetable"); } @@ -300,7 +308,8 @@ class InputController { throw new IllegalArgumentException( "Could not send relative event to input device for given token"); } - if (inputDeviceDescriptor.getDisplayId() != mActivePointerDisplayId) { + if (inputDeviceDescriptor.getDisplayId() + != mInputManagerInternal.getVirtualMousePointerDisplayId()) { throw new IllegalStateException( "Display id associated with this mouse is not currently targetable"); } @@ -317,7 +326,8 @@ class InputController { throw new IllegalArgumentException( "Could not send scroll event to input device for given token"); } - if (inputDeviceDescriptor.getDisplayId() != mActivePointerDisplayId) { + if (inputDeviceDescriptor.getDisplayId() + != mInputManagerInternal.getVirtualMousePointerDisplayId()) { throw new IllegalStateException( "Display id associated with this mouse is not currently targetable"); } @@ -334,7 +344,8 @@ class InputController { throw new IllegalArgumentException( "Could not get cursor position for input device for given token"); } - if (inputDeviceDescriptor.getDisplayId() != mActivePointerDisplayId) { + if (inputDeviceDescriptor.getDisplayId() + != mInputManagerInternal.getVirtualMousePointerDisplayId()) { throw new IllegalStateException( "Display id associated with this mouse is not currently targetable"); } @@ -354,7 +365,6 @@ class InputController { fout.println(" type: " + inputDeviceDescriptor.getType()); fout.println(" phys: " + inputDeviceDescriptor.getPhys()); } - fout.println(" Active mouse display id: " + mActivePointerDisplayId); } } 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 95b9e58a9dfdd..47e218b48c093 100644 --- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java +++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java @@ -37,7 +37,6 @@ 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; @@ -56,8 +55,8 @@ import android.util.Slog; import android.util.SparseArray; import android.window.DisplayWindowPolicyController; +import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; -import com.android.server.LocalServices; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -83,6 +82,9 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub private final VirtualDeviceParams mParams; private final Map mPerDisplayWakelocks = new ArrayMap<>(); private final IVirtualDeviceActivityListener mActivityListener; + // The default setting for showing the pointer on new displays. + @GuardedBy("mVirtualDeviceLock") + private boolean mDefaultShowPointerIcon = true; private ActivityListener createListenerAdapter(int displayId) { return new ActivityListener() { @@ -382,6 +384,25 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub } } + @Override // Binder call + public void setShowPointerIcon(boolean showPointerIcon) { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.CREATE_VIRTUAL_DEVICE, + "Permission required to unregister this input device"); + + final long binderToken = Binder.clearCallingIdentity(); + try { + synchronized (mVirtualDeviceLock) { + mDefaultShowPointerIcon = showPointerIcon; + for (int displayId : mVirtualDisplayIds) { + mInputController.setShowPointerIcon(mDefaultShowPointerIcon, displayId); + } + } + } finally { + Binder.restoreCallingIdentity(binderToken); + } + } + @Override protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) { fout.println(" VirtualDevice: "); @@ -392,6 +413,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub for (int id : mVirtualDisplayIds) { fout.println(" " + id); } + fout.println(" mDefaultShowPointerIcon: " + mDefaultShowPointerIcon); } mInputController.dump(fout); } @@ -403,15 +425,16 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub "Virtual device already have a virtual display with ID " + displayId); } mVirtualDisplayIds.add(displayId); + mInputController.setShowPointerIcon(mDefaultShowPointerIcon, displayId); + mInputController.setPointerAcceleration(1f, displayId); + mInputController.setDisplayEligibilityForPointerCapture(/* isEligible= */ false, + displayId); // Since we're being called in the middle of the display being created, we post a // task to grab the wakelock instead of doing it synchronously here, to avoid // reentrancy problems. mContext.getMainThreadHandler().post(() -> addWakeLockForDisplay(displayId)); - LocalServices.getService( - InputManagerInternal.class).setDisplayEligibilityForPointerCapture(displayId, - false); final GenericWindowPolicyController dwpc = new GenericWindowPolicyController(FLAG_SECURE, SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS, @@ -471,9 +494,6 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub mPerDisplayWakelocks.remove(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 bfaa7b37fa33e..a1321b78a0691 100644 --- a/services/core/java/com/android/server/input/InputManagerService.java +++ b/services/core/java/com/android/server/input/InputManagerService.java @@ -64,6 +64,7 @@ import android.os.CombinedVibration; import android.os.Environment; import android.os.Handler; import android.os.IBinder; +import android.os.IInputConstants; import android.os.IVibratorStateListener; import android.os.InputEventInjectionResult; import android.os.InputEventInjectionSync; @@ -274,10 +275,19 @@ 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(); + + private final Object mAdditionalDisplayInputPropertiesLock = new Object(); + // Forces the MouseCursorController to target a specific display id. - @GuardedBy("mPointerDisplayIdLock") + @GuardedBy("mAdditionalDisplayInputPropertiesLock") private int mOverriddenPointerDisplayId = Display.INVALID_DISPLAY; + @GuardedBy("mAdditionalDisplayInputPropertiesLock") + private final SparseArray mAdditionalDisplayInputProperties = + new SparseArray<>(); + @GuardedBy("mAdditionalDisplayInputPropertiesLock") + private int mIconType = PointerIcon.TYPE_NOT_SPECIFIED; + @GuardedBy("mAdditionalDisplayInputPropertiesLock") + private PointerIcon mIcon; // Holds all the registered gesture monitors that are implemented as spy windows. The spy @@ -609,11 +619,26 @@ public class InputManagerService extends IInputManager.Stub } private void setDisplayViewportsInternal(List viewports) { - final DisplayViewport[] vArray = new DisplayViewport[viewports.size()]; + synchronized (mAdditionalDisplayInputPropertiesLock) { + final DisplayViewport[] vArray = new DisplayViewport[viewports.size()]; for (int i = viewports.size() - 1; i >= 0; --i) { vArray[i] = viewports.get(i); } - nativeSetDisplayViewports(mPtr, vArray); + nativeSetDisplayViewports(mPtr, vArray); + + if (mOverriddenPointerDisplayId != Display.INVALID_DISPLAY) { + final AdditionalDisplayInputProperties properties = + mAdditionalDisplayInputProperties.get(mOverriddenPointerDisplayId); + if (properties != null) { + updatePointerIconVisibleLocked(properties.pointerIconVisible); + updatePointerAccelerationLocked(properties.pointerAcceleration); + return; + } + } + updatePointerIconVisibleLocked( + AdditionalDisplayInputProperties.DEFAULT_POINTER_ICON_VISIBLE); + updatePointerAccelerationLocked(IInputConstants.DEFAULT_POINTER_ACCELERATION); + } } /** @@ -1850,10 +1875,60 @@ public class InputManagerService extends IInputManager.Stub nativeSetPointerSpeed(mPtr, speed); } - private void setPointerAcceleration(float acceleration) { + private void setPointerAcceleration(float acceleration, int displayId) { + synchronized (mAdditionalDisplayInputPropertiesLock) { + AdditionalDisplayInputProperties properties = + mAdditionalDisplayInputProperties.get(displayId); + if (properties == null) { + properties = new AdditionalDisplayInputProperties(); + mAdditionalDisplayInputProperties.put(displayId, properties); + } + properties.pointerAcceleration = acceleration; + if (properties.allDefaults()) { + mAdditionalDisplayInputProperties.remove(displayId); + } + if (mOverriddenPointerDisplayId == displayId) { + updatePointerAccelerationLocked(acceleration); + } + } + } + + @GuardedBy("mAdditionalDisplayInputPropertiesLock") + private void updatePointerAccelerationLocked(float acceleration) { nativeSetPointerAcceleration(mPtr, acceleration); } + private void setPointerIconVisible(boolean visible, int displayId) { + synchronized (mAdditionalDisplayInputPropertiesLock) { + AdditionalDisplayInputProperties properties = + mAdditionalDisplayInputProperties.get(displayId); + if (properties == null) { + properties = new AdditionalDisplayInputProperties(); + mAdditionalDisplayInputProperties.put(displayId, properties); + } + properties.pointerIconVisible = visible; + if (properties.allDefaults()) { + mAdditionalDisplayInputProperties.remove(displayId); + } + if (mOverriddenPointerDisplayId == displayId) { + updatePointerIconVisibleLocked(visible); + } + } + } + + @GuardedBy("mAdditionalDisplayInputPropertiesLock") + private void updatePointerIconVisibleLocked(boolean visible) { + if (visible) { + if (mIconType == PointerIcon.TYPE_CUSTOM) { + nativeSetCustomPointerIcon(mPtr, mIcon); + } else { + nativeSetPointerIconType(mPtr, mIconType); + } + } else { + nativeSetPointerIconType(mPtr, PointerIcon.TYPE_NULL); + } + } + private void registerPointerSpeedSettingObserver() { mContext.getContentResolver().registerContentObserver( Settings.System.getUriFor(Settings.System.POINTER_SPEED), true, @@ -1988,13 +2063,27 @@ public class InputManagerService extends IInputManager.Stub } private void setVirtualMousePointerDisplayId(int displayId) { - synchronized (mPointerDisplayIdLock) { + synchronized (mAdditionalDisplayInputPropertiesLock) { mOverriddenPointerDisplayId = displayId; + if (displayId != Display.INVALID_DISPLAY) { + final AdditionalDisplayInputProperties properties = + mAdditionalDisplayInputProperties.get(displayId); + if (properties != null) { + updatePointerAccelerationLocked(properties.pointerAcceleration); + updatePointerIconVisibleLocked(properties.pointerIconVisible); + } + } } // TODO(b/215597605): trigger MousePositionTracker update nativeNotifyPointerDisplayIdChanged(mPtr); } + private int getVirtualMousePointerDisplayId() { + synchronized (mAdditionalDisplayInputPropertiesLock) { + return mOverriddenPointerDisplayId; + } + } + private void setDisplayEligibilityForPointerCapture(int displayId, boolean isEligible) { nativeSetDisplayEligibilityForPointerCapture(mPtr, displayId, isEligible); } @@ -2283,15 +2372,44 @@ public class InputManagerService extends IInputManager.Stub // Binder call @Override - public void setPointerIconType(int iconId) { - nativeSetPointerIconType(mPtr, iconId); + public void setPointerIconType(int iconType) { + if (iconType == PointerIcon.TYPE_CUSTOM) { + throw new IllegalArgumentException("Use setCustomPointerIcon to set custom pointers"); + } + synchronized (mAdditionalDisplayInputPropertiesLock) { + mIcon = null; + mIconType = iconType; + if (mOverriddenPointerDisplayId != Display.INVALID_DISPLAY) { + final AdditionalDisplayInputProperties properties = + mAdditionalDisplayInputProperties.get(mOverriddenPointerDisplayId); + if (properties == null || properties.pointerIconVisible) { + nativeSetPointerIconType(mPtr, mIconType); + } + } else { + nativeSetPointerIconType(mPtr, mIconType); + } + } } // Binder call @Override public void setCustomPointerIcon(PointerIcon icon) { Objects.requireNonNull(icon); - nativeSetCustomPointerIcon(mPtr, icon); + synchronized (mAdditionalDisplayInputPropertiesLock) { + mIconType = PointerIcon.TYPE_CUSTOM; + mIcon = icon; + if (mOverriddenPointerDisplayId != Display.INVALID_DISPLAY) { + final AdditionalDisplayInputProperties properties = + mAdditionalDisplayInputProperties.get(mOverriddenPointerDisplayId); + if (properties == null || properties.pointerIconVisible) { + // Only set the icon if it is not currently hidden; otherwise, it will be set + // once it's no longer hidden. + nativeSetCustomPointerIcon(mPtr, mIcon); + } + } else { + nativeSetCustomPointerIcon(mPtr, mIcon); + } + } } /** @@ -2620,6 +2738,7 @@ public class InputManagerService extends IInputManager.Stub pw.println("Input Manager Service (Java) State:"); dumpAssociations(pw, " " /*prefix*/); dumpSpyWindowGestureMonitors(pw, " " /*prefix*/); + dumpDisplayInputPropertiesValues(pw, " " /* prefix */); } private void dumpAssociations(PrintWriter pw, String prefix) { @@ -2660,6 +2779,25 @@ public class InputManagerService extends IInputManager.Stub } } + private void dumpDisplayInputPropertiesValues(PrintWriter pw, String prefix) { + synchronized (mAdditionalDisplayInputPropertiesLock) { + if (mAdditionalDisplayInputProperties.size() != 0) { + pw.println(prefix + "mAdditionalDisplayInputProperties:"); + for (int i = 0; i < mAdditionalDisplayInputProperties.size(); i++) { + pw.println(prefix + " displayId: " + + mAdditionalDisplayInputProperties.keyAt(i)); + final AdditionalDisplayInputProperties properties = + mAdditionalDisplayInputProperties.valueAt(i); + pw.println(prefix + " pointerAcceleration: " + properties.pointerAcceleration); + pw.println(prefix + " pointerIconVisible: " + properties.pointerIconVisible); + } + } + if (mOverriddenPointerDisplayId != Display.INVALID_DISPLAY) { + pw.println(prefix + "mOverriddenPointerDisplayId: " + mOverriddenPointerDisplayId); + } + } + } + private boolean checkCallingPermission(String permission, String func) { // Quick check: if the calling permission is me, it's all okay. if (Binder.getCallingPid() == Process.myPid()) { @@ -2683,8 +2821,8 @@ 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 */ } synchronized (mInputMonitors) { /* Test if blocked by input monitor lock. */ } + synchronized (mAdditionalDisplayInputPropertiesLock) { /* Test if blocked by props lock */ } nativeMonitor(mPtr); } @@ -3102,7 +3240,7 @@ public class InputManagerService extends IInputManager.Stub // Native callback. private int getPointerDisplayId() { - synchronized (mPointerDisplayIdLock) { + synchronized (mAdditionalDisplayInputPropertiesLock) { // Prefer the override to all other displays. if (mOverriddenPointerDisplayId != Display.INVALID_DISPLAY) { return mOverriddenPointerDisplayId; @@ -3591,14 +3729,19 @@ public class InputManagerService extends IInputManager.Stub InputManagerService.this.setVirtualMousePointerDisplayId(pointerDisplayId); } + @Override + public int getVirtualMousePointerDisplayId() { + return InputManagerService.this.getVirtualMousePointerDisplayId(); + } + @Override public PointF getCursorPosition() { return mWindowManagerCallbacks.getCursorPosition(); } @Override - public void setPointerAcceleration(float acceleration) { - InputManagerService.this.setPointerAcceleration(acceleration); + public void setPointerAcceleration(float acceleration, int displayId) { + InputManagerService.this.setPointerAcceleration(acceleration, displayId); } @Override @@ -3606,6 +3749,11 @@ public class InputManagerService extends IInputManager.Stub InputManagerService.this.setDisplayEligibilityForPointerCapture(displayId, isEligible); } + @Override + public void setPointerIconVisible(boolean visible, int displayId) { + InputManagerService.this.setPointerIconVisible(visible, displayId); + } + @Override public void registerLidSwitchCallback(LidSwitchCallback callbacks) { registerLidSwitchCallbackInternal(callbacks); @@ -3633,4 +3781,21 @@ public class InputManagerService extends IInputManager.Stub new InputShellCommand().exec(this, in, out, err, args, callback, resultReceiver); } + private static class AdditionalDisplayInputProperties { + + static final boolean DEFAULT_POINTER_ICON_VISIBLE = true; + static final float DEFAULT_POINTER_ACCELERATION = + (float) IInputConstants.DEFAULT_POINTER_ACCELERATION; + + // The pointer acceleration for this display. + public float pointerAcceleration = DEFAULT_POINTER_ACCELERATION; + + // Whether the pointer icon should be visible or hidden on this display. + public boolean pointerIconVisible = DEFAULT_POINTER_ICON_VISIBLE; + + public boolean allDefaults() { + return Float.compare(pointerAcceleration, DEFAULT_POINTER_ACCELERATION) == 0 + && pointerIconVisible == DEFAULT_POINTER_ICON_VISIBLE; + } + } } 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 index 83fa7ac025036..b4bb04d2b1b43 100644 --- a/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java @@ -21,7 +21,6 @@ import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import android.hardware.display.DisplayManagerInternal; @@ -30,7 +29,6 @@ import android.hardware.input.InputManager; import android.hardware.input.InputManagerInternal; import android.os.Binder; import android.os.IBinder; -import android.os.IInputConstants; import android.platform.test.annotations.Presubmit; import android.view.Display; import android.view.DisplayInfo; @@ -81,17 +79,15 @@ public class InputControllerTest { } @Test - public void unregisterInputDevice_allMiceUnregistered_unsetValues() { + public void unregisterInputDevice_allMiceUnregistered_clearPointerDisplayId() { final IBinder deviceToken = new Binder(); mInputController.createMouse("name", /*vendorId= */ 1, /*productId= */ 1, deviceToken, /* displayId= */ 1); verify(mInputManagerInternalMock).setVirtualMousePointerDisplayId(eq(1)); - verify(mInputManagerInternalMock).setPointerAcceleration(eq(1f)); + doReturn(1).when(mInputManagerInternalMock).getVirtualMousePointerDisplayId(); mInputController.unregisterInputDevice(deviceToken); verify(mInputManagerInternalMock).setVirtualMousePointerDisplayId( eq(Display.INVALID_DISPLAY)); - verify(mInputManagerInternalMock).setPointerAcceleration( - eq((float) IInputConstants.DEFAULT_POINTER_ACCELERATION)); } @Test @@ -100,14 +96,11 @@ public class InputControllerTest { mInputController.createMouse("name", /*vendorId= */ 1, /*productId= */ 1, deviceToken, /* displayId= */ 1); verify(mInputManagerInternalMock).setVirtualMousePointerDisplayId(eq(1)); - verify(mInputManagerInternalMock).setPointerAcceleration(eq(1f)); final IBinder deviceToken2 = new Binder(); mInputController.createMouse("name", /*vendorId= */ 1, /*productId= */ 1, deviceToken2, /* displayId= */ 2); verify(mInputManagerInternalMock).setVirtualMousePointerDisplayId(eq(2)); mInputController.unregisterInputDevice(deviceToken); verify(mInputManagerInternalMock).setVirtualMousePointerDisplayId(eq(1)); - verify(mInputManagerInternalMock, times(0)).setPointerAcceleration( - eq((float) IInputConstants.DEFAULT_POINTER_ACCELERATION)); } } 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 33540c874c0a7..3b4aece5997e2 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 @@ -19,6 +19,8 @@ package com.android.server.companion.virtual; import static com.google.common.truth.Truth.assertWithMessage; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyFloat; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; @@ -27,6 +29,7 @@ import static org.mockito.Mockito.doCallRealMethod; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.testng.Assert.assertThrows; @@ -117,6 +120,8 @@ public class VirtualDeviceManagerServiceTest { LocalServices.addService(DisplayManagerInternal.class, mDisplayManagerInternalMock); doNothing().when(mInputManagerInternalMock).setVirtualMousePointerDisplayId(anyInt()); + doNothing().when(mInputManagerInternalMock).setPointerAcceleration(anyFloat(), anyInt()); + doNothing().when(mInputManagerInternalMock).setPointerIconVisible(anyBoolean(), anyInt()); LocalServices.removeServiceForTest(InputManagerInternal.class); LocalServices.addService(InputManagerInternal.class, mInputManagerInternalMock); @@ -353,7 +358,7 @@ public class VirtualDeviceManagerServiceTest { mInputController.mInputDeviceDescriptors.put(BINDER, new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 2, /* displayId= */ 1, PHYS)); - mInputController.mActivePointerDisplayId = 1; + doReturn(1).when(mInputManagerInternalMock).getVirtualMousePointerDisplayId(); mDeviceImpl.sendButtonEvent(BINDER, new VirtualMouseButtonEvent.Builder() .setButtonCode(buttonCode) .setAction(action).build()); @@ -394,7 +399,7 @@ public class VirtualDeviceManagerServiceTest { mInputController.mInputDeviceDescriptors.put(BINDER, new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 2, /* displayId= */ 1, PHYS)); - mInputController.mActivePointerDisplayId = 1; + doReturn(1).when(mInputManagerInternalMock).getVirtualMousePointerDisplayId(); mDeviceImpl.sendRelativeEvent(BINDER, new VirtualMouseRelativeEvent.Builder() .setRelativeX(x).setRelativeY(y).build()); verify(mNativeWrapperMock).writeRelativeEvent(fd, x, y); @@ -435,7 +440,7 @@ public class VirtualDeviceManagerServiceTest { mInputController.mInputDeviceDescriptors.put(BINDER, new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 2, /* displayId= */ 1, PHYS)); - mInputController.mActivePointerDisplayId = 1; + doReturn(1).when(mInputManagerInternalMock).getVirtualMousePointerDisplayId(); mDeviceImpl.sendScrollEvent(BINDER, new VirtualMouseScrollEvent.Builder() .setXAxisMovement(x) .setYAxisMovement(y).build()); @@ -508,4 +513,19 @@ public class VirtualDeviceManagerServiceTest { verify(mNativeWrapperMock).writeTouchEvent(fd, pointerId, toolType, action, x, y, pressure, majorAxisSize); } + + @Test + public void setShowPointerIcon_setsValueForAllDisplays() { + mDeviceImpl.mVirtualDisplayIds.add(1); + mDeviceImpl.mVirtualDisplayIds.add(2); + mDeviceImpl.mVirtualDisplayIds.add(3); + mDeviceImpl.createVirtualMouse(1, DEVICE_NAME, VENDOR_ID, PRODUCT_ID, BINDER); + mDeviceImpl.createVirtualMouse(2, DEVICE_NAME, VENDOR_ID, PRODUCT_ID, BINDER); + mDeviceImpl.createVirtualMouse(3, DEVICE_NAME, VENDOR_ID, PRODUCT_ID, BINDER); + mDeviceImpl.setShowPointerIcon(false); + verify(mInputManagerInternalMock, times(3)).setPointerIconVisible(eq(false), anyInt()); + verify(mInputManagerInternalMock, never()).setPointerIconVisible(eq(true), anyInt()); + mDeviceImpl.setShowPointerIcon(true); + verify(mInputManagerInternalMock, times(3)).setPointerIconVisible(eq(true), anyInt()); + } }