Pointer icon refactor for stylus
When PointerChoreographer is enabled, PointerChoreographer can
create multiple StylusPointerControllers for each stylus device.
A StylusPointerController is created when the corresponding stylus
sends the first hover event. It can show and hide a hover pointer
on the associated display.
Test: atest inputflinger_tests
Bug: 293587049
Change-Id: Iaedf724815d96c3af3accf70dbd54f62b953ecc9
diff --git a/services/inputflinger/PointerChoreographer.cpp b/services/inputflinger/PointerChoreographer.cpp
index 13fbf1d..841d95b 100644
--- a/services/inputflinger/PointerChoreographer.cpp
+++ b/services/inputflinger/PointerChoreographer.cpp
@@ -31,6 +31,14 @@
args.pointerProperties[0].toolType == ToolType::MOUSE;
}
+bool isHoverAction(int32_t action) {
+ return action == AMOTION_EVENT_ACTION_HOVER_ENTER ||
+ action == AMOTION_EVENT_ACTION_HOVER_MOVE || action == AMOTION_EVENT_ACTION_HOVER_EXIT;
+}
+
+bool isStylusHoverEvent(const NotifyMotionArgs& args) {
+ return isStylusEvent(args.source, args.pointerProperties) && isHoverAction(args.action);
+}
} // namespace
// --- PointerChoreographer ---
@@ -41,7 +49,8 @@
mPolicy(policy),
mDefaultMouseDisplayId(ADISPLAY_ID_DEFAULT),
mNotifiedPointerDisplayId(ADISPLAY_ID_NONE),
- mShowTouchesEnabled(false) {}
+ mShowTouchesEnabled(false),
+ mStylusPointerIconEnabled(false) {}
void PointerChoreographer::notifyInputDevicesChanged(const NotifyInputDevicesChangedArgs& args) {
std::scoped_lock _l(mLock);
@@ -70,6 +79,8 @@
if (isFromMouse(args)) {
return processMouseEventLocked(args);
+ } else if (mStylusPointerIconEnabled && isStylusHoverEvent(args)) {
+ processStylusHoverEventLocked(args);
} else if (isFromSource(args.source, AINPUT_SOURCE_TOUCHSCREEN)) {
processTouchscreenAndStylusEventLocked(args);
}
@@ -155,6 +166,33 @@
pc.setSpots(coords, idToIndex.cbegin(), idBits, args.displayId);
}
+void PointerChoreographer::processStylusHoverEventLocked(const NotifyMotionArgs& args) {
+ if (args.displayId == ADISPLAY_ID_NONE) {
+ return;
+ }
+
+ if (args.getPointerCount() != 1) {
+ LOG(WARNING) << "Only stylus hover events with a single pointer are currently supported: "
+ << args.dump();
+ }
+
+ // Get the stylus pointer controller for the device, or create one if it doesn't exist.
+ auto [it, _] =
+ mStylusPointersByDevice.try_emplace(args.deviceId,
+ getStylusControllerConstructor(args.displayId));
+
+ PointerControllerInterface& pc = *it->second;
+
+ const float x = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_X);
+ const float y = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_Y);
+ pc.setPosition(x, y);
+ if (args.action == AMOTION_EVENT_ACTION_HOVER_EXIT) {
+ pc.fade(PointerControllerInterface::Transition::IMMEDIATE);
+ } else {
+ pc.unfade(PointerControllerInterface::Transition::IMMEDIATE);
+ }
+}
+
void PointerChoreographer::notifySwitch(const NotifySwitchArgs& args) {
mNextListener.notify(args);
}
@@ -181,13 +219,22 @@
return;
}
- if (isFromSource(info->getSources(), AINPUT_SOURCE_TOUCHSCREEN) && mShowTouchesEnabled &&
- info->getAssociatedDisplayId() != ADISPLAY_ID_NONE) {
+ const uint32_t sources = info->getSources();
+ const int32_t displayId = info->getAssociatedDisplayId();
+ if (isFromSource(sources, AINPUT_SOURCE_TOUCHSCREEN) && mShowTouchesEnabled &&
+ displayId != ADISPLAY_ID_NONE) {
if (const auto it = mTouchPointersByDevice.find(args.deviceId);
it != mTouchPointersByDevice.end()) {
it->second->clearSpots();
}
}
+ if (isFromSource(info->getSources(), AINPUT_SOURCE_STYLUS) && mStylusPointerIconEnabled &&
+ displayId != ADISPLAY_ID_NONE) {
+ if (const auto it = mStylusPointersByDevice.find(args.deviceId);
+ it != mStylusPointersByDevice.end()) {
+ it->second->fade(PointerControllerInterface::Transition::IMMEDIATE);
+ }
+ }
}
void PointerChoreographer::notifyPointerCaptureChanged(
@@ -206,6 +253,8 @@
dump += "PointerChoreographer:\n";
dump += StringPrintf("show touches: %s\n", mShowTouchesEnabled ? "true" : "false");
+ dump += StringPrintf("stylus pointer icon enabled: %s\n",
+ mStylusPointerIconEnabled ? "true" : "false");
dump += INDENT "MousePointerControllers:\n";
for (const auto& [displayId, mousePointerController] : mMousePointersByDisplay) {
@@ -217,6 +266,11 @@
std::string pointerControllerDump = addLinePrefix(touchPointerController->dump(), INDENT);
dump += INDENT + std::to_string(deviceId) + " : " + pointerControllerDump;
}
+ dump += INDENT "StylusPointerControllers:\n";
+ for (const auto& [deviceId, stylusPointerController] : mStylusPointersByDevice) {
+ std::string pointerControllerDump = addLinePrefix(stylusPointerController->dump(), INDENT);
+ dump += INDENT + std::to_string(deviceId) + " : " + pointerControllerDump;
+ }
dump += "\n";
}
@@ -245,6 +299,7 @@
void PointerChoreographer::updatePointerControllersLocked() {
std::set<int32_t /*displayId*/> mouseDisplaysToKeep;
std::set<DeviceId> touchDevicesToKeep;
+ std::set<DeviceId> stylusDevicesToKeep;
// Mark the displayIds or deviceIds of PointerControllers currently needed.
for (const auto& info : mInputDeviceInfos) {
@@ -259,6 +314,10 @@
info.getAssociatedDisplayId() != ADISPLAY_ID_NONE) {
touchDevicesToKeep.insert(info.getId());
}
+ if (isFromSource(sources, AINPUT_SOURCE_STYLUS) && mStylusPointerIconEnabled &&
+ info.getAssociatedDisplayId() != ADISPLAY_ID_NONE) {
+ stylusDevicesToKeep.insert(info.getId());
+ }
}
// Remove PointerControllers no longer needed.
@@ -279,6 +338,14 @@
}
return false;
});
+ std::erase_if(mStylusPointersByDevice, [&stylusDevicesToKeep](const auto& pair) {
+ auto& [deviceId, controller] = pair;
+ if (stylusDevicesToKeep.find(deviceId) == stylusDevicesToKeep.end()) {
+ controller->fade(PointerControllerInterface::Transition::IMMEDIATE);
+ return true;
+ }
+ return false;
+ });
// Notify the policy if there's a change on the pointer display ID.
notifyPointerDisplayIdChangedLocked();
@@ -314,10 +381,17 @@
void PointerChoreographer::setDisplayViewports(const std::vector<DisplayViewport>& viewports) {
std::scoped_lock _l(mLock);
for (const auto& viewport : viewports) {
- if (const auto it = mMousePointersByDisplay.find(viewport.displayId);
+ const int32_t displayId = viewport.displayId;
+ if (const auto it = mMousePointersByDisplay.find(displayId);
it != mMousePointersByDisplay.end()) {
it->second->setDisplayViewport(viewport);
}
+ for (const auto& [deviceId, stylusPointerController] : mStylusPointersByDevice) {
+ const InputDeviceInfo* info = findInputDeviceLocked(deviceId);
+ if (info && info->getAssociatedDisplayId() == displayId) {
+ stylusPointerController->setDisplayViewport(viewport);
+ }
+ }
}
mViewports = viewports;
notifyPointerDisplayIdChangedLocked();
@@ -352,6 +426,15 @@
updatePointerControllersLocked();
}
+void PointerChoreographer::setStylusPointerIconEnabled(bool enabled) {
+ std::scoped_lock _l(mLock);
+ if (mStylusPointerIconEnabled == enabled) {
+ return;
+ }
+ mStylusPointerIconEnabled = enabled;
+ updatePointerControllersLocked();
+}
+
PointerChoreographer::ControllerConstructor PointerChoreographer::getMouseControllerConstructor(
int32_t displayId) {
std::function<std::shared_ptr<PointerControllerInterface>()> ctor =
@@ -373,4 +456,18 @@
return ConstructorDelegate(std::move(ctor));
}
+PointerChoreographer::ControllerConstructor PointerChoreographer::getStylusControllerConstructor(
+ int32_t displayId) {
+ std::function<std::shared_ptr<PointerControllerInterface>()> ctor =
+ [this, displayId]() REQUIRES(mLock) {
+ auto pc = mPolicy.createPointerController(
+ PointerControllerInterface::ControllerType::STYLUS);
+ if (const auto viewport = findViewportByIdLocked(displayId); viewport) {
+ pc->setDisplayViewport(*viewport);
+ }
+ return pc;
+ };
+ return ConstructorDelegate(std::move(ctor));
+}
+
} // namespace android
diff --git a/services/inputflinger/PointerChoreographer.h b/services/inputflinger/PointerChoreographer.h
index 84eb1e7..90569d8 100644
--- a/services/inputflinger/PointerChoreographer.h
+++ b/services/inputflinger/PointerChoreographer.h
@@ -57,6 +57,7 @@
int32_t associatedDisplayId = ADISPLAY_ID_NONE) = 0;
virtual FloatPoint getMouseCursorPosition(int32_t displayId) = 0;
virtual void setShowTouchesEnabled(bool enabled) = 0;
+ virtual void setStylusPointerIconEnabled(bool enabled) = 0;
/**
* This method may be called on any thread (usually by the input manager on a binder thread).
*/
@@ -75,6 +76,7 @@
int32_t associatedDisplayId) override;
FloatPoint getMouseCursorPosition(int32_t displayId) override;
void setShowTouchesEnabled(bool enabled) override;
+ void setStylusPointerIconEnabled(bool enabled) override;
void notifyInputDevicesChanged(const NotifyInputDevicesChangedArgs& args) override;
void notifyConfigurationChanged(const NotifyConfigurationChangedArgs& args) override;
@@ -98,12 +100,14 @@
NotifyMotionArgs processMotion(const NotifyMotionArgs& args);
NotifyMotionArgs processMouseEventLocked(const NotifyMotionArgs& args) REQUIRES(mLock);
void processTouchscreenAndStylusEventLocked(const NotifyMotionArgs& args) REQUIRES(mLock);
+ void processStylusHoverEventLocked(const NotifyMotionArgs& args) REQUIRES(mLock);
void processDeviceReset(const NotifyDeviceResetArgs& args);
using ControllerConstructor =
ConstructorDelegate<std::function<std::shared_ptr<PointerControllerInterface>()>>;
ControllerConstructor getMouseControllerConstructor(int32_t displayId) REQUIRES(mLock);
ControllerConstructor getTouchControllerConstructor() REQUIRES(mLock);
+ ControllerConstructor getStylusControllerConstructor(int32_t displayId) REQUIRES(mLock);
std::mutex mLock;
@@ -114,12 +118,15 @@
GUARDED_BY(mLock);
std::map<DeviceId, std::shared_ptr<PointerControllerInterface>> mTouchPointersByDevice
GUARDED_BY(mLock);
+ std::map<DeviceId, std::shared_ptr<PointerControllerInterface>> mStylusPointersByDevice
+ GUARDED_BY(mLock);
int32_t mDefaultMouseDisplayId GUARDED_BY(mLock);
int32_t mNotifiedPointerDisplayId GUARDED_BY(mLock);
std::vector<InputDeviceInfo> mInputDeviceInfos GUARDED_BY(mLock);
std::vector<DisplayViewport> mViewports GUARDED_BY(mLock);
bool mShowTouchesEnabled GUARDED_BY(mLock);
+ bool mStylusPointerIconEnabled GUARDED_BY(mLock);
};
} // namespace android
diff --git a/services/inputflinger/include/PointerControllerInterface.h b/services/inputflinger/include/PointerControllerInterface.h
index 9038056..ef74a55 100644
--- a/services/inputflinger/include/PointerControllerInterface.h
+++ b/services/inputflinger/include/PointerControllerInterface.h
@@ -65,6 +65,8 @@
MOUSE,
// Represents multiple touch spots.
TOUCH,
+ // Represents a single stylus pointer.
+ STYLUS,
};
/* Dumps the state of the pointer controller. */
diff --git a/services/inputflinger/tests/PointerChoreographer_test.cpp b/services/inputflinger/tests/PointerChoreographer_test.cpp
index d556d78..1a52729 100644
--- a/services/inputflinger/tests/PointerChoreographer_test.cpp
+++ b/services/inputflinger/tests/PointerChoreographer_test.cpp
@@ -806,4 +806,274 @@
ASSERT_TRUE(it == pc->getSpots().end());
}
+TEST_F(PointerChoreographerTest,
+ WhenStylusPointerIconEnabledAndDisabledDoesNotCreatePointerController) {
+ // Disable stylus pointer icon and add a stylus device.
+ mChoreographer.setStylusPointerIconEnabled(false);
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}});
+ assertPointerControllerNotCreated();
+
+ // Enable stylus pointer icon. PointerController still should not be created.
+ mChoreographer.setStylusPointerIconEnabled(true);
+ assertPointerControllerNotCreated();
+}
+
+TEST_F(PointerChoreographerTest, WhenStylusHoverEventOccursCreatesPointerController) {
+ // Add a stylus device and enable stylus pointer icon.
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}});
+ mChoreographer.setStylusPointerIconEnabled(true);
+ assertPointerControllerNotCreated();
+
+ // Emit hover event. Now PointerController should be created.
+ mChoreographer.notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
+ .pointer(STYLUS_POINTER)
+ .deviceId(DEVICE_ID)
+ .displayId(DISPLAY_ID)
+ .build());
+ assertPointerControllerCreated(ControllerType::STYLUS);
+}
+
+TEST_F(PointerChoreographerTest,
+ WhenStylusPointerIconDisabledAndHoverEventOccursDoesNotCreatePointerController) {
+ // Add a stylus device and disable stylus pointer icon.
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}});
+ mChoreographer.setStylusPointerIconEnabled(false);
+ assertPointerControllerNotCreated();
+
+ // Emit hover event. Still, PointerController should not be created.
+ mChoreographer.notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
+ .pointer(STYLUS_POINTER)
+ .deviceId(DEVICE_ID)
+ .displayId(DISPLAY_ID)
+ .build());
+ assertPointerControllerNotCreated();
+}
+
+TEST_F(PointerChoreographerTest, WhenStylusDeviceIsRemovedRemovesPointerController) {
+ // Make sure the PointerController is created.
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}});
+ mChoreographer.setStylusPointerIconEnabled(true);
+ assertPointerControllerNotCreated();
+ 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);
+
+ // Remove the device.
+ mChoreographer.notifyInputDevicesChanged({/*id=*/1, {}});
+ assertPointerControllerRemoved(pc);
+}
+
+TEST_F(PointerChoreographerTest, WhenStylusPointerIconDisabledRemovesPointerController) {
+ // Make sure the PointerController is created.
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}});
+ mChoreographer.setStylusPointerIconEnabled(true);
+ assertPointerControllerNotCreated();
+ 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);
+
+ // Disable stylus pointer icon.
+ mChoreographer.setStylusPointerIconEnabled(false);
+ assertPointerControllerRemoved(pc);
+}
+
+TEST_F(PointerChoreographerTest, SetsViewportForStylusPointerController) {
+ // Set viewport.
+ mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+
+ // Make sure the PointerController is created.
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}});
+ mChoreographer.setStylusPointerIconEnabled(true);
+ assertPointerControllerNotCreated();
+ 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);
+
+ // Check that displayId is set.
+ ASSERT_EQ(DISPLAY_ID, pc->getDisplayId());
+}
+
+TEST_F(PointerChoreographerTest, WhenViewportIsSetLaterSetsViewportForStylusPointerController) {
+ // Make sure the PointerController is created.
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}});
+ mChoreographer.setStylusPointerIconEnabled(true);
+ assertPointerControllerNotCreated();
+ 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);
+
+ // Check that displayId is unset.
+ ASSERT_EQ(ADISPLAY_ID_NONE, pc->getDisplayId());
+
+ // Set viewport.
+ mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+
+ // Check that displayId is set.
+ ASSERT_EQ(DISPLAY_ID, pc->getDisplayId());
+}
+
+TEST_F(PointerChoreographerTest,
+ WhenViewportDoesNotMatchDoesNotSetViewportForStylusPointerController) {
+ // Make sure the PointerController is created.
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}});
+ mChoreographer.setStylusPointerIconEnabled(true);
+ assertPointerControllerNotCreated();
+ 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);
+
+ // Check that displayId is unset.
+ ASSERT_EQ(ADISPLAY_ID_NONE, pc->getDisplayId());
+
+ // Set viewport which does not match the associated display of the stylus.
+ mChoreographer.setDisplayViewports(createViewports({ANOTHER_DISPLAY_ID}));
+
+ // Check that displayId is still unset.
+ ASSERT_EQ(ADISPLAY_ID_NONE, pc->getDisplayId());
+}
+
+TEST_F(PointerChoreographerTest, StylusHoverManipulatesPointer) {
+ mChoreographer.setStylusPointerIconEnabled(true);
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}});
+ mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+
+ // Emit hover enter event. This is for creating PointerController.
+ 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);
+
+ // Emit hover move event. After bounds are set, PointerController will update the position.
+ mChoreographer.notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS)
+ .pointer(PointerBuilder(/*id=*/0, ToolType::STYLUS).x(150).y(250))
+ .deviceId(DEVICE_ID)
+ .displayId(DISPLAY_ID)
+ .build());
+ pc->assertPosition(150, 250);
+ ASSERT_TRUE(pc->isPointerShown());
+
+ // Emit hover exit event.
+ mChoreographer.notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_EXIT, AINPUT_SOURCE_STYLUS)
+ .pointer(PointerBuilder(/*id=*/0, ToolType::STYLUS).x(150).y(250))
+ .deviceId(DEVICE_ID)
+ .displayId(DISPLAY_ID)
+ .build());
+ // Check that the pointer is gone.
+ ASSERT_FALSE(pc->isPointerShown());
+}
+
+TEST_F(PointerChoreographerTest, StylusHoverManipulatesPointerForTwoDisplays) {
+ mChoreographer.setStylusPointerIconEnabled(true);
+ // Add two stylus devices associated to different displays.
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0,
+ {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID),
+ generateTestDeviceInfo(SECOND_DEVICE_ID, AINPUT_SOURCE_STYLUS, ANOTHER_DISPLAY_ID)}});
+ mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID, ANOTHER_DISPLAY_ID}));
+
+ // Emit hover event with first device. This is for creating PointerController.
+ mChoreographer.notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
+ .pointer(STYLUS_POINTER)
+ .deviceId(DEVICE_ID)
+ .displayId(DISPLAY_ID)
+ .build());
+ auto firstDisplayPc = assertPointerControllerCreated(ControllerType::STYLUS);
+
+ // Emit hover event with second device. This is for creating PointerController.
+ mChoreographer.notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
+ .pointer(STYLUS_POINTER)
+ .deviceId(SECOND_DEVICE_ID)
+ .displayId(ANOTHER_DISPLAY_ID)
+ .build());
+
+ // There should be another PointerController created.
+ auto secondDisplayPc = assertPointerControllerCreated(ControllerType::STYLUS);
+
+ // Emit hover event with first device.
+ mChoreographer.notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS)
+ .pointer(PointerBuilder(/*id=*/0, ToolType::STYLUS).x(150).y(250))
+ .deviceId(DEVICE_ID)
+ .displayId(DISPLAY_ID)
+ .build());
+
+ // Check the pointer of the first device.
+ firstDisplayPc->assertPosition(150, 250);
+ ASSERT_TRUE(firstDisplayPc->isPointerShown());
+
+ // Emit hover event with second device.
+ mChoreographer.notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS)
+ .pointer(PointerBuilder(/*id=*/0, ToolType::STYLUS).x(250).y(350))
+ .deviceId(SECOND_DEVICE_ID)
+ .displayId(ANOTHER_DISPLAY_ID)
+ .build());
+
+ // Check the pointer of the second device.
+ secondDisplayPc->assertPosition(250, 350);
+ ASSERT_TRUE(secondDisplayPc->isPointerShown());
+
+ // Check that there's no change on the pointer of the first device.
+ firstDisplayPc->assertPosition(150, 250);
+ ASSERT_TRUE(firstDisplayPc->isPointerShown());
+}
+
+TEST_F(PointerChoreographerTest, WhenStylusDeviceIsResetFadesPointer) {
+ // Make sure the PointerController is created and there is a pointer.
+ 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);
+ ASSERT_TRUE(pc->isPointerShown());
+
+ // Reset the device and see the pointer disappeared.
+ mChoreographer.notifyDeviceReset(NotifyDeviceResetArgs(/*id=*/1, /*eventTime=*/0, DEVICE_ID));
+ ASSERT_FALSE(pc->isPointerShown());
+}
+
} // namespace android