Merge changes from topics "presubmit-am-e1ccffb4201944be9e638acb9f6f4dfc", "presubmit-am-e6236e948fb14688bc4afb3adb9e745b" into tm-dev
* changes: Synchronize pointer display change requests Make virtual input device creation synchronous
This commit is contained in:
committed by
Android (Google) Code Review
commit
dd2c72a65d
@@ -75,8 +75,15 @@ public abstract class InputManagerInternal {
|
||||
/**
|
||||
* Sets the display id that the MouseCursorController will be forced to target. Pass
|
||||
* {@link android.view.Display#INVALID_DISPLAY} to clear the override.
|
||||
*
|
||||
* Note: This method generally blocks until the pointer display override has propagated.
|
||||
* When setting a new override, the caller should ensure that an input device that can control
|
||||
* the mouse pointer is connected. If a new override is set when no such input device is
|
||||
* connected, the caller may be blocked for an arbitrary period of time.
|
||||
*
|
||||
* @return true if the pointer displayId was set successfully, or false if it fails.
|
||||
*/
|
||||
public abstract void setVirtualMousePointerDisplayId(int pointerDisplayId);
|
||||
public abstract boolean setVirtualMousePointerDisplayId(int pointerDisplayId);
|
||||
|
||||
/**
|
||||
* Gets the display id that the MouseCursorController is being forced to target. Returns
|
||||
|
||||
@@ -106,6 +106,7 @@ PointerController::PointerController(const sp<PointerControllerPolicyInterface>&
|
||||
PointerController::~PointerController() {
|
||||
mDisplayInfoListener->onPointerControllerDestroyed();
|
||||
mUnregisterWindowInfosListener(mDisplayInfoListener);
|
||||
mContext.getPolicy()->onPointerDisplayIdChanged(ADISPLAY_ID_NONE, 0, 0);
|
||||
}
|
||||
|
||||
std::mutex& PointerController::getLock() const {
|
||||
@@ -255,6 +256,12 @@ void PointerController::setDisplayViewport(const DisplayViewport& viewport) {
|
||||
getAdditionalMouseResources = true;
|
||||
}
|
||||
mCursorController.setDisplayViewport(viewport, getAdditionalMouseResources);
|
||||
if (viewport.displayId != mLocked.pointerDisplayId) {
|
||||
float xPos, yPos;
|
||||
mCursorController.getPosition(&xPos, &yPos);
|
||||
mContext.getPolicy()->onPointerDisplayIdChanged(viewport.displayId, xPos, yPos);
|
||||
mLocked.pointerDisplayId = viewport.displayId;
|
||||
}
|
||||
}
|
||||
|
||||
void PointerController::updatePointerIcon(int32_t iconId) {
|
||||
|
||||
@@ -104,6 +104,7 @@ private:
|
||||
|
||||
struct Locked {
|
||||
Presentation presentation;
|
||||
int32_t pointerDisplayId = ADISPLAY_ID_NONE;
|
||||
|
||||
std::vector<gui::DisplayInfo> mDisplayInfos;
|
||||
std::unordered_map<int32_t /* displayId */, TouchSpotController> spotControllers;
|
||||
|
||||
@@ -79,6 +79,7 @@ public:
|
||||
std::map<int32_t, PointerAnimation>* outAnimationResources, int32_t displayId) = 0;
|
||||
virtual int32_t getDefaultPointerIconId() = 0;
|
||||
virtual int32_t getCustomPointerIconId() = 0;
|
||||
virtual void onPointerDisplayIdChanged(int32_t displayId, float xPos, float yPos) = 0;
|
||||
};
|
||||
|
||||
/*
|
||||
|
||||
@@ -56,9 +56,11 @@ public:
|
||||
std::map<int32_t, PointerAnimation>* outAnimationResources, int32_t displayId) override;
|
||||
virtual int32_t getDefaultPointerIconId() override;
|
||||
virtual int32_t getCustomPointerIconId() override;
|
||||
virtual void onPointerDisplayIdChanged(int32_t displayId, float xPos, float yPos) override;
|
||||
|
||||
bool allResourcesAreLoaded();
|
||||
bool noResourcesAreLoaded();
|
||||
std::optional<int32_t> getLastReportedPointerDisplayId() { return latestPointerDisplayId; }
|
||||
|
||||
private:
|
||||
void loadPointerIconForType(SpriteIcon* icon, int32_t cursorType);
|
||||
@@ -66,6 +68,7 @@ private:
|
||||
bool pointerIconLoaded{false};
|
||||
bool pointerResourcesLoaded{false};
|
||||
bool additionalMouseResourcesLoaded{false};
|
||||
std::optional<int32_t /*displayId*/> latestPointerDisplayId;
|
||||
};
|
||||
|
||||
void MockPointerControllerPolicyInterface::loadPointerIcon(SpriteIcon* icon, int32_t) {
|
||||
@@ -126,12 +129,19 @@ void MockPointerControllerPolicyInterface::loadPointerIconForType(SpriteIcon* ic
|
||||
icon->hotSpotX = hotSpot.first;
|
||||
icon->hotSpotY = hotSpot.second;
|
||||
}
|
||||
|
||||
void MockPointerControllerPolicyInterface::onPointerDisplayIdChanged(int32_t displayId,
|
||||
float /*xPos*/,
|
||||
float /*yPos*/) {
|
||||
latestPointerDisplayId = displayId;
|
||||
}
|
||||
|
||||
class PointerControllerTest : public Test {
|
||||
protected:
|
||||
PointerControllerTest();
|
||||
~PointerControllerTest();
|
||||
|
||||
void ensureDisplayViewportIsSet();
|
||||
void ensureDisplayViewportIsSet(int32_t displayId = ADISPLAY_ID_DEFAULT);
|
||||
|
||||
sp<MockSprite> mPointerSprite;
|
||||
sp<MockPointerControllerPolicyInterface> mPolicy;
|
||||
@@ -168,9 +178,9 @@ PointerControllerTest::~PointerControllerTest() {
|
||||
mThread.join();
|
||||
}
|
||||
|
||||
void PointerControllerTest::ensureDisplayViewportIsSet() {
|
||||
void PointerControllerTest::ensureDisplayViewportIsSet(int32_t displayId) {
|
||||
DisplayViewport viewport;
|
||||
viewport.displayId = ADISPLAY_ID_DEFAULT;
|
||||
viewport.displayId = displayId;
|
||||
viewport.logicalRight = 1600;
|
||||
viewport.logicalBottom = 1200;
|
||||
viewport.physicalRight = 800;
|
||||
@@ -255,6 +265,30 @@ TEST_F(PointerControllerTest, doesNotGetResourcesBeforeSettingViewport) {
|
||||
ensureDisplayViewportIsSet();
|
||||
}
|
||||
|
||||
TEST_F(PointerControllerTest, notifiesPolicyWhenPointerDisplayChanges) {
|
||||
EXPECT_FALSE(mPolicy->getLastReportedPointerDisplayId())
|
||||
<< "A pointer display change does not occur when PointerController is created.";
|
||||
|
||||
ensureDisplayViewportIsSet(ADISPLAY_ID_DEFAULT);
|
||||
|
||||
const auto lastReportedPointerDisplayId = mPolicy->getLastReportedPointerDisplayId();
|
||||
ASSERT_TRUE(lastReportedPointerDisplayId)
|
||||
<< "The policy is notified of a pointer display change when the viewport is first set.";
|
||||
EXPECT_EQ(ADISPLAY_ID_DEFAULT, *lastReportedPointerDisplayId)
|
||||
<< "Incorrect pointer display notified.";
|
||||
|
||||
ensureDisplayViewportIsSet(42);
|
||||
|
||||
EXPECT_EQ(42, *mPolicy->getLastReportedPointerDisplayId())
|
||||
<< "The policy is notified when the pointer display changes.";
|
||||
|
||||
// Release the PointerController.
|
||||
mPointerController = nullptr;
|
||||
|
||||
EXPECT_EQ(ADISPLAY_ID_NONE, *mPolicy->getLastReportedPointerDisplayId())
|
||||
<< "The pointer display changes to invalid when PointerController is destroyed.";
|
||||
}
|
||||
|
||||
class PointerControllerWindowInfoListenerTest : public Test {};
|
||||
|
||||
class TestPointerController : public PointerController {
|
||||
|
||||
@@ -22,6 +22,7 @@ import android.annotation.StringDef;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.PointF;
|
||||
import android.hardware.display.DisplayManagerInternal;
|
||||
import android.hardware.input.InputDeviceIdentifier;
|
||||
import android.hardware.input.InputManager;
|
||||
import android.hardware.input.InputManagerInternal;
|
||||
import android.hardware.input.VirtualKeyEvent;
|
||||
@@ -29,11 +30,13 @@ import android.hardware.input.VirtualMouseButtonEvent;
|
||||
import android.hardware.input.VirtualMouseRelativeEvent;
|
||||
import android.hardware.input.VirtualMouseScrollEvent;
|
||||
import android.hardware.input.VirtualTouchEvent;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteException;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.Slog;
|
||||
import android.view.Display;
|
||||
import android.view.InputDevice;
|
||||
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
@@ -44,7 +47,11 @@ import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/** Controls virtual input devices, including device lifecycle and event dispatch. */
|
||||
class InputController {
|
||||
@@ -72,20 +79,27 @@ class InputController {
|
||||
@GuardedBy("mLock")
|
||||
final Map<IBinder, InputDeviceDescriptor> mInputDeviceDescriptors = new ArrayMap<>();
|
||||
|
||||
private final Handler mHandler;
|
||||
private final NativeWrapper mNativeWrapper;
|
||||
private final DisplayManagerInternal mDisplayManagerInternal;
|
||||
private final InputManagerInternal mInputManagerInternal;
|
||||
private final DeviceCreationThreadVerifier mThreadVerifier;
|
||||
|
||||
InputController(@NonNull Object lock) {
|
||||
this(lock, new NativeWrapper());
|
||||
InputController(@NonNull Object lock, @NonNull Handler handler) {
|
||||
this(lock, new NativeWrapper(), handler,
|
||||
// Verify that virtual devices are not created on the handler thread.
|
||||
() -> !handler.getLooper().isCurrentThread());
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
InputController(@NonNull Object lock, @NonNull NativeWrapper nativeWrapper) {
|
||||
InputController(@NonNull Object lock, @NonNull NativeWrapper nativeWrapper,
|
||||
@NonNull Handler handler, @NonNull DeviceCreationThreadVerifier threadVerifier) {
|
||||
mLock = lock;
|
||||
mHandler = handler;
|
||||
mNativeWrapper = nativeWrapper;
|
||||
mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class);
|
||||
mInputManagerInternal = LocalServices.getService(InputManagerInternal.class);
|
||||
mThreadVerifier = threadVerifier;
|
||||
}
|
||||
|
||||
void close() {
|
||||
@@ -108,23 +122,13 @@ class InputController {
|
||||
@NonNull IBinder deviceToken,
|
||||
int displayId) {
|
||||
final String phys = createPhys(PHYS_TYPE_KEYBOARD);
|
||||
setUniqueIdAssociation(displayId, phys);
|
||||
final int fd = mNativeWrapper.openUinputKeyboard(deviceName, vendorId, productId, phys);
|
||||
if (fd < 0) {
|
||||
throw new RuntimeException(
|
||||
"A native error occurred when creating keyboard: " + -fd);
|
||||
}
|
||||
final BinderDeathRecipient binderDeathRecipient = new BinderDeathRecipient(deviceToken);
|
||||
synchronized (mLock) {
|
||||
mInputDeviceDescriptors.put(deviceToken,
|
||||
new InputDeviceDescriptor(fd, binderDeathRecipient,
|
||||
InputDeviceDescriptor.TYPE_KEYBOARD, displayId, phys));
|
||||
}
|
||||
try {
|
||||
deviceToken.linkToDeath(binderDeathRecipient, /* flags= */ 0);
|
||||
} catch (RemoteException e) {
|
||||
// TODO(b/215608394): remove and close InputDeviceDescriptor
|
||||
throw new RuntimeException("Could not create virtual keyboard", e);
|
||||
createDeviceInternal(InputDeviceDescriptor.TYPE_KEYBOARD, deviceName, vendorId,
|
||||
productId, deviceToken, displayId, phys,
|
||||
() -> mNativeWrapper.openUinputKeyboard(deviceName, vendorId, productId, phys));
|
||||
} catch (DeviceCreationException e) {
|
||||
throw new RuntimeException(
|
||||
"Failed to create virtual keyboard device '" + deviceName + "'.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,25 +138,15 @@ class InputController {
|
||||
@NonNull IBinder deviceToken,
|
||||
int displayId) {
|
||||
final String phys = createPhys(PHYS_TYPE_MOUSE);
|
||||
setUniqueIdAssociation(displayId, phys);
|
||||
final int fd = mNativeWrapper.openUinputMouse(deviceName, vendorId, productId, phys);
|
||||
if (fd < 0) {
|
||||
throw new RuntimeException(
|
||||
"A native error occurred when creating mouse: " + -fd);
|
||||
}
|
||||
final BinderDeathRecipient binderDeathRecipient = new BinderDeathRecipient(deviceToken);
|
||||
synchronized (mLock) {
|
||||
mInputDeviceDescriptors.put(deviceToken,
|
||||
new InputDeviceDescriptor(fd, binderDeathRecipient,
|
||||
InputDeviceDescriptor.TYPE_MOUSE, displayId, phys));
|
||||
mInputManagerInternal.setVirtualMousePointerDisplayId(displayId);
|
||||
}
|
||||
try {
|
||||
deviceToken.linkToDeath(binderDeathRecipient, /* flags= */ 0);
|
||||
} catch (RemoteException e) {
|
||||
// TODO(b/215608394): remove and close InputDeviceDescriptor
|
||||
throw new RuntimeException("Could not create virtual mouse", e);
|
||||
createDeviceInternal(InputDeviceDescriptor.TYPE_MOUSE, deviceName, vendorId, productId,
|
||||
deviceToken, displayId, phys,
|
||||
() -> mNativeWrapper.openUinputMouse(deviceName, vendorId, productId, phys));
|
||||
} catch (DeviceCreationException e) {
|
||||
throw new RuntimeException(
|
||||
"Failed to create virtual mouse device: '" + deviceName + "'.", e);
|
||||
}
|
||||
mInputManagerInternal.setVirtualMousePointerDisplayId(displayId);
|
||||
}
|
||||
|
||||
void createTouchscreen(@NonNull String deviceName,
|
||||
@@ -162,24 +156,14 @@ class InputController {
|
||||
int displayId,
|
||||
@NonNull Point screenSize) {
|
||||
final String phys = createPhys(PHYS_TYPE_TOUCHSCREEN);
|
||||
setUniqueIdAssociation(displayId, phys);
|
||||
final int fd = mNativeWrapper.openUinputTouchscreen(deviceName, vendorId, productId, phys,
|
||||
screenSize.y, screenSize.x);
|
||||
if (fd < 0) {
|
||||
throw new RuntimeException(
|
||||
"A native error occurred when creating touchscreen: " + -fd);
|
||||
}
|
||||
final BinderDeathRecipient binderDeathRecipient = new BinderDeathRecipient(deviceToken);
|
||||
synchronized (mLock) {
|
||||
mInputDeviceDescriptors.put(deviceToken,
|
||||
new InputDeviceDescriptor(fd, binderDeathRecipient,
|
||||
InputDeviceDescriptor.TYPE_TOUCHSCREEN, displayId, phys));
|
||||
}
|
||||
try {
|
||||
deviceToken.linkToDeath(binderDeathRecipient, /* flags= */ 0);
|
||||
} catch (RemoteException e) {
|
||||
// TODO(b/215608394): remove and close InputDeviceDescriptor
|
||||
throw new RuntimeException("Could not create virtual touchscreen", e);
|
||||
createDeviceInternal(InputDeviceDescriptor.TYPE_TOUCHSCREEN, deviceName, vendorId,
|
||||
productId, deviceToken, displayId, phys,
|
||||
() -> mNativeWrapper.openUinputTouchscreen(deviceName, vendorId, productId,
|
||||
phys, screenSize.y, screenSize.x));
|
||||
} catch (DeviceCreationException e) {
|
||||
throw new RuntimeException(
|
||||
"Failed to create virtual touchscreen device '" + deviceName + "'.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -510,4 +494,133 @@ class InputController {
|
||||
unregisterInputDevice(mDeviceToken);
|
||||
}
|
||||
}
|
||||
|
||||
/** A helper class used to wait for an input device to be registered. */
|
||||
private class WaitForDevice implements AutoCloseable {
|
||||
private final CountDownLatch mDeviceAddedLatch = new CountDownLatch(1);
|
||||
private final InputManager.InputDeviceListener mListener;
|
||||
|
||||
WaitForDevice(String deviceName, int vendorId, int productId) {
|
||||
mListener = new InputManager.InputDeviceListener() {
|
||||
@Override
|
||||
public void onInputDeviceAdded(int deviceId) {
|
||||
final InputDevice device = InputManager.getInstance().getInputDevice(
|
||||
deviceId);
|
||||
Objects.requireNonNull(device, "Newly added input device was null.");
|
||||
if (!device.getName().equals(deviceName)) {
|
||||
return;
|
||||
}
|
||||
final InputDeviceIdentifier id = device.getIdentifier();
|
||||
if (id.getVendorId() != vendorId || id.getProductId() != productId) {
|
||||
return;
|
||||
}
|
||||
mDeviceAddedLatch.countDown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInputDeviceRemoved(int deviceId) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInputDeviceChanged(int deviceId) {
|
||||
|
||||
}
|
||||
};
|
||||
InputManager.getInstance().registerInputDeviceListener(mListener, mHandler);
|
||||
}
|
||||
|
||||
/** Note: This must not be called from {@link #mHandler}'s thread. */
|
||||
void waitForDeviceCreation() throws DeviceCreationException {
|
||||
try {
|
||||
if (!mDeviceAddedLatch.await(1, TimeUnit.MINUTES)) {
|
||||
throw new DeviceCreationException(
|
||||
"Timed out waiting for virtual device to be created.");
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
throw new DeviceCreationException(
|
||||
"Interrupted while waiting for virtual device to be created.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
InputManager.getInstance().unregisterInputDeviceListener(mListener);
|
||||
}
|
||||
}
|
||||
|
||||
/** An internal exception that is thrown to indicate an error when opening a virtual device. */
|
||||
private static class DeviceCreationException extends Exception {
|
||||
DeviceCreationException(String message) {
|
||||
super(message);
|
||||
}
|
||||
DeviceCreationException(String message, Exception cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a virtual input device synchronously, and waits for the notification that the device
|
||||
* was added.
|
||||
*
|
||||
* Note: Input device creation is expected to happen on a binder thread, and the calling thread
|
||||
* will be blocked until the input device creation is successful. This should not be called on
|
||||
* the handler's thread.
|
||||
*
|
||||
* @throws DeviceCreationException Throws this exception if anything unexpected happens in the
|
||||
* process of creating the device. This method will take care
|
||||
* to restore the state of the system in the event of any
|
||||
* unexpected behavior.
|
||||
*/
|
||||
private void createDeviceInternal(@InputDeviceDescriptor.Type int type, String deviceName,
|
||||
int vendorId, int productId, IBinder deviceToken, int displayId, String phys,
|
||||
Supplier<Integer> deviceOpener)
|
||||
throws DeviceCreationException {
|
||||
if (!mThreadVerifier.isValidThread()) {
|
||||
throw new IllegalStateException(
|
||||
"Virtual device creation should happen on an auxiliary thread (e.g. binder "
|
||||
+ "thread) and not from the handler's thread.");
|
||||
}
|
||||
|
||||
final int fd;
|
||||
final BinderDeathRecipient binderDeathRecipient;
|
||||
|
||||
setUniqueIdAssociation(displayId, phys);
|
||||
try (WaitForDevice waiter = new WaitForDevice(deviceName, vendorId, productId)) {
|
||||
fd = deviceOpener.get();
|
||||
if (fd < 0) {
|
||||
throw new DeviceCreationException(
|
||||
"A native error occurred when creating touchscreen: " + -fd);
|
||||
}
|
||||
// The fd is valid from here, so ensure that all failures close the fd after this point.
|
||||
try {
|
||||
waiter.waitForDeviceCreation();
|
||||
|
||||
binderDeathRecipient = new BinderDeathRecipient(deviceToken);
|
||||
try {
|
||||
deviceToken.linkToDeath(binderDeathRecipient, /* flags= */ 0);
|
||||
} catch (RemoteException e) {
|
||||
throw new DeviceCreationException(
|
||||
"Client died before virtual device could be created.", e);
|
||||
}
|
||||
} catch (DeviceCreationException e) {
|
||||
mNativeWrapper.closeUinput(fd);
|
||||
throw e;
|
||||
}
|
||||
} catch (DeviceCreationException e) {
|
||||
InputManager.getInstance().removeUniqueIdAssociation(phys);
|
||||
throw e;
|
||||
}
|
||||
|
||||
synchronized (mLock) {
|
||||
mInputDeviceDescriptors.put(deviceToken,
|
||||
new InputDeviceDescriptor(fd, binderDeathRecipient, type, displayId, phys));
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
interface DeviceCreationThreadVerifier {
|
||||
/** Returns true if the calling thread is a valid thread for device creation. */
|
||||
boolean isValidThread();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -166,7 +166,8 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
|
||||
mAppToken = token;
|
||||
mParams = params;
|
||||
if (inputController == null) {
|
||||
mInputController = new InputController(mVirtualDeviceLock);
|
||||
mInputController = new InputController(
|
||||
mVirtualDeviceLock, context.getMainThreadHandler());
|
||||
} else {
|
||||
mInputController = inputController;
|
||||
}
|
||||
|
||||
@@ -164,6 +164,7 @@ public class InputManagerService extends IInputManager.Stub
|
||||
private static final int MSG_UPDATE_KEYBOARD_LAYOUTS = 4;
|
||||
private static final int MSG_RELOAD_DEVICE_ALIASES = 5;
|
||||
private static final int MSG_DELIVER_TABLET_MODE_CHANGED = 6;
|
||||
private static final int MSG_POINTER_DISPLAY_ID_CHANGED = 7;
|
||||
|
||||
private static final int DEFAULT_VIBRATION_MAGNITUDE = 192;
|
||||
|
||||
@@ -276,11 +277,24 @@ public class InputManagerService extends IInputManager.Stub
|
||||
@GuardedBy("mAssociationLock")
|
||||
private final Map<String, String> mUniqueIdAssociations = new ArrayMap<>();
|
||||
|
||||
// Guards per-display input properties and properties relating to the mouse pointer.
|
||||
// Threads can wait on this lock to be notified the next time the display on which the mouse
|
||||
// pointer is shown has changed.
|
||||
private final Object mAdditionalDisplayInputPropertiesLock = new Object();
|
||||
|
||||
// Forces the MouseCursorController to target a specific display id.
|
||||
// Forces the PointerController to target a specific display id.
|
||||
@GuardedBy("mAdditionalDisplayInputPropertiesLock")
|
||||
private int mOverriddenPointerDisplayId = Display.INVALID_DISPLAY;
|
||||
|
||||
// PointerController is the source of truth of the pointer display. This is the value of the
|
||||
// latest pointer display id reported by PointerController.
|
||||
@GuardedBy("mAdditionalDisplayInputPropertiesLock")
|
||||
private int mAcknowledgedPointerDisplayId = Display.INVALID_DISPLAY;
|
||||
// This is the latest display id that IMS has requested PointerController to use. If there are
|
||||
// no devices that can control the pointer, PointerController may end up disregarding this
|
||||
// value.
|
||||
@GuardedBy("mAdditionalDisplayInputPropertiesLock")
|
||||
private int mRequestedPointerDisplayId = Display.INVALID_DISPLAY;
|
||||
@GuardedBy("mAdditionalDisplayInputPropertiesLock")
|
||||
private final SparseArray<AdditionalDisplayInputProperties> mAdditionalDisplayInputProperties =
|
||||
new SparseArray<>();
|
||||
@@ -289,7 +303,6 @@ public class InputManagerService extends IInputManager.Stub
|
||||
@GuardedBy("mAdditionalDisplayInputPropertiesLock")
|
||||
private PointerIcon mIcon;
|
||||
|
||||
|
||||
// Holds all the registered gesture monitors that are implemented as spy windows. The spy
|
||||
// windows are mapped by their InputChannel tokens.
|
||||
@GuardedBy("mInputMonitors")
|
||||
@@ -383,6 +396,10 @@ public class InputManagerService extends IInputManager.Stub
|
||||
NativeInputManagerService getNativeService(InputManagerService service) {
|
||||
return new NativeInputManagerService.NativeImpl(service, mContext, mLooper.getQueue());
|
||||
}
|
||||
|
||||
void registerLocalService(InputManagerInternal localService) {
|
||||
LocalServices.addService(InputManagerInternal.class, localService);
|
||||
}
|
||||
}
|
||||
|
||||
public InputManagerService(Context context) {
|
||||
@@ -409,7 +426,7 @@ public class InputManagerService extends IInputManager.Stub
|
||||
mDoubleTouchGestureEnableFile = TextUtils.isEmpty(doubleTouchGestureEnablePath) ? null :
|
||||
new File(doubleTouchGestureEnablePath);
|
||||
|
||||
LocalServices.addService(InputManagerInternal.class, new LocalService());
|
||||
injector.registerLocalService(new LocalService());
|
||||
}
|
||||
|
||||
public void setWindowManagerCallbacks(WindowManagerCallbacks callbacks) {
|
||||
@@ -559,6 +576,8 @@ public class InputManagerService extends IInputManager.Stub
|
||||
vArray[i] = viewports.get(i);
|
||||
}
|
||||
mNative.setDisplayViewports(vArray);
|
||||
// Always attempt to update the pointer display when viewports change.
|
||||
updatePointerDisplayId();
|
||||
|
||||
if (mOverriddenPointerDisplayId != Display.INVALID_DISPLAY) {
|
||||
final AdditionalDisplayInputProperties properties =
|
||||
@@ -1964,10 +1983,43 @@ public class InputManagerService extends IInputManager.Stub
|
||||
return result;
|
||||
}
|
||||
|
||||
private void setVirtualMousePointerDisplayId(int displayId) {
|
||||
/**
|
||||
* Update the display on which the mouse pointer is shown.
|
||||
* If there is an overridden display for the mouse pointer, use that. Otherwise, query
|
||||
* WindowManager for the pointer display.
|
||||
*
|
||||
* @return true if the pointer displayId changed, false otherwise.
|
||||
*/
|
||||
private boolean updatePointerDisplayId() {
|
||||
synchronized (mAdditionalDisplayInputPropertiesLock) {
|
||||
final int pointerDisplayId = mOverriddenPointerDisplayId != Display.INVALID_DISPLAY
|
||||
? mOverriddenPointerDisplayId : mWindowManagerCallbacks.getPointerDisplayId();
|
||||
if (mRequestedPointerDisplayId == pointerDisplayId) {
|
||||
return false;
|
||||
}
|
||||
mRequestedPointerDisplayId = pointerDisplayId;
|
||||
mNative.setPointerDisplayId(pointerDisplayId);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private void handlePointerDisplayIdChanged(PointerDisplayIdChangedArgs args) {
|
||||
synchronized (mAdditionalDisplayInputPropertiesLock) {
|
||||
mAcknowledgedPointerDisplayId = args.mPointerDisplayId;
|
||||
// Notify waiting threads that the display of the mouse pointer has changed.
|
||||
mAdditionalDisplayInputPropertiesLock.notifyAll();
|
||||
}
|
||||
mWindowManagerCallbacks.notifyPointerDisplayIdChanged(
|
||||
args.mPointerDisplayId, args.mXPosition, args.mYPosition);
|
||||
}
|
||||
|
||||
private boolean setVirtualMousePointerDisplayIdBlocking(int displayId) {
|
||||
// Indicates whether this request is for removing the override.
|
||||
final boolean removingOverride = displayId == Display.INVALID_DISPLAY;
|
||||
|
||||
synchronized (mAdditionalDisplayInputPropertiesLock) {
|
||||
mOverriddenPointerDisplayId = displayId;
|
||||
if (displayId != Display.INVALID_DISPLAY) {
|
||||
if (!removingOverride) {
|
||||
final AdditionalDisplayInputProperties properties =
|
||||
mAdditionalDisplayInputProperties.get(displayId);
|
||||
if (properties != null) {
|
||||
@@ -1975,9 +2027,30 @@ public class InputManagerService extends IInputManager.Stub
|
||||
updatePointerIconVisibleLocked(properties.pointerIconVisible);
|
||||
}
|
||||
}
|
||||
if (!updatePointerDisplayId() && mAcknowledgedPointerDisplayId == displayId) {
|
||||
// The requested pointer display is already set.
|
||||
return true;
|
||||
}
|
||||
if (removingOverride && mAcknowledgedPointerDisplayId == Display.INVALID_DISPLAY) {
|
||||
// The pointer display override is being removed, but the current pointer display
|
||||
// is already invalid. This can happen when the PointerController is destroyed as a
|
||||
// result of the removal of all input devices that can control the pointer.
|
||||
return true;
|
||||
}
|
||||
try {
|
||||
// The pointer display changed, so wait until the change has propagated.
|
||||
mAdditionalDisplayInputPropertiesLock.wait(5_000 /*mills*/);
|
||||
} catch (InterruptedException ignored) {
|
||||
}
|
||||
// This request succeeds in two cases:
|
||||
// - This request was to remove the override, in which case the new pointer display
|
||||
// could be anything that WM has set.
|
||||
// - We are setting a new override, in which case the request only succeeds if the
|
||||
// reported new displayId is the one we requested. This check ensures that if two
|
||||
// competing overrides are requested in succession, the caller can be notified if one
|
||||
// of them fails.
|
||||
return removingOverride || mAcknowledgedPointerDisplayId == displayId;
|
||||
}
|
||||
// TODO(b/215597605): trigger MousePositionTracker update
|
||||
mNative.notifyPointerDisplayIdChanged();
|
||||
}
|
||||
|
||||
private int getVirtualMousePointerDisplayId() {
|
||||
@@ -3157,18 +3230,6 @@ public class InputManagerService extends IInputManager.Stub
|
||||
return mContext.createDisplayContext(display);
|
||||
}
|
||||
|
||||
// Native callback.
|
||||
@SuppressWarnings("unused")
|
||||
private int getPointerDisplayId() {
|
||||
synchronized (mAdditionalDisplayInputPropertiesLock) {
|
||||
// Prefer the override to all other displays.
|
||||
if (mOverriddenPointerDisplayId != Display.INVALID_DISPLAY) {
|
||||
return mOverriddenPointerDisplayId;
|
||||
}
|
||||
}
|
||||
return mWindowManagerCallbacks.getPointerDisplayId();
|
||||
}
|
||||
|
||||
// Native callback.
|
||||
@SuppressWarnings("unused")
|
||||
private String[] getKeyboardLayoutOverlay(InputDeviceIdentifier identifier) {
|
||||
@@ -3209,6 +3270,26 @@ public class InputManagerService extends IInputManager.Stub
|
||||
return null;
|
||||
}
|
||||
|
||||
private static class PointerDisplayIdChangedArgs {
|
||||
final int mPointerDisplayId;
|
||||
final float mXPosition;
|
||||
final float mYPosition;
|
||||
PointerDisplayIdChangedArgs(int pointerDisplayId, float xPosition, float yPosition) {
|
||||
mPointerDisplayId = pointerDisplayId;
|
||||
mXPosition = xPosition;
|
||||
mYPosition = yPosition;
|
||||
}
|
||||
}
|
||||
|
||||
// Native callback.
|
||||
@SuppressWarnings("unused")
|
||||
@VisibleForTesting
|
||||
void onPointerDisplayIdChanged(int pointerDisplayId, float xPosition, float yPosition) {
|
||||
mHandler.obtainMessage(MSG_POINTER_DISPLAY_ID_CHANGED,
|
||||
new PointerDisplayIdChangedArgs(pointerDisplayId, xPosition,
|
||||
yPosition)).sendToTarget();
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback interface implemented by the Window Manager.
|
||||
*/
|
||||
@@ -3332,6 +3413,14 @@ public class InputManagerService extends IInputManager.Stub
|
||||
*/
|
||||
@Nullable
|
||||
SurfaceControl createSurfaceForGestureMonitor(String name, int displayId);
|
||||
|
||||
/**
|
||||
* Notify WindowManagerService when the display of the mouse pointer changes.
|
||||
* @param displayId The display on which the mouse pointer is shown.
|
||||
* @param x The x coordinate of the mouse pointer.
|
||||
* @param y The y coordinate of the mouse pointer.
|
||||
*/
|
||||
void notifyPointerDisplayIdChanged(int displayId, float x, float y);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -3384,6 +3473,9 @@ public class InputManagerService extends IInputManager.Stub
|
||||
boolean inTabletMode = (boolean) args.arg1;
|
||||
deliverTabletModeChanged(whenNanos, inTabletMode);
|
||||
break;
|
||||
case MSG_POINTER_DISPLAY_ID_CHANGED:
|
||||
handlePointerDisplayIdChanged((PointerDisplayIdChangedArgs) msg.obj);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3634,8 +3726,9 @@ public class InputManagerService extends IInputManager.Stub
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setVirtualMousePointerDisplayId(int pointerDisplayId) {
|
||||
InputManagerService.this.setVirtualMousePointerDisplayId(pointerDisplayId);
|
||||
public boolean setVirtualMousePointerDisplayId(int pointerDisplayId) {
|
||||
return InputManagerService.this
|
||||
.setVirtualMousePointerDisplayIdBlocking(pointerDisplayId);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -176,6 +176,9 @@ public interface NativeInputManagerService {
|
||||
|
||||
void cancelCurrentTouch();
|
||||
|
||||
/** Set the displayId on which the mouse cursor should be shown. */
|
||||
void setPointerDisplayId(int displayId);
|
||||
|
||||
/** The native implementation of InputManagerService methods. */
|
||||
class NativeImpl implements NativeInputManagerService {
|
||||
/** Pointer to native input manager service object, used by native code. */
|
||||
@@ -388,5 +391,8 @@ public interface NativeInputManagerService {
|
||||
|
||||
@Override
|
||||
public native void cancelCurrentTouch();
|
||||
|
||||
@Override
|
||||
public native void setPointerDisplayId(int displayId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -270,6 +270,22 @@ final class InputManagerCallback implements InputManagerService.WindowManagerCal
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyPointerDisplayIdChanged(int displayId, float x, float y) {
|
||||
synchronized (mService.mGlobalLock) {
|
||||
mService.setMousePointerDisplayId(displayId);
|
||||
if (displayId == Display.INVALID_DISPLAY) return;
|
||||
|
||||
final DisplayContent dc = mService.mRoot.getDisplayContent(displayId);
|
||||
if (dc == null) {
|
||||
Slog.wtf(TAG, "The mouse pointer was moved to display " + displayId
|
||||
+ " that does not have a valid DisplayContent.");
|
||||
return;
|
||||
}
|
||||
mService.restorePointerIconLocked(dc, x, y);
|
||||
}
|
||||
}
|
||||
|
||||
/** Waits until the built-in input devices have been configured. */
|
||||
public boolean waitForInputDevicesReady(long timeoutMillis) {
|
||||
synchronized (mInputDevicesReadyMonitor) {
|
||||
|
||||
@@ -7208,18 +7208,42 @@ public class WindowManagerService extends IWindowManager.Stub
|
||||
private float mLatestMouseX;
|
||||
private float mLatestMouseY;
|
||||
|
||||
void updatePosition(float x, float y) {
|
||||
/**
|
||||
* The display that the pointer (mouse cursor) is currently shown on. This is updated
|
||||
* directly by InputManagerService when the pointer display changes.
|
||||
*/
|
||||
private int mPointerDisplayId = INVALID_DISPLAY;
|
||||
|
||||
/**
|
||||
* Update the mouse cursor position as a result of a mouse movement.
|
||||
* @return true if the position was successfully updated, false otherwise.
|
||||
*/
|
||||
boolean updatePosition(int displayId, float x, float y) {
|
||||
synchronized (this) {
|
||||
mLatestEventWasMouse = true;
|
||||
|
||||
if (displayId != mPointerDisplayId) {
|
||||
// The display of the position update does not match the display on which the
|
||||
// mouse pointer is shown, so do not update the position.
|
||||
return false;
|
||||
}
|
||||
mLatestMouseX = x;
|
||||
mLatestMouseY = y;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
void setPointerDisplayId(int displayId) {
|
||||
synchronized (this) {
|
||||
mPointerDisplayId = displayId;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPointerEvent(MotionEvent motionEvent) {
|
||||
if (motionEvent.isFromSource(InputDevice.SOURCE_MOUSE)) {
|
||||
updatePosition(motionEvent.getRawX(), motionEvent.getRawY());
|
||||
updatePosition(motionEvent.getDisplayId(), motionEvent.getRawX(),
|
||||
motionEvent.getRawY());
|
||||
} else {
|
||||
synchronized (this) {
|
||||
mLatestEventWasMouse = false;
|
||||
@@ -7229,6 +7253,7 @@ public class WindowManagerService extends IWindowManager.Stub
|
||||
};
|
||||
|
||||
void updatePointerIcon(IWindow client) {
|
||||
int pointerDisplayId;
|
||||
float mouseX, mouseY;
|
||||
|
||||
synchronized(mMousePositionTracker) {
|
||||
@@ -7237,6 +7262,7 @@ public class WindowManagerService extends IWindowManager.Stub
|
||||
}
|
||||
mouseX = mMousePositionTracker.mLatestMouseX;
|
||||
mouseY = mMousePositionTracker.mLatestMouseY;
|
||||
pointerDisplayId = mMousePositionTracker.mPointerDisplayId;
|
||||
}
|
||||
|
||||
synchronized (mGlobalLock) {
|
||||
@@ -7253,6 +7279,10 @@ public class WindowManagerService extends IWindowManager.Stub
|
||||
if (displayContent == null) {
|
||||
return;
|
||||
}
|
||||
if (pointerDisplayId != displayContent.getDisplayId()) {
|
||||
// Do not let the pointer icon be updated by a window on a different display.
|
||||
return;
|
||||
}
|
||||
WindowState windowUnderPointer =
|
||||
displayContent.getTouchableWinAtPointLocked(mouseX, mouseY);
|
||||
if (windowUnderPointer != callingWin) {
|
||||
@@ -7270,7 +7300,11 @@ public class WindowManagerService extends IWindowManager.Stub
|
||||
|
||||
void restorePointerIconLocked(DisplayContent displayContent, float latestX, float latestY) {
|
||||
// Mouse position tracker has not been getting updates while dragging, update it now.
|
||||
mMousePositionTracker.updatePosition(latestX, latestY);
|
||||
if (!mMousePositionTracker.updatePosition(
|
||||
displayContent.getDisplayId(), latestX, latestY)) {
|
||||
// The mouse position could not be updated, so ignore this request.
|
||||
return;
|
||||
}
|
||||
|
||||
WindowState windowUnderPointer =
|
||||
displayContent.getTouchableWinAtPointLocked(latestX, latestY);
|
||||
@@ -7294,6 +7328,10 @@ public class WindowManagerService extends IWindowManager.Stub
|
||||
}
|
||||
}
|
||||
|
||||
void setMousePointerDisplayId(int displayId) {
|
||||
mMousePositionTracker.setPointerDisplayId(displayId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a tap exclude region in the window identified by the provided id. Touches down on this
|
||||
* region will not:
|
||||
|
||||
@@ -107,6 +107,7 @@ static struct {
|
||||
jmethodID interceptKeyBeforeDispatching;
|
||||
jmethodID dispatchUnhandledKey;
|
||||
jmethodID checkInjectEventsPermission;
|
||||
jmethodID onPointerDisplayIdChanged;
|
||||
jmethodID onPointerDownOutsideFocus;
|
||||
jmethodID getVirtualKeyQuietTimeMillis;
|
||||
jmethodID getExcludedDeviceNames;
|
||||
@@ -120,7 +121,6 @@ static struct {
|
||||
jmethodID getLongPressTimeout;
|
||||
jmethodID getPointerLayer;
|
||||
jmethodID getPointerIcon;
|
||||
jmethodID getPointerDisplayId;
|
||||
jmethodID getKeyboardLayoutOverlay;
|
||||
jmethodID getDeviceAlias;
|
||||
jmethodID getTouchCalibrationForInputDevice;
|
||||
@@ -277,6 +277,7 @@ public:
|
||||
void setFocusedDisplay(int32_t displayId);
|
||||
void setInputDispatchMode(bool enabled, bool frozen);
|
||||
void setSystemUiLightsOut(bool lightsOut);
|
||||
void setPointerDisplayId(int32_t displayId);
|
||||
void setPointerSpeed(int32_t speed);
|
||||
void setPointerAcceleration(float acceleration);
|
||||
void setInputDeviceEnabled(uint32_t deviceId, bool enabled);
|
||||
@@ -288,7 +289,6 @@ public:
|
||||
void requestPointerCapture(const sp<IBinder>& windowToken, bool enabled);
|
||||
void setCustomPointerIcon(const SpriteIcon& icon);
|
||||
void setMotionClassifierEnabled(bool enabled);
|
||||
void notifyPointerDisplayIdChanged();
|
||||
|
||||
/* --- InputReaderPolicyInterface implementation --- */
|
||||
|
||||
@@ -346,6 +346,7 @@ public:
|
||||
std::map<int32_t, PointerAnimation>* outAnimationResources, int32_t displayId);
|
||||
virtual int32_t getDefaultPointerIconId();
|
||||
virtual int32_t getCustomPointerIconId();
|
||||
virtual void onPointerDisplayIdChanged(int32_t displayId, float xPos, float yPos);
|
||||
|
||||
private:
|
||||
sp<InputManagerInterface> mInputManager;
|
||||
@@ -394,7 +395,6 @@ private:
|
||||
void updateInactivityTimeoutLocked();
|
||||
void handleInterceptActions(jint wmActions, nsecs_t when, uint32_t& policyFlags);
|
||||
void ensureSpriteControllerLocked();
|
||||
int32_t getPointerDisplayId();
|
||||
sp<SurfaceControl> getParentSurfaceForPointers(int displayId);
|
||||
static bool checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodName);
|
||||
|
||||
@@ -498,13 +498,9 @@ void NativeInputManager::setDisplayViewports(JNIEnv* env, jobjectArray viewportO
|
||||
}
|
||||
}
|
||||
|
||||
// Get the preferred pointer controller displayId.
|
||||
int32_t pointerDisplayId = getPointerDisplayId();
|
||||
|
||||
{ // acquire lock
|
||||
AutoMutex _l(mLock);
|
||||
mLocked.viewports = viewports;
|
||||
mLocked.pointerDisplayId = pointerDisplayId;
|
||||
std::shared_ptr<PointerController> controller = mLocked.pointerController.lock();
|
||||
if (controller != nullptr) {
|
||||
controller->onDisplayViewportsUpdated(mLocked.viewports);
|
||||
@@ -666,15 +662,12 @@ std::shared_ptr<PointerControllerInterface> NativeInputManager::obtainPointerCon
|
||||
return controller;
|
||||
}
|
||||
|
||||
int32_t NativeInputManager::getPointerDisplayId() {
|
||||
void NativeInputManager::onPointerDisplayIdChanged(int32_t pointerDisplayId, float xPos,
|
||||
float yPos) {
|
||||
JNIEnv* env = jniEnv();
|
||||
jint pointerDisplayId = env->CallIntMethod(mServiceObj,
|
||||
gServiceClassInfo.getPointerDisplayId);
|
||||
if (checkAndClearExceptionFromCallback(env, "getPointerDisplayId")) {
|
||||
pointerDisplayId = ADISPLAY_ID_DEFAULT;
|
||||
}
|
||||
|
||||
return pointerDisplayId;
|
||||
env->CallVoidMethod(mServiceObj, gServiceClassInfo.onPointerDisplayIdChanged, pointerDisplayId,
|
||||
xPos, yPos);
|
||||
checkAndClearExceptionFromCallback(env, "onPointerDisplayIdChanged");
|
||||
}
|
||||
|
||||
sp<SurfaceControl> NativeInputManager::getParentSurfaceForPointers(int displayId) {
|
||||
@@ -1032,6 +1025,22 @@ void NativeInputManager::updateInactivityTimeoutLocked() REQUIRES(mLock) {
|
||||
: InactivityTimeout::NORMAL);
|
||||
}
|
||||
|
||||
void NativeInputManager::setPointerDisplayId(int32_t displayId) {
|
||||
{ // acquire lock
|
||||
AutoMutex _l(mLock);
|
||||
|
||||
if (mLocked.pointerDisplayId == displayId) {
|
||||
return;
|
||||
}
|
||||
|
||||
ALOGI("Setting pointer display id to %d.", displayId);
|
||||
mLocked.pointerDisplayId = displayId;
|
||||
} // release lock
|
||||
|
||||
mInputManager->getReader().requestRefreshConfiguration(
|
||||
InputReaderConfiguration::CHANGE_DISPLAY_INFO);
|
||||
}
|
||||
|
||||
void NativeInputManager::setPointerSpeed(int32_t speed) {
|
||||
{ // acquire lock
|
||||
AutoMutex _l(mLock);
|
||||
@@ -1494,18 +1503,6 @@ 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 NativeInputManager* getNativeInputManager(JNIEnv* env, jobject clazz) {
|
||||
@@ -2199,11 +2196,6 @@ static void nativeNotifyPortAssociationsChanged(JNIEnv* env, jobject nativeImplO
|
||||
InputReaderConfiguration::CHANGE_DISPLAY_INFO);
|
||||
}
|
||||
|
||||
static void nativeNotifyPointerDisplayIdChanged(JNIEnv* env, jobject nativeImplObj) {
|
||||
NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
|
||||
im->notifyPointerDisplayIdChanged();
|
||||
}
|
||||
|
||||
static void nativeSetDisplayEligibilityForPointerCapture(JNIEnv* env, jobject nativeImplObj,
|
||||
jint displayId, jboolean isEligible) {
|
||||
NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
|
||||
@@ -2321,6 +2313,11 @@ static void nativeCancelCurrentTouch(JNIEnv* env, jobject nativeImplObj) {
|
||||
im->getInputManager()->getDispatcher().cancelCurrentTouch();
|
||||
}
|
||||
|
||||
static void nativeSetPointerDisplayId(JNIEnv* env, jobject nativeImplObj, jint displayId) {
|
||||
NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
|
||||
im->setPointerDisplayId(displayId);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
static const JNINativeMethod gInputManagerMethods[] = {
|
||||
@@ -2393,7 +2390,6 @@ static const JNINativeMethod gInputManagerMethods[] = {
|
||||
{"canDispatchToDisplay", "(II)Z", (void*)nativeCanDispatchToDisplay},
|
||||
{"notifyPortAssociationsChanged", "()V", (void*)nativeNotifyPortAssociationsChanged},
|
||||
{"changeUniqueIdAssociation", "()V", (void*)nativeChangeUniqueIdAssociation},
|
||||
{"notifyPointerDisplayIdChanged", "()V", (void*)nativeNotifyPointerDisplayIdChanged},
|
||||
{"setDisplayEligibilityForPointerCapture", "(IZ)V",
|
||||
(void*)nativeSetDisplayEligibilityForPointerCapture},
|
||||
{"setMotionClassifierEnabled", "(Z)V", (void*)nativeSetMotionClassifierEnabled},
|
||||
@@ -2403,6 +2399,7 @@ static const JNINativeMethod gInputManagerMethods[] = {
|
||||
{"disableSensor", "(II)V", (void*)nativeDisableSensor},
|
||||
{"flushSensor", "(II)Z", (void*)nativeFlushSensor},
|
||||
{"cancelCurrentTouch", "()V", (void*)nativeCancelCurrentTouch},
|
||||
{"setPointerDisplayId", "(I)V", (void*)nativeSetPointerDisplayId},
|
||||
};
|
||||
|
||||
#define FIND_CLASS(var, className) \
|
||||
@@ -2498,6 +2495,9 @@ int register_android_server_InputManager(JNIEnv* env) {
|
||||
GET_METHOD_ID(gServiceClassInfo.checkInjectEventsPermission, clazz,
|
||||
"checkInjectEventsPermission", "(II)Z");
|
||||
|
||||
GET_METHOD_ID(gServiceClassInfo.onPointerDisplayIdChanged, clazz, "onPointerDisplayIdChanged",
|
||||
"(IFF)V");
|
||||
|
||||
GET_METHOD_ID(gServiceClassInfo.onPointerDownOutsideFocus, clazz,
|
||||
"onPointerDownOutsideFocus", "(Landroid/os/IBinder;)V");
|
||||
|
||||
@@ -2537,9 +2537,6 @@ int register_android_server_InputManager(JNIEnv* env) {
|
||||
GET_METHOD_ID(gServiceClassInfo.getPointerIcon, clazz,
|
||||
"getPointerIcon", "(I)Landroid/view/PointerIcon;");
|
||||
|
||||
GET_METHOD_ID(gServiceClassInfo.getPointerDisplayId, clazz,
|
||||
"getPointerDisplayId", "()I");
|
||||
|
||||
GET_METHOD_ID(gServiceClassInfo.getKeyboardLayoutOverlay, clazz,
|
||||
"getKeyboardLayoutOverlay",
|
||||
"(Landroid/hardware/input/InputDeviceIdentifier;)[Ljava/lang/String;");
|
||||
|
||||
@@ -19,22 +19,22 @@ package com.android.server.companion.virtual;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
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;
|
||||
import android.hardware.input.IInputManager;
|
||||
import android.hardware.input.InputManager;
|
||||
import android.hardware.input.InputManagerInternal;
|
||||
import android.os.Binder;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.platform.test.annotations.Presubmit;
|
||||
import android.testing.AndroidTestingRunner;
|
||||
import android.testing.TestableLooper;
|
||||
import android.view.Display;
|
||||
import android.view.DisplayInfo;
|
||||
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import com.android.server.LocalServices;
|
||||
|
||||
import org.junit.Before;
|
||||
@@ -44,7 +44,8 @@ import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
@Presubmit
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@RunWith(AndroidTestingRunner.class)
|
||||
@TestableLooper.RunWithLooper(setAsMainLooper = true)
|
||||
public class InputControllerTest {
|
||||
|
||||
@Mock
|
||||
@@ -56,13 +57,16 @@ public class InputControllerTest {
|
||||
@Mock
|
||||
private IInputManager mIInputManagerMock;
|
||||
|
||||
private InputManagerMockHelper mInputManagerMockHelper;
|
||||
private InputController mInputController;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
mInputManagerMockHelper = new InputManagerMockHelper(
|
||||
TestableLooper.get(this), mNativeWrapperMock, mIInputManagerMock);
|
||||
|
||||
doNothing().when(mInputManagerInternalMock).setVirtualMousePointerDisplayId(anyInt());
|
||||
doReturn(true).when(mInputManagerInternalMock).setVirtualMousePointerDisplayId(anyInt());
|
||||
LocalServices.removeServiceForTest(InputManagerInternal.class);
|
||||
LocalServices.addService(InputManagerInternal.class, mInputManagerInternalMock);
|
||||
|
||||
@@ -72,10 +76,10 @@ public class InputControllerTest {
|
||||
LocalServices.removeServiceForTest(DisplayManagerInternal.class);
|
||||
LocalServices.addService(DisplayManagerInternal.class, mDisplayManagerInternalMock);
|
||||
|
||||
InputManager.resetInstance(mIInputManagerMock);
|
||||
doNothing().when(mIInputManagerMock).addUniqueIdAssociation(anyString(), anyString());
|
||||
doNothing().when(mIInputManagerMock).removeUniqueIdAssociation(anyString());
|
||||
mInputController = new InputController(new Object(), mNativeWrapperMock);
|
||||
// Allow virtual devices to be created on the looper thread for testing.
|
||||
final InputController.DeviceCreationThreadVerifier threadVerifier = () -> true;
|
||||
mInputController = new InputController(new Object(), mNativeWrapperMock,
|
||||
new Handler(TestableLooper.get(this).getLooper()), threadVerifier);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -83,6 +87,7 @@ public class InputControllerTest {
|
||||
final IBinder deviceToken = new Binder();
|
||||
mInputController.createMouse("name", /*vendorId= */ 1, /*productId= */ 1, deviceToken,
|
||||
/* displayId= */ 1);
|
||||
verify(mNativeWrapperMock).openUinputMouse(eq("name"), eq(1), eq(1), anyString());
|
||||
verify(mInputManagerInternalMock).setVirtualMousePointerDisplayId(eq(1));
|
||||
doReturn(1).when(mInputManagerInternalMock).getVirtualMousePointerDisplayId();
|
||||
mInputController.unregisterInputDevice(deviceToken);
|
||||
@@ -95,10 +100,12 @@ public class InputControllerTest {
|
||||
final IBinder deviceToken = new Binder();
|
||||
mInputController.createMouse("name", /*vendorId= */ 1, /*productId= */ 1, deviceToken,
|
||||
/* displayId= */ 1);
|
||||
verify(mNativeWrapperMock).openUinputMouse(eq("name"), eq(1), eq(1), anyString());
|
||||
verify(mInputManagerInternalMock).setVirtualMousePointerDisplayId(eq(1));
|
||||
final IBinder deviceToken2 = new Binder();
|
||||
mInputController.createMouse("name", /*vendorId= */ 1, /*productId= */ 1, deviceToken2,
|
||||
/* displayId= */ 2);
|
||||
verify(mNativeWrapperMock, times(2)).openUinputMouse(eq("name"), eq(1), eq(1), anyString());
|
||||
verify(mInputManagerInternalMock).setVirtualMousePointerDisplayId(eq(2));
|
||||
mInputController.unregisterInputDevice(deviceToken);
|
||||
verify(mInputManagerInternalMock).setVirtualMousePointerDisplayId(eq(1));
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* 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.anyString;
|
||||
import static org.mockito.ArgumentMatchers.notNull;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.doNothing;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.hardware.input.IInputDevicesChangedListener;
|
||||
import android.hardware.input.IInputManager;
|
||||
import android.hardware.input.InputManager;
|
||||
import android.os.RemoteException;
|
||||
import android.testing.TestableLooper;
|
||||
import android.view.InputDevice;
|
||||
|
||||
import org.mockito.invocation.InvocationOnMock;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
/**
|
||||
* A test utility class used to share the logic for setting up {@link InputManager}'s callback for
|
||||
* when a virtual input device being added.
|
||||
*/
|
||||
class InputManagerMockHelper {
|
||||
private final TestableLooper mTestableLooper;
|
||||
private final InputController.NativeWrapper mNativeWrapperMock;
|
||||
private final IInputManager mIInputManagerMock;
|
||||
private final List<InputDevice> mDevices = new ArrayList<>();
|
||||
private IInputDevicesChangedListener mDevicesChangedListener;
|
||||
|
||||
InputManagerMockHelper(TestableLooper testableLooper,
|
||||
InputController.NativeWrapper nativeWrapperMock, IInputManager iInputManagerMock)
|
||||
throws Exception {
|
||||
mTestableLooper = testableLooper;
|
||||
mNativeWrapperMock = nativeWrapperMock;
|
||||
mIInputManagerMock = iInputManagerMock;
|
||||
|
||||
doAnswer(this::handleNativeOpenInputDevice).when(mNativeWrapperMock).openUinputMouse(
|
||||
anyString(), anyInt(), anyInt(), anyString());
|
||||
doAnswer(this::handleNativeOpenInputDevice).when(mNativeWrapperMock).openUinputKeyboard(
|
||||
anyString(), anyInt(), anyInt(), anyString());
|
||||
doAnswer(this::handleNativeOpenInputDevice).when(mNativeWrapperMock).openUinputTouchscreen(
|
||||
anyString(), anyInt(), anyInt(), anyString(), anyInt(), anyInt());
|
||||
|
||||
doAnswer(inv -> {
|
||||
mDevicesChangedListener = inv.getArgument(0);
|
||||
return null;
|
||||
}).when(mIInputManagerMock).registerInputDevicesChangedListener(notNull());
|
||||
when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[0]);
|
||||
doAnswer(inv -> mDevices.get(inv.getArgument(0)))
|
||||
.when(mIInputManagerMock).getInputDevice(anyInt());
|
||||
doNothing().when(mIInputManagerMock).addUniqueIdAssociation(anyString(), anyString());
|
||||
doNothing().when(mIInputManagerMock).removeUniqueIdAssociation(anyString());
|
||||
|
||||
// Set a new instance of InputManager for testing that uses the IInputManager mock as the
|
||||
// interface to the server.
|
||||
InputManager.resetInstance(mIInputManagerMock);
|
||||
}
|
||||
|
||||
private Void handleNativeOpenInputDevice(InvocationOnMock inv) {
|
||||
Objects.requireNonNull(mDevicesChangedListener,
|
||||
"InputController did not register an InputDevicesChangedListener.");
|
||||
// We only use a subset of the fields of InputDevice in InputController.
|
||||
final InputDevice device = new InputDevice(mDevices.size() /*id*/, 1 /*generation*/, 0,
|
||||
inv.getArgument(0) /*name*/, inv.getArgument(1) /*vendorId*/,
|
||||
inv.getArgument(2) /*productId*/, inv.getArgument(3) /*descriptor*/,
|
||||
true /*isExternal*/, 0 /*sources*/, 0 /*keyboardType*/,
|
||||
null /*keyCharacterMap*/, false /*hasVibrator*/, false /*hasMic*/,
|
||||
false /*hasButtonUnderPad*/, false /*hasSensor*/, false /*hasBattery*/);
|
||||
mDevices.add(device);
|
||||
try {
|
||||
mDevicesChangedListener.onInputDevicesChanged(
|
||||
mDevices.stream().flatMapToInt(
|
||||
d -> IntStream.of(d.getId(), d.getGeneration())).toArray());
|
||||
} catch (RemoteException ignored) {
|
||||
}
|
||||
// Process the device added notification.
|
||||
mTestableLooper.processAllMessages();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -54,6 +54,7 @@ import android.content.pm.ActivityInfo;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.graphics.Point;
|
||||
import android.hardware.display.DisplayManagerInternal;
|
||||
import android.hardware.input.IInputManager;
|
||||
import android.hardware.input.InputManagerInternal;
|
||||
import android.hardware.input.VirtualKeyEvent;
|
||||
import android.hardware.input.VirtualMouseButtonEvent;
|
||||
@@ -118,6 +119,7 @@ public class VirtualDeviceManagerServiceTest {
|
||||
private static final int FLAG_CANNOT_DISPLAY_ON_REMOTE_DEVICES = 0x00000;
|
||||
|
||||
private Context mContext;
|
||||
private InputManagerMockHelper mInputManagerMockHelper;
|
||||
private VirtualDeviceImpl mDeviceImpl;
|
||||
private InputController mInputController;
|
||||
private AssociationInfo mAssociationInfo;
|
||||
@@ -146,6 +148,8 @@ public class VirtualDeviceManagerServiceTest {
|
||||
private IAudioConfigChangedCallback mConfigChangedCallback;
|
||||
@Mock
|
||||
private ApplicationInfo mApplicationInfoMock;
|
||||
@Mock
|
||||
IInputManager mIInputManagerMock;
|
||||
|
||||
private ArraySet<ComponentName> getBlockedActivities() {
|
||||
ArraySet<ComponentName> blockedActivities = new ArraySet<>();
|
||||
@@ -170,13 +174,13 @@ public class VirtualDeviceManagerServiceTest {
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
public void setUp() throws Exception {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
|
||||
LocalServices.removeServiceForTest(DisplayManagerInternal.class);
|
||||
LocalServices.addService(DisplayManagerInternal.class, mDisplayManagerInternalMock);
|
||||
|
||||
doNothing().when(mInputManagerInternalMock).setVirtualMousePointerDisplayId(anyInt());
|
||||
doReturn(true).when(mInputManagerInternalMock).setVirtualMousePointerDisplayId(anyInt());
|
||||
doNothing().when(mInputManagerInternalMock).setPointerAcceleration(anyFloat(), anyInt());
|
||||
doNothing().when(mInputManagerInternalMock).setPointerIconVisible(anyBoolean(), anyInt());
|
||||
LocalServices.removeServiceForTest(InputManagerInternal.class);
|
||||
@@ -199,7 +203,13 @@ public class VirtualDeviceManagerServiceTest {
|
||||
new Handler(TestableLooper.get(this).getLooper()));
|
||||
when(mContext.getSystemService(Context.POWER_SERVICE)).thenReturn(mPowerManager);
|
||||
|
||||
mInputController = new InputController(new Object(), mNativeWrapperMock);
|
||||
mInputManagerMockHelper = new InputManagerMockHelper(
|
||||
TestableLooper.get(this), mNativeWrapperMock, mIInputManagerMock);
|
||||
// Allow virtual devices to be created on the looper thread for testing.
|
||||
final InputController.DeviceCreationThreadVerifier threadVerifier = () -> true;
|
||||
mInputController = new InputController(new Object(), mNativeWrapperMock,
|
||||
new Handler(TestableLooper.get(this).getLooper()), threadVerifier);
|
||||
|
||||
mAssociationInfo = new AssociationInfo(1, 0, null,
|
||||
MacAddress.BROADCAST_ADDRESS, "", null, true, false, 0, 0);
|
||||
|
||||
|
||||
@@ -0,0 +1,222 @@
|
||||
/*
|
||||
* 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.input
|
||||
|
||||
import android.content.Context
|
||||
import android.content.ContextWrapper
|
||||
import android.hardware.display.DisplayViewport
|
||||
import android.hardware.input.InputManagerInternal
|
||||
import android.os.test.TestLooper
|
||||
import android.platform.test.annotations.Presubmit
|
||||
import android.view.Display
|
||||
import androidx.test.InstrumentationRegistry
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.mockito.ArgumentMatchers.any
|
||||
import org.mockito.ArgumentMatchers.anyInt
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Mockito.`when`
|
||||
import org.mockito.Mockito.doAnswer
|
||||
import org.mockito.Mockito.never
|
||||
import org.mockito.Mockito.spy
|
||||
import org.mockito.Mockito.times
|
||||
import org.mockito.Mockito.verify
|
||||
import org.mockito.junit.MockitoJUnit
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
/**
|
||||
* Tests for {@link InputManagerService}.
|
||||
*
|
||||
* Build/Install/Run:
|
||||
* atest FrameworksServicesTests:InputManagerServiceTests
|
||||
*/
|
||||
@Presubmit
|
||||
class InputManagerServiceTests {
|
||||
|
||||
@get:Rule
|
||||
val rule = MockitoJUnit.rule()!!
|
||||
|
||||
@Mock
|
||||
private lateinit var native: NativeInputManagerService
|
||||
|
||||
@Mock
|
||||
private lateinit var wmCallbacks: InputManagerService.WindowManagerCallbacks
|
||||
|
||||
private lateinit var service: InputManagerService
|
||||
private lateinit var localService: InputManagerInternal
|
||||
private lateinit var context: Context
|
||||
private lateinit var testLooper: TestLooper
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
context = spy(ContextWrapper(InstrumentationRegistry.getContext()))
|
||||
testLooper = TestLooper()
|
||||
service =
|
||||
InputManagerService(object : InputManagerService.Injector(context, testLooper.looper) {
|
||||
override fun getNativeService(
|
||||
service: InputManagerService?
|
||||
): NativeInputManagerService {
|
||||
return native
|
||||
}
|
||||
|
||||
override fun registerLocalService(service: InputManagerInternal?) {
|
||||
localService = service!!
|
||||
}
|
||||
})
|
||||
assertTrue("Local service must be registered", this::localService.isInitialized)
|
||||
service.setWindowManagerCallbacks(wmCallbacks)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testPointerDisplayUpdatesWhenDisplayViewportsChanged() {
|
||||
val displayId = 123
|
||||
`when`(wmCallbacks.pointerDisplayId).thenReturn(displayId)
|
||||
val viewports = listOf<DisplayViewport>()
|
||||
localService.setDisplayViewports(viewports)
|
||||
verify(native).setDisplayViewports(any(Array<DisplayViewport>::class.java))
|
||||
verify(native).setPointerDisplayId(displayId)
|
||||
|
||||
val x = 42f
|
||||
val y = 314f
|
||||
service.onPointerDisplayIdChanged(displayId, x, y)
|
||||
testLooper.dispatchNext()
|
||||
verify(wmCallbacks).notifyPointerDisplayIdChanged(displayId, x, y)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSetVirtualMousePointerDisplayId() {
|
||||
// Set the virtual mouse pointer displayId, and ensure that the calling thread is blocked
|
||||
// until the native callback happens.
|
||||
var countDownLatch = CountDownLatch(1)
|
||||
val overrideDisplayId = 123
|
||||
Thread {
|
||||
assertTrue("Setting virtual pointer display should succeed",
|
||||
localService.setVirtualMousePointerDisplayId(overrideDisplayId))
|
||||
countDownLatch.countDown()
|
||||
}.start()
|
||||
assertFalse("Setting virtual pointer display should block",
|
||||
countDownLatch.await(100, TimeUnit.MILLISECONDS))
|
||||
|
||||
val x = 42f
|
||||
val y = 314f
|
||||
service.onPointerDisplayIdChanged(overrideDisplayId, x, y)
|
||||
testLooper.dispatchNext()
|
||||
verify(wmCallbacks).notifyPointerDisplayIdChanged(overrideDisplayId, x, y)
|
||||
assertTrue("Native callback unblocks calling thread",
|
||||
countDownLatch.await(100, TimeUnit.MILLISECONDS))
|
||||
verify(native).setPointerDisplayId(overrideDisplayId)
|
||||
|
||||
// Ensure that setting the same override again succeeds immediately.
|
||||
assertTrue("Setting the same virtual mouse pointer displayId again should succeed",
|
||||
localService.setVirtualMousePointerDisplayId(overrideDisplayId))
|
||||
|
||||
// Ensure that we did not query WM for the pointerDisplayId when setting the override
|
||||
verify(wmCallbacks, never()).pointerDisplayId
|
||||
|
||||
// Unset the virtual mouse pointer displayId, and ensure that we query WM for the new
|
||||
// pointer displayId and the calling thread is blocked until the native callback happens.
|
||||
countDownLatch = CountDownLatch(1)
|
||||
val pointerDisplayId = 42
|
||||
`when`(wmCallbacks.pointerDisplayId).thenReturn(pointerDisplayId)
|
||||
Thread {
|
||||
assertTrue("Unsetting virtual mouse pointer displayId should succeed",
|
||||
localService.setVirtualMousePointerDisplayId(Display.INVALID_DISPLAY))
|
||||
countDownLatch.countDown()
|
||||
}.start()
|
||||
assertFalse("Unsetting virtual mouse pointer displayId should block",
|
||||
countDownLatch.await(100, TimeUnit.MILLISECONDS))
|
||||
|
||||
service.onPointerDisplayIdChanged(pointerDisplayId, x, y)
|
||||
testLooper.dispatchNext()
|
||||
verify(wmCallbacks).notifyPointerDisplayIdChanged(pointerDisplayId, x, y)
|
||||
assertTrue("Native callback unblocks calling thread",
|
||||
countDownLatch.await(100, TimeUnit.MILLISECONDS))
|
||||
verify(native).setPointerDisplayId(pointerDisplayId)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSetVirtualMousePointerDisplayId_unsuccessfulUpdate() {
|
||||
// Set the virtual mouse pointer displayId, and ensure that the calling thread is blocked
|
||||
// until the native callback happens.
|
||||
val countDownLatch = CountDownLatch(1)
|
||||
val overrideDisplayId = 123
|
||||
Thread {
|
||||
assertFalse("Setting virtual pointer display should be unsuccessful",
|
||||
localService.setVirtualMousePointerDisplayId(overrideDisplayId))
|
||||
countDownLatch.countDown()
|
||||
}.start()
|
||||
assertFalse("Setting virtual pointer display should block",
|
||||
countDownLatch.await(100, TimeUnit.MILLISECONDS))
|
||||
|
||||
val x = 42f
|
||||
val y = 314f
|
||||
// Assume the native callback updates the pointerDisplayId to the incorrect value.
|
||||
service.onPointerDisplayIdChanged(Display.INVALID_DISPLAY, x, y)
|
||||
testLooper.dispatchNext()
|
||||
verify(wmCallbacks).notifyPointerDisplayIdChanged(Display.INVALID_DISPLAY, x, y)
|
||||
assertTrue("Native callback unblocks calling thread",
|
||||
countDownLatch.await(100, TimeUnit.MILLISECONDS))
|
||||
verify(native).setPointerDisplayId(overrideDisplayId)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSetVirtualMousePointerDisplayId_competingRequests() {
|
||||
val firstRequestSyncLatch = CountDownLatch(1)
|
||||
doAnswer {
|
||||
firstRequestSyncLatch.countDown()
|
||||
}.`when`(native).setPointerDisplayId(anyInt())
|
||||
|
||||
val firstRequestLatch = CountDownLatch(1)
|
||||
val firstOverride = 123
|
||||
Thread {
|
||||
assertFalse("Setting virtual pointer display from thread 1 should be unsuccessful",
|
||||
localService.setVirtualMousePointerDisplayId(firstOverride))
|
||||
firstRequestLatch.countDown()
|
||||
}.start()
|
||||
assertFalse("Setting virtual pointer display should block",
|
||||
firstRequestLatch.await(100, TimeUnit.MILLISECONDS))
|
||||
|
||||
assertTrue("Wait for first thread's request should succeed",
|
||||
firstRequestSyncLatch.await(100, TimeUnit.MILLISECONDS))
|
||||
|
||||
val secondRequestLatch = CountDownLatch(1)
|
||||
val secondOverride = 42
|
||||
Thread {
|
||||
assertTrue("Setting virtual mouse pointer from thread 2 should be successful",
|
||||
localService.setVirtualMousePointerDisplayId(secondOverride))
|
||||
secondRequestLatch.countDown()
|
||||
}.start()
|
||||
assertFalse("Setting virtual mouse pointer should block",
|
||||
secondRequestLatch.await(100, TimeUnit.MILLISECONDS))
|
||||
|
||||
val x = 42f
|
||||
val y = 314f
|
||||
// Assume the native callback updates directly to the second request.
|
||||
service.onPointerDisplayIdChanged(secondOverride, x, y)
|
||||
testLooper.dispatchNext()
|
||||
verify(wmCallbacks).notifyPointerDisplayIdChanged(secondOverride, x, y)
|
||||
assertTrue("Native callback unblocks first thread",
|
||||
firstRequestLatch.await(100, TimeUnit.MILLISECONDS))
|
||||
assertTrue("Native callback unblocks second thread",
|
||||
secondRequestLatch.await(100, TimeUnit.MILLISECONDS))
|
||||
verify(native, times(2)).setPointerDisplayId(anyInt())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user