Merge changes from topics "pointer-icon-refactor-setPointerIcon", "setPointerIcon-hitTest" into main
* changes:
InputDispatcher: Add hit test method isPointerInWindow
Address comments: Add setPointerIcon for PointerChoreographer
Add setPointerIcon for PointerChoreographer
diff --git a/services/inputflinger/PointerChoreographer.cpp b/services/inputflinger/PointerChoreographer.cpp
index f1faf69..07a5041 100644
--- a/services/inputflinger/PointerChoreographer.cpp
+++ b/services/inputflinger/PointerChoreographer.cpp
@@ -445,6 +445,59 @@
updatePointerControllersLocked();
}
+bool PointerChoreographer::setPointerIcon(
+ std::variant<std::unique_ptr<SpriteIcon>, PointerIconStyle> icon, int32_t displayId,
+ DeviceId deviceId) {
+ std::scoped_lock _l(mLock);
+ if (deviceId < 0) {
+ LOG(WARNING) << "Invalid device id " << deviceId << ". Cannot set pointer icon.";
+ return false;
+ }
+ const InputDeviceInfo* info = findInputDeviceLocked(deviceId);
+ if (!info) {
+ LOG(WARNING) << "No input device info found for id " << deviceId
+ << ". Cannot set pointer icon.";
+ return false;
+ }
+ const uint32_t sources = info->getSources();
+ const auto stylusPointerIt = mStylusPointersByDevice.find(deviceId);
+
+ if (isFromSource(sources, AINPUT_SOURCE_STYLUS) &&
+ stylusPointerIt != mStylusPointersByDevice.end()) {
+ if (std::holds_alternative<std::unique_ptr<SpriteIcon>>(icon)) {
+ if (std::get<std::unique_ptr<SpriteIcon>>(icon) == nullptr) {
+ LOG(FATAL) << "SpriteIcon should not be null";
+ }
+ stylusPointerIt->second->setCustomPointerIcon(
+ *std::get<std::unique_ptr<SpriteIcon>>(icon));
+ } else {
+ stylusPointerIt->second->updatePointerIcon(std::get<PointerIconStyle>(icon));
+ }
+ } else if (isFromSource(sources, AINPUT_SOURCE_MOUSE)) {
+ if (const auto mousePointerIt = mMousePointersByDisplay.find(displayId);
+ mousePointerIt != mMousePointersByDisplay.end()) {
+ if (std::holds_alternative<std::unique_ptr<SpriteIcon>>(icon)) {
+ if (std::get<std::unique_ptr<SpriteIcon>>(icon) == nullptr) {
+ LOG(FATAL) << "SpriteIcon should not be null";
+ }
+ mousePointerIt->second->setCustomPointerIcon(
+ *std::get<std::unique_ptr<SpriteIcon>>(icon));
+ } else {
+ mousePointerIt->second->updatePointerIcon(std::get<PointerIconStyle>(icon));
+ }
+ } else {
+ LOG(WARNING) << "No mouse pointer controller found for display " << displayId
+ << ", device " << deviceId << ".";
+ return false;
+ }
+ } else {
+ LOG(WARNING) << "Cannot set pointer icon for display " << displayId << ", device "
+ << deviceId << ".";
+ return false;
+ }
+ return true;
+}
+
PointerChoreographer::ControllerConstructor PointerChoreographer::getMouseControllerConstructor(
int32_t displayId) {
std::function<std::shared_ptr<PointerControllerInterface>()> ctor =
diff --git a/services/inputflinger/PointerChoreographer.h b/services/inputflinger/PointerChoreographer.h
index 90a0d3f..9b809a1 100644
--- a/services/inputflinger/PointerChoreographer.h
+++ b/services/inputflinger/PointerChoreographer.h
@@ -25,6 +25,8 @@
namespace android {
+struct SpriteIcon;
+
/**
* A helper class that wraps a factory method that acts as a constructor for the type returned
* by the factory method.
@@ -59,6 +61,14 @@
virtual void setShowTouchesEnabled(bool enabled) = 0;
virtual void setStylusPointerIconEnabled(bool enabled) = 0;
/**
+ * Set the icon that is shown for the given pointer. The request may fail in some cases, such
+ * as if the device or display was removed, or if the cursor was moved to a different display.
+ * Returns true if the icon was changed successfully, false otherwise.
+ */
+ virtual bool setPointerIcon(std::variant<std::unique_ptr<SpriteIcon>, PointerIconStyle> icon,
+ int32_t displayId, DeviceId deviceId) = 0;
+
+ /**
* This method may be called on any thread (usually by the input manager on a binder thread).
*/
virtual void dump(std::string& dump) = 0;
@@ -77,6 +87,8 @@
FloatPoint getMouseCursorPosition(int32_t displayId) override;
void setShowTouchesEnabled(bool enabled) override;
void setStylusPointerIconEnabled(bool enabled) override;
+ bool setPointerIcon(std::variant<std::unique_ptr<SpriteIcon>, PointerIconStyle> icon,
+ int32_t displayId, DeviceId deviceId) override;
void notifyInputDevicesChanged(const NotifyInputDevicesChangedArgs& args) override;
void notifyConfigurationChanged(const NotifyConfigurationChangedArgs& args) override;
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index b8cdb3d..5df9874 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -6924,4 +6924,21 @@
mConfig.keyRepeatDelay = delay.count();
}
+bool InputDispatcher::isPointerInWindow(const sp<android::IBinder>& token, int32_t displayId,
+ DeviceId deviceId, int32_t pointerId) {
+ std::scoped_lock _l(mLock);
+ auto touchStateIt = mTouchStatesByDisplay.find(displayId);
+ if (touchStateIt == mTouchStatesByDisplay.end()) {
+ return false;
+ }
+ for (const TouchedWindow& window : touchStateIt->second.windows) {
+ if (window.windowHandle->getToken() == token &&
+ (window.hasTouchingPointer(deviceId, pointerId) ||
+ window.hasHoveringPointer(deviceId, pointerId))) {
+ return true;
+ }
+ }
+ return false;
+}
+
} // namespace android::inputdispatcher
diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h
index fbbde4b..3f99b2d 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.h
+++ b/services/inputflinger/dispatcher/InputDispatcher.h
@@ -148,6 +148,9 @@
void setKeyRepeatConfiguration(std::chrono::nanoseconds timeout,
std::chrono::nanoseconds delay) override;
+ bool isPointerInWindow(const sp<IBinder>& token, int32_t displayId, DeviceId deviceId,
+ int32_t pointerId) override;
+
private:
enum class DropReason {
NOT_DROPPED,
diff --git a/services/inputflinger/dispatcher/include/InputDispatcherInterface.h b/services/inputflinger/dispatcher/include/InputDispatcherInterface.h
index bc7b644..001dc6c 100644
--- a/services/inputflinger/dispatcher/include/InputDispatcherInterface.h
+++ b/services/inputflinger/dispatcher/include/InputDispatcherInterface.h
@@ -223,6 +223,12 @@
*/
virtual void setKeyRepeatConfiguration(std::chrono::nanoseconds timeout,
std::chrono::nanoseconds delay) = 0;
+
+ /*
+ * Determine if a pointer from a device is being dispatched to the given window.
+ */
+ virtual bool isPointerInWindow(const sp<IBinder>& token, int32_t displayId, DeviceId deviceId,
+ int32_t pointerId) = 0;
};
} // namespace android
diff --git a/services/inputflinger/include/PointerControllerInterface.h b/services/inputflinger/include/PointerControllerInterface.h
index ef74a55..c44486f 100644
--- a/services/inputflinger/include/PointerControllerInterface.h
+++ b/services/inputflinger/include/PointerControllerInterface.h
@@ -22,6 +22,8 @@
namespace android {
+struct SpriteIcon;
+
struct FloatPoint {
float x;
float y;
@@ -135,6 +137,12 @@
/* Sets the associated display of this pointer. Pointer should show on that display. */
virtual void setDisplayViewport(const DisplayViewport& displayViewport) = 0;
+
+ /* Sets the pointer icon type for mice or styluses. */
+ virtual void updatePointerIcon(PointerIconStyle iconId) = 0;
+
+ /* Sets the custom pointer icon for mice or styluses. */
+ virtual void setCustomPointerIcon(const SpriteIcon& icon) = 0;
};
} // namespace android
diff --git a/services/inputflinger/tests/FakePointerController.cpp b/services/inputflinger/tests/FakePointerController.cpp
index 8043812..80319f2 100644
--- a/services/inputflinger/tests/FakePointerController.cpp
+++ b/services/inputflinger/tests/FakePointerController.cpp
@@ -54,6 +54,16 @@
viewport.logicalBottom - 1);
}
+void FakePointerController::updatePointerIcon(PointerIconStyle iconId) {
+ ASSERT_FALSE(mIconStyle.has_value()) << "Pointer icon was set more than once";
+ mIconStyle = iconId;
+}
+
+void FakePointerController::setCustomPointerIcon(const SpriteIcon& icon) {
+ ASSERT_FALSE(mCustomIconStyle.has_value()) << "Custom pointer icon was set more than once";
+ mCustomIconStyle = icon.style;
+}
+
void FakePointerController::assertViewportSet(int32_t displayId) {
ASSERT_TRUE(mDisplayId);
ASSERT_EQ(displayId, mDisplayId);
@@ -75,6 +85,26 @@
ASSERT_EQ(static_cast<size_t>(count), it->second.size());
}
+void FakePointerController::assertPointerIconSet(PointerIconStyle iconId) {
+ ASSERT_TRUE(mIconStyle) << "Pointer icon style was not set";
+ ASSERT_EQ(iconId, mIconStyle);
+ mIconStyle.reset();
+}
+
+void FakePointerController::assertPointerIconNotSet() {
+ ASSERT_EQ(std::nullopt, mIconStyle);
+}
+
+void FakePointerController::assertCustomPointerIconSet(PointerIconStyle iconId) {
+ ASSERT_TRUE(mCustomIconStyle) << "Custom pointer icon was not set";
+ ASSERT_EQ(iconId, mCustomIconStyle);
+ mCustomIconStyle.reset();
+}
+
+void FakePointerController::assertCustomPointerIconNotSet() {
+ ASSERT_EQ(std::nullopt, mCustomIconStyle);
+}
+
bool FakePointerController::isPointerShown() {
return mIsPointerShown;
}
diff --git a/services/inputflinger/tests/FakePointerController.h b/services/inputflinger/tests/FakePointerController.h
index 9be6a6c..7668011 100644
--- a/services/inputflinger/tests/FakePointerController.h
+++ b/services/inputflinger/tests/FakePointerController.h
@@ -24,6 +24,10 @@
namespace android {
+struct SpriteIcon {
+ PointerIconStyle style;
+};
+
class FakePointerController : public PointerControllerInterface {
public:
virtual ~FakePointerController() {}
@@ -35,11 +39,17 @@
FloatPoint getPosition() const override;
int32_t getDisplayId() const override;
void setDisplayViewport(const DisplayViewport& viewport) override;
+ void updatePointerIcon(PointerIconStyle iconId) override;
+ void setCustomPointerIcon(const SpriteIcon& icon) override;
void assertViewportSet(int32_t displayId);
void assertViewportNotSet();
void assertPosition(float x, float y);
void assertSpotCount(int32_t displayId, int32_t count);
+ void assertPointerIconSet(PointerIconStyle iconId);
+ void assertPointerIconNotSet();
+ void assertCustomPointerIconSet(PointerIconStyle iconId);
+ void assertCustomPointerIconNotSet();
bool isPointerShown();
private:
@@ -58,6 +68,8 @@
float mX{0}, mY{0};
std::optional<int32_t> mDisplayId;
bool mIsPointerShown{false};
+ std::optional<PointerIconStyle> mIconStyle;
+ std::optional<PointerIconStyle> mCustomIconStyle;
std::map<int32_t, std::vector<int32_t>> mSpotsByDisplay;
};
diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp
index e230a91..728b85a 100644
--- a/services/inputflinger/tests/InputDispatcher_test.cpp
+++ b/services/inputflinger/tests/InputDispatcher_test.cpp
@@ -11254,4 +11254,243 @@
randosWindow->assertNoEvents();
}
+using InputDispatcherPointerInWindowTest = InputDispatcherTest;
+
+TEST_F(InputDispatcherPointerInWindowTest, PointerInWindowWhenHovering) {
+ std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+
+ sp<FakeWindowHandle> left = sp<FakeWindowHandle>::make(application, mDispatcher, "Left Window",
+ ADISPLAY_ID_DEFAULT);
+ left->setFrame(Rect(0, 0, 100, 100));
+ sp<FakeWindowHandle> right = sp<FakeWindowHandle>::make(application, mDispatcher,
+ "Right Window", ADISPLAY_ID_DEFAULT);
+ right->setFrame(Rect(100, 0, 200, 100));
+ sp<FakeWindowHandle> spy =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Spy Window", ADISPLAY_ID_DEFAULT);
+ spy->setFrame(Rect(0, 0, 200, 100));
+ spy->setTrustedOverlay(true);
+ spy->setSpy(true);
+
+ mDispatcher->onWindowInfosChanged(
+ {{*spy->getInfo(), *left->getInfo(), *right->getInfo()}, {}, 0, 0});
+
+ // Hover into the left window.
+ mDispatcher->notifyMotion(
+ MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
+ .pointer(PointerBuilder(/*id=*/0, ToolType::STYLUS).x(50).y(50))
+ .build());
+
+ left->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER));
+ spy->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER));
+
+ ASSERT_TRUE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+ /*pointerId=*/0));
+ ASSERT_TRUE(mDispatcher->isPointerInWindow(spy->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+ /*pointerId=*/0));
+ ASSERT_FALSE(mDispatcher->isPointerInWindow(right->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+ /*pointerId=*/0));
+
+ // Hover move to the right window.
+ mDispatcher->notifyMotion(
+ MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS)
+ .pointer(PointerBuilder(/*id=*/0, ToolType::STYLUS).x(150).y(50))
+ .build());
+
+ left->consumeMotionEvent(WithMotionAction(ACTION_HOVER_EXIT));
+ right->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER));
+ spy->consumeMotionEvent(WithMotionAction(ACTION_HOVER_MOVE));
+
+ ASSERT_FALSE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+ /*pointerId=*/0));
+ ASSERT_TRUE(mDispatcher->isPointerInWindow(spy->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+ /*pointerId=*/0));
+ ASSERT_TRUE(mDispatcher->isPointerInWindow(right->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+ /*pointerId=*/0));
+
+ // Stop hovering.
+ mDispatcher->notifyMotion(
+ MotionArgsBuilder(ACTION_HOVER_EXIT, AINPUT_SOURCE_STYLUS)
+ .pointer(PointerBuilder(/*id=*/0, ToolType::STYLUS).x(150).y(50))
+ .build());
+
+ right->consumeMotionEvent(WithMotionAction(ACTION_HOVER_EXIT));
+ spy->consumeMotionEvent(WithMotionAction(ACTION_HOVER_EXIT));
+
+ ASSERT_FALSE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+ /*pointerId=*/0));
+ ASSERT_FALSE(mDispatcher->isPointerInWindow(spy->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+ /*pointerId=*/0));
+ ASSERT_FALSE(mDispatcher->isPointerInWindow(right->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+ /*pointerId=*/0));
+}
+
+TEST_F(InputDispatcherPointerInWindowTest, PointerInWindowWithSplitTouch) {
+ std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+
+ sp<FakeWindowHandle> left = sp<FakeWindowHandle>::make(application, mDispatcher, "Left Window",
+ ADISPLAY_ID_DEFAULT);
+ left->setFrame(Rect(0, 0, 100, 100));
+ sp<FakeWindowHandle> right = sp<FakeWindowHandle>::make(application, mDispatcher,
+ "Right Window", ADISPLAY_ID_DEFAULT);
+ right->setFrame(Rect(100, 0, 200, 100));
+ sp<FakeWindowHandle> spy =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Spy Window", ADISPLAY_ID_DEFAULT);
+ spy->setFrame(Rect(0, 0, 200, 100));
+ spy->setTrustedOverlay(true);
+ spy->setSpy(true);
+
+ mDispatcher->onWindowInfosChanged(
+ {{*spy->getInfo(), *left->getInfo(), *right->getInfo()}, {}, 0, 0});
+
+ // First pointer down on left window.
+ mDispatcher->notifyMotion(
+ MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+ .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(50).y(50))
+ .build());
+
+ left->consumeMotionDown();
+ spy->consumeMotionDown();
+
+ ASSERT_TRUE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+ /*pointerId=*/0));
+ ASSERT_TRUE(mDispatcher->isPointerInWindow(spy->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+ /*pointerId=*/0));
+ ASSERT_FALSE(mDispatcher->isPointerInWindow(right->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+ /*pointerId=*/0));
+
+ // Second pointer down on right window.
+ mDispatcher->notifyMotion(
+ MotionArgsBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+ .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(50).y(50))
+ .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(150).y(50))
+ .build());
+
+ left->consumeMotionMove();
+ right->consumeMotionDown();
+ spy->consumeMotionEvent(WithMotionAction(POINTER_1_DOWN));
+
+ ASSERT_TRUE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+ /*pointerId=*/0));
+ ASSERT_TRUE(mDispatcher->isPointerInWindow(spy->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+ /*pointerId=*/0));
+ ASSERT_FALSE(mDispatcher->isPointerInWindow(right->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+ /*pointerId=*/0));
+ ASSERT_FALSE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+ /*pointerId=*/1));
+ ASSERT_TRUE(mDispatcher->isPointerInWindow(spy->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+ /*pointerId=*/1));
+ ASSERT_TRUE(mDispatcher->isPointerInWindow(right->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+ /*pointerId=*/1));
+
+ // Second pointer up.
+ mDispatcher->notifyMotion(
+ MotionArgsBuilder(POINTER_1_UP, AINPUT_SOURCE_TOUCHSCREEN)
+ .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(50).y(50))
+ .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(150).y(50))
+ .build());
+
+ left->consumeMotionMove();
+ right->consumeMotionUp();
+ spy->consumeMotionEvent(WithMotionAction(POINTER_1_UP));
+
+ ASSERT_TRUE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+ /*pointerId=*/0));
+ ASSERT_TRUE(mDispatcher->isPointerInWindow(spy->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+ /*pointerId=*/0));
+ ASSERT_FALSE(mDispatcher->isPointerInWindow(right->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+ /*pointerId=*/0));
+ ASSERT_FALSE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+ /*pointerId=*/1));
+ ASSERT_FALSE(mDispatcher->isPointerInWindow(spy->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+ /*pointerId=*/1));
+ ASSERT_FALSE(mDispatcher->isPointerInWindow(right->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+ /*pointerId=*/1));
+
+ // First pointer up.
+ mDispatcher->notifyMotion(
+ MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN)
+ .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(50).y(50))
+ .build());
+
+ left->consumeMotionUp();
+ spy->consumeMotionUp();
+
+ ASSERT_FALSE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+ /*pointerId=*/0));
+ ASSERT_FALSE(mDispatcher->isPointerInWindow(spy->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+ /*pointerId=*/0));
+ ASSERT_FALSE(mDispatcher->isPointerInWindow(right->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+ /*pointerId=*/0));
+}
+
+TEST_F(InputDispatcherPointerInWindowTest, MultipleDevicesControllingOneMouse) {
+ std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+
+ sp<FakeWindowHandle> left = sp<FakeWindowHandle>::make(application, mDispatcher, "Left Window",
+ ADISPLAY_ID_DEFAULT);
+ left->setFrame(Rect(0, 0, 100, 100));
+ sp<FakeWindowHandle> right = sp<FakeWindowHandle>::make(application, mDispatcher,
+ "Right Window", ADISPLAY_ID_DEFAULT);
+ right->setFrame(Rect(100, 0, 200, 100));
+
+ mDispatcher->onWindowInfosChanged({{*left->getInfo(), *right->getInfo()}, {}, 0, 0});
+
+ ASSERT_FALSE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+ /*pointerId=*/0));
+ ASSERT_FALSE(mDispatcher->isPointerInWindow(right->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+ /*pointerId=*/0));
+
+ // Hover move into the window.
+ mDispatcher->notifyMotion(
+ MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+ .pointer(PointerBuilder(/*id=*/0, ToolType::MOUSE).x(50).y(50))
+ .rawXCursorPosition(50)
+ .rawYCursorPosition(50)
+ .deviceId(DEVICE_ID)
+ .build());
+
+ left->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER));
+
+ ASSERT_TRUE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+ /*pointerId=*/0));
+
+ // Move the mouse with another device. This cancels the hovering pointer from the first device.
+ mDispatcher->notifyMotion(
+ MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+ .pointer(PointerBuilder(/*id=*/0, ToolType::MOUSE).x(51).y(50))
+ .rawXCursorPosition(51)
+ .rawYCursorPosition(50)
+ .deviceId(SECOND_DEVICE_ID)
+ .build());
+
+ left->consumeMotionEvent(WithMotionAction(ACTION_HOVER_EXIT));
+ left->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER));
+
+ // TODO(b/313689709): InputDispatcher's touch state is not updated, even though the window gets
+ // a HOVER_EXIT from the first device.
+ ASSERT_TRUE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+ /*pointerId=*/0));
+ ASSERT_TRUE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT,
+ SECOND_DEVICE_ID,
+ /*pointerId=*/0));
+
+ // Move the mouse outside the window. Document the current behavior, where the window does not
+ // receive HOVER_EXIT even though the mouse left the window.
+ mDispatcher->notifyMotion(
+ MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+ .pointer(PointerBuilder(/*id=*/0, ToolType::MOUSE).x(150).y(50))
+ .rawXCursorPosition(150)
+ .rawYCursorPosition(50)
+ .deviceId(SECOND_DEVICE_ID)
+ .build());
+
+ left->consumeMotionEvent(WithMotionAction(ACTION_HOVER_EXIT));
+ right->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER));
+ ASSERT_TRUE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+ /*pointerId=*/0));
+ ASSERT_FALSE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT,
+ SECOND_DEVICE_ID,
+ /*pointerId=*/0));
+}
+
} // namespace android::inputdispatcher
diff --git a/services/inputflinger/tests/PointerChoreographer_test.cpp b/services/inputflinger/tests/PointerChoreographer_test.cpp
index 1efb797..2457f7c 100644
--- a/services/inputflinger/tests/PointerChoreographer_test.cpp
+++ b/services/inputflinger/tests/PointerChoreographer_test.cpp
@@ -1511,4 +1511,278 @@
ASSERT_FALSE(pc->isPointerShown());
}
+TEST_F(PointerChoreographerTest, SetsPointerIconForMouse) {
+ // Make sure there is a PointerController.
+ mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+ mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}});
+ mChoreographer.notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+ .pointer(MOUSE_POINTER)
+ .deviceId(DEVICE_ID)
+ .displayId(ADISPLAY_ID_NONE)
+ .build());
+ auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
+ pc->assertPointerIconNotSet();
+
+ // Set pointer icon for the device.
+ ASSERT_TRUE(mChoreographer.setPointerIcon(PointerIconStyle::TYPE_TEXT, DISPLAY_ID, DEVICE_ID));
+ pc->assertPointerIconSet(PointerIconStyle::TYPE_TEXT);
+}
+
+TEST_F(PointerChoreographerTest, DoesNotSetMousePointerIconForWrongDisplayId) {
+ // Make sure there is a PointerController.
+ mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+ mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}});
+ mChoreographer.notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+ .pointer(MOUSE_POINTER)
+ .deviceId(DEVICE_ID)
+ .displayId(ADISPLAY_ID_NONE)
+ .build());
+ auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
+ pc->assertPointerIconNotSet();
+
+ // Set pointer icon for wrong display id. This should be ignored.
+ ASSERT_FALSE(mChoreographer.setPointerIcon(PointerIconStyle::TYPE_TEXT, ANOTHER_DISPLAY_ID,
+ SECOND_DEVICE_ID));
+ pc->assertPointerIconNotSet();
+}
+
+TEST_F(PointerChoreographerTest, DoesNotSetPointerIconForWrongDeviceId) {
+ // Make sure there is a PointerController.
+ mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+ mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}});
+ mChoreographer.notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+ .pointer(MOUSE_POINTER)
+ .deviceId(DEVICE_ID)
+ .displayId(ADISPLAY_ID_NONE)
+ .build());
+ auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
+ pc->assertPointerIconNotSet();
+
+ // Set pointer icon for wrong device id. This should be ignored.
+ ASSERT_FALSE(mChoreographer.setPointerIcon(PointerIconStyle::TYPE_TEXT, DISPLAY_ID,
+ SECOND_DEVICE_ID));
+ pc->assertPointerIconNotSet();
+}
+
+TEST_F(PointerChoreographerTest, DoesNotSetPointerIconForDeviceWithoutPointerController) {
+ // Add two devices, one with a PointerController and the other without PointerController.
+ mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+ mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0,
+ {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE),
+ generateTestDeviceInfo(SECOND_DEVICE_ID, AINPUT_SOURCE_MOUSE, ANOTHER_DISPLAY_ID)}});
+ mChoreographer.notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+ .pointer(MOUSE_POINTER)
+ .deviceId(DEVICE_ID)
+ .displayId(ADISPLAY_ID_NONE)
+ .build());
+ auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
+ pc->assertPointerIconNotSet();
+
+ // Set pointer icon for the device without PointerController. This should be ignored.
+ ASSERT_FALSE(mChoreographer.setPointerIcon(PointerIconStyle::TYPE_TEXT, ANOTHER_DISPLAY_ID,
+ SECOND_DEVICE_ID));
+ pc->assertPointerIconNotSet();
+}
+
+TEST_F(PointerChoreographerTest, SetsCustomPointerIconForMouse) {
+ // Make sure there is a PointerController.
+ mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+ mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}});
+ mChoreographer.notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+ .pointer(MOUSE_POINTER)
+ .deviceId(DEVICE_ID)
+ .displayId(ADISPLAY_ID_NONE)
+ .build());
+ auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
+ pc->assertCustomPointerIconNotSet();
+
+ // Set custom pointer icon for the device.
+ ASSERT_TRUE(mChoreographer.setPointerIcon(std::make_unique<SpriteIcon>(
+ PointerIconStyle::TYPE_CUSTOM),
+ DISPLAY_ID, DEVICE_ID));
+ pc->assertCustomPointerIconSet(PointerIconStyle::TYPE_CUSTOM);
+
+ // Set custom pointer icon for wrong device id. This should be ignored.
+ ASSERT_FALSE(mChoreographer.setPointerIcon(std::make_unique<SpriteIcon>(
+ PointerIconStyle::TYPE_CUSTOM),
+ DISPLAY_ID, SECOND_DEVICE_ID));
+ pc->assertCustomPointerIconNotSet();
+}
+
+TEST_F(PointerChoreographerTest, SetsPointerIconForMouseOnTwoDisplays) {
+ // Make sure there are two PointerControllers on different displays.
+ mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID, ANOTHER_DISPLAY_ID}));
+ mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0,
+ {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE),
+ generateTestDeviceInfo(SECOND_DEVICE_ID, AINPUT_SOURCE_MOUSE, ANOTHER_DISPLAY_ID)}});
+ mChoreographer.notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+ .pointer(MOUSE_POINTER)
+ .deviceId(DEVICE_ID)
+ .displayId(ADISPLAY_ID_NONE)
+ .build());
+ auto firstMousePc = assertPointerControllerCreated(ControllerType::MOUSE);
+ ASSERT_EQ(DISPLAY_ID, firstMousePc->getDisplayId());
+ mChoreographer.notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+ .pointer(MOUSE_POINTER)
+ .deviceId(SECOND_DEVICE_ID)
+ .displayId(ANOTHER_DISPLAY_ID)
+ .build());
+ auto secondMousePc = assertPointerControllerCreated(ControllerType::MOUSE);
+ ASSERT_EQ(ANOTHER_DISPLAY_ID, secondMousePc->getDisplayId());
+
+ // Set pointer icon for one mouse.
+ ASSERT_TRUE(mChoreographer.setPointerIcon(PointerIconStyle::TYPE_TEXT, DISPLAY_ID, DEVICE_ID));
+ firstMousePc->assertPointerIconSet(PointerIconStyle::TYPE_TEXT);
+ secondMousePc->assertPointerIconNotSet();
+
+ // Set pointer icon for another mouse.
+ ASSERT_TRUE(mChoreographer.setPointerIcon(PointerIconStyle::TYPE_TEXT, ANOTHER_DISPLAY_ID,
+ SECOND_DEVICE_ID));
+ secondMousePc->assertPointerIconSet(PointerIconStyle::TYPE_TEXT);
+ firstMousePc->assertPointerIconNotSet();
+}
+
+TEST_F(PointerChoreographerTest, SetsPointerIconForStylus) {
+ // Make sure there is a PointerController.
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}});
+ mChoreographer.setStylusPointerIconEnabled(true);
+ mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+ mChoreographer.notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
+ .pointer(STYLUS_POINTER)
+ .deviceId(DEVICE_ID)
+ .displayId(DISPLAY_ID)
+ .build());
+ auto pc = assertPointerControllerCreated(ControllerType::STYLUS);
+ pc->assertPointerIconNotSet();
+
+ // Set pointer icon for the device.
+ ASSERT_TRUE(mChoreographer.setPointerIcon(PointerIconStyle::TYPE_TEXT, DISPLAY_ID, DEVICE_ID));
+ pc->assertPointerIconSet(PointerIconStyle::TYPE_TEXT);
+
+ // Set pointer icon for wrong device id. This should be ignored.
+ ASSERT_FALSE(mChoreographer.setPointerIcon(PointerIconStyle::TYPE_TEXT, DISPLAY_ID,
+ SECOND_DEVICE_ID));
+ pc->assertPointerIconNotSet();
+}
+
+TEST_F(PointerChoreographerTest, SetsCustomPointerIconForStylus) {
+ // Make sure there is a PointerController.
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}});
+ mChoreographer.setStylusPointerIconEnabled(true);
+ mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+ mChoreographer.notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
+ .pointer(STYLUS_POINTER)
+ .deviceId(DEVICE_ID)
+ .displayId(DISPLAY_ID)
+ .build());
+ auto pc = assertPointerControllerCreated(ControllerType::STYLUS);
+ pc->assertCustomPointerIconNotSet();
+
+ // Set custom pointer icon for the device.
+ ASSERT_TRUE(mChoreographer.setPointerIcon(std::make_unique<SpriteIcon>(
+ PointerIconStyle::TYPE_CUSTOM),
+ DISPLAY_ID, DEVICE_ID));
+ pc->assertCustomPointerIconSet(PointerIconStyle::TYPE_CUSTOM);
+
+ // Set custom pointer icon for wrong device id. This should be ignored.
+ ASSERT_FALSE(mChoreographer.setPointerIcon(std::make_unique<SpriteIcon>(
+ PointerIconStyle::TYPE_CUSTOM),
+ DISPLAY_ID, SECOND_DEVICE_ID));
+ pc->assertCustomPointerIconNotSet();
+}
+
+TEST_F(PointerChoreographerTest, SetsPointerIconForTwoStyluses) {
+ // Make sure there are two StylusPointerControllers. They can be on a same display.
+ mChoreographer.setStylusPointerIconEnabled(true);
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0,
+ {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID),
+ generateTestDeviceInfo(SECOND_DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}});
+ mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+ mChoreographer.notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
+ .pointer(STYLUS_POINTER)
+ .deviceId(DEVICE_ID)
+ .displayId(DISPLAY_ID)
+ .build());
+ auto firstStylusPc = assertPointerControllerCreated(ControllerType::STYLUS);
+ mChoreographer.notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
+ .pointer(STYLUS_POINTER)
+ .deviceId(SECOND_DEVICE_ID)
+ .displayId(DISPLAY_ID)
+ .build());
+ auto secondStylusPc = assertPointerControllerCreated(ControllerType::STYLUS);
+
+ // Set pointer icon for one stylus.
+ ASSERT_TRUE(mChoreographer.setPointerIcon(PointerIconStyle::TYPE_TEXT, DISPLAY_ID, DEVICE_ID));
+ firstStylusPc->assertPointerIconSet(PointerIconStyle::TYPE_TEXT);
+ secondStylusPc->assertPointerIconNotSet();
+
+ // Set pointer icon for another stylus.
+ ASSERT_TRUE(mChoreographer.setPointerIcon(PointerIconStyle::TYPE_TEXT, DISPLAY_ID,
+ SECOND_DEVICE_ID));
+ secondStylusPc->assertPointerIconSet(PointerIconStyle::TYPE_TEXT);
+ firstStylusPc->assertPointerIconNotSet();
+}
+
+TEST_F(PointerChoreographerTest, SetsPointerIconForMouseAndStylus) {
+ // Make sure there are PointerControllers for a mouse and a stylus.
+ mChoreographer.setStylusPointerIconEnabled(true);
+ mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0,
+ {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE),
+ generateTestDeviceInfo(SECOND_DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}});
+ mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+ mChoreographer.notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+ .pointer(MOUSE_POINTER)
+ .deviceId(DEVICE_ID)
+ .displayId(ADISPLAY_ID_NONE)
+ .build());
+ auto mousePc = assertPointerControllerCreated(ControllerType::MOUSE);
+ mChoreographer.notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
+ .pointer(STYLUS_POINTER)
+ .deviceId(SECOND_DEVICE_ID)
+ .displayId(DISPLAY_ID)
+ .build());
+ auto stylusPc = assertPointerControllerCreated(ControllerType::STYLUS);
+
+ // Set pointer icon for the mouse.
+ ASSERT_TRUE(mChoreographer.setPointerIcon(PointerIconStyle::TYPE_TEXT, DISPLAY_ID, DEVICE_ID));
+ mousePc->assertPointerIconSet(PointerIconStyle::TYPE_TEXT);
+ stylusPc->assertPointerIconNotSet();
+
+ // Set pointer icon for the stylus.
+ ASSERT_TRUE(mChoreographer.setPointerIcon(PointerIconStyle::TYPE_TEXT, DISPLAY_ID,
+ SECOND_DEVICE_ID));
+ stylusPc->assertPointerIconSet(PointerIconStyle::TYPE_TEXT);
+ mousePc->assertPointerIconNotSet();
+}
+
} // namespace android
diff --git a/services/inputflinger/tests/fuzzers/MapperHelpers.h b/services/inputflinger/tests/fuzzers/MapperHelpers.h
index 2f84497..81c570d 100644
--- a/services/inputflinger/tests/fuzzers/MapperHelpers.h
+++ b/services/inputflinger/tests/fuzzers/MapperHelpers.h
@@ -275,6 +275,8 @@
void clearSpots() override {}
int32_t getDisplayId() const override { return mFdp->ConsumeIntegral<int32_t>(); }
void setDisplayViewport(const DisplayViewport& displayViewport) override {}
+ void updatePointerIcon(PointerIconStyle iconId) override {}
+ void setCustomPointerIcon(const SpriteIcon& icon) override {}
std::string dump() override { return ""; }
};