Merge changes from topics "pointer-icon-refactor-comments", "pointer-icon-refactor-stylus", "pointer-icon-refactor-touch" into main
* changes:
Address additional comments: Pointer icon refactor for touch/stylus
Pointer icon refactor for stylus
Pointer icon refactor for touch
Add unit test cases for PointerChoreographer
diff --git a/services/inputflinger/PointerChoreographer.cpp b/services/inputflinger/PointerChoreographer.cpp
index 1092bdb..e529bdd 100644
--- a/services/inputflinger/PointerChoreographer.cpp
+++ b/services/inputflinger/PointerChoreographer.cpp
@@ -31,16 +31,30 @@
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 ---
PointerChoreographer::PointerChoreographer(InputListenerInterface& listener,
PointerChoreographerPolicyInterface& policy)
- : mNextListener(listener),
+ : mTouchControllerConstructor([this]() REQUIRES(mLock) {
+ return mPolicy.createPointerController(
+ PointerControllerInterface::ControllerType::TOUCH);
+ }),
+ mNextListener(listener),
mPolicy(policy),
mDefaultMouseDisplayId(ADISPLAY_ID_DEFAULT),
- mNotifiedPointerDisplayId(ADISPLAY_ID_NONE) {}
+ mNotifiedPointerDisplayId(ADISPLAY_ID_NONE),
+ mShowTouchesEnabled(false),
+ mStylusPointerIconEnabled(false) {}
void PointerChoreographer::notifyInputDevicesChanged(const NotifyInputDevicesChangedArgs& args) {
std::scoped_lock _l(mLock);
@@ -69,8 +83,10 @@
if (isFromMouse(args)) {
return processMouseEventLocked(args);
+ } else if (mStylusPointerIconEnabled && isStylusHoverEvent(args)) {
+ processStylusHoverEventLocked(args);
} else if (isFromSource(args.source, AINPUT_SOURCE_TOUCHSCREEN)) {
- return processTouchscreenEventLocked(args);
+ processTouchscreenAndStylusEventLocked(args);
}
return args;
}
@@ -114,12 +130,70 @@
* mouse device keeps moving and unfades the cursor.
* For touch events, we do not need to populate the cursor position.
*/
-NotifyMotionArgs PointerChoreographer::processTouchscreenEventLocked(const NotifyMotionArgs& args) {
+void PointerChoreographer::processTouchscreenAndStylusEventLocked(const NotifyMotionArgs& args) {
+ if (args.displayId == ADISPLAY_ID_NONE) {
+ return;
+ }
+
if (const auto it = mMousePointersByDisplay.find(args.displayId);
it != mMousePointersByDisplay.end() && args.action == AMOTION_EVENT_ACTION_DOWN) {
it->second->fade(PointerControllerInterface::Transition::GRADUAL);
}
- return args;
+
+ if (!mShowTouchesEnabled) {
+ return;
+ }
+
+ // Get the touch pointer controller for the device, or create one if it doesn't exist.
+ auto [it, _] = mTouchPointersByDevice.try_emplace(args.deviceId, mTouchControllerConstructor);
+
+ PointerControllerInterface& pc = *it->second;
+
+ const PointerCoords* coords = args.pointerCoords.data();
+ const int32_t maskedAction = MotionEvent::getActionMasked(args.action);
+ const uint8_t actionIndex = MotionEvent::getActionIndex(args.action);
+ std::array<uint32_t, MAX_POINTER_ID + 1> idToIndex;
+ BitSet32 idBits;
+ if (maskedAction != AMOTION_EVENT_ACTION_UP && maskedAction != AMOTION_EVENT_ACTION_CANCEL) {
+ for (size_t i = 0; i < args.getPointerCount(); i++) {
+ if (maskedAction == AMOTION_EVENT_ACTION_POINTER_UP && actionIndex == i) {
+ continue;
+ }
+ uint32_t id = args.pointerProperties[i].id;
+ idToIndex[id] = i;
+ idBits.markBit(id);
+ }
+ }
+ // The PointerController already handles setting spots per-display, so
+ // we do not need to manually manage display changes for touch spots for now.
+ 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) {
@@ -135,9 +209,17 @@
}
void PointerChoreographer::notifyDeviceReset(const NotifyDeviceResetArgs& args) {
+ processDeviceReset(args);
+
mNextListener.notify(args);
}
+void PointerChoreographer::processDeviceReset(const NotifyDeviceResetArgs& args) {
+ std::scoped_lock _l(mLock);
+ mTouchPointersByDevice.erase(args.deviceId);
+ mStylusPointersByDevice.erase(args.deviceId);
+}
+
void PointerChoreographer::notifyPointerCaptureChanged(
const NotifyPointerCaptureChangedArgs& args) {
if (args.request.enable) {
@@ -153,12 +235,25 @@
std::scoped_lock _l(mLock);
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) {
std::string pointerControllerDump = addLinePrefix(mousePointerController->dump(), INDENT);
dump += INDENT + std::to_string(displayId) + " : " + pointerControllerDump;
}
+ dump += INDENT "TouchPointerControllers:\n";
+ for (const auto& [deviceId, touchPointerController] : mTouchPointersByDevice) {
+ 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";
}
@@ -175,8 +270,16 @@
return associatedDisplayId == ADISPLAY_ID_NONE ? mDefaultMouseDisplayId : associatedDisplayId;
}
+InputDeviceInfo* PointerChoreographer::findInputDeviceLocked(DeviceId deviceId) {
+ auto it = std::find_if(mInputDeviceInfos.begin(), mInputDeviceInfos.end(),
+ [deviceId](const auto& info) { return info.getId() == deviceId; });
+ return it != mInputDeviceInfos.end() ? &(*it) : nullptr;
+}
+
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) {
@@ -187,17 +290,25 @@
getTargetMouseDisplayLocked(info.getAssociatedDisplayId());
mouseDisplaysToKeep.insert(resolvedDisplayId);
}
+ if (isFromSource(sources, AINPUT_SOURCE_TOUCHSCREEN) && mShowTouchesEnabled &&
+ 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.
- // This has the side-effect of fading pointers or clearing spots before removal.
std::erase_if(mMousePointersByDisplay, [&mouseDisplaysToKeep](const auto& pair) {
- auto& [displayId, controller] = pair;
- if (mouseDisplaysToKeep.find(displayId) == mouseDisplaysToKeep.end()) {
- controller->fade(PointerControllerInterface::Transition::IMMEDIATE);
- return true;
- }
- return false;
+ return mouseDisplaysToKeep.find(pair.first) == mouseDisplaysToKeep.end();
+ });
+ std::erase_if(mTouchPointersByDevice, [&touchDevicesToKeep](const auto& pair) {
+ return touchDevicesToKeep.find(pair.first) == touchDevicesToKeep.end();
+ });
+ std::erase_if(mStylusPointersByDevice, [&stylusDevicesToKeep](const auto& pair) {
+ return stylusDevicesToKeep.find(pair.first) == stylusDevicesToKeep.end();
});
// Notify the policy if there's a change on the pointer display ID.
@@ -234,10 +345,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();
@@ -263,6 +381,24 @@
return {AMOTION_EVENT_INVALID_CURSOR_POSITION, AMOTION_EVENT_INVALID_CURSOR_POSITION};
}
+void PointerChoreographer::setShowTouchesEnabled(bool enabled) {
+ std::scoped_lock _l(mLock);
+ if (mShowTouchesEnabled == enabled) {
+ return;
+ }
+ mShowTouchesEnabled = enabled;
+ 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 =
@@ -277,4 +413,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 c1b900f..26d2fef 100644
--- a/services/inputflinger/PointerChoreographer.h
+++ b/services/inputflinger/PointerChoreographer.h
@@ -56,6 +56,8 @@
virtual std::optional<DisplayViewport> getViewportForPointerDevice(
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).
*/
@@ -73,6 +75,8 @@
std::optional<DisplayViewport> getViewportForPointerDevice(
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;
@@ -91,14 +95,19 @@
void notifyPointerDisplayIdChangedLocked() REQUIRES(mLock);
const DisplayViewport* findViewportByIdLocked(int32_t displayId) const REQUIRES(mLock);
int32_t getTargetMouseDisplayLocked(int32_t associatedDisplayId) const REQUIRES(mLock);
+ InputDeviceInfo* findInputDeviceLocked(DeviceId deviceId) REQUIRES(mLock);
NotifyMotionArgs processMotion(const NotifyMotionArgs& args);
NotifyMotionArgs processMouseEventLocked(const NotifyMotionArgs& args) REQUIRES(mLock);
- NotifyMotionArgs processTouchscreenEventLocked(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 mTouchControllerConstructor GUARDED_BY(mLock);
ControllerConstructor getMouseControllerConstructor(int32_t displayId) REQUIRES(mLock);
+ ControllerConstructor getStylusControllerConstructor(int32_t displayId) REQUIRES(mLock);
std::mutex mLock;
@@ -107,11 +116,17 @@
std::map<int32_t, std::shared_ptr<PointerControllerInterface>> mMousePointersByDisplay
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 8837b25..ef74a55 100644
--- a/services/inputflinger/include/PointerControllerInterface.h
+++ b/services/inputflinger/include/PointerControllerInterface.h
@@ -48,8 +48,8 @@
*/
class PointerControllerInterface {
protected:
- PointerControllerInterface() { }
- virtual ~PointerControllerInterface() { }
+ PointerControllerInterface() {}
+ virtual ~PointerControllerInterface() {}
public:
/**
@@ -63,6 +63,10 @@
LEGACY,
// Represents a single mouse pointer.
MOUSE,
+ // Represents multiple touch spots.
+ TOUCH,
+ // Represents a single stylus pointer.
+ STYLUS,
};
/* Dumps the state of the pointer controller. */
@@ -121,7 +125,7 @@
* pressed (not hovering).
*/
virtual void setSpots(const PointerCoords* spotCoords, const uint32_t* spotIdToIndex,
- BitSet32 spotIdBits, int32_t displayId) = 0;
+ BitSet32 spotIdBits, int32_t displayId) = 0;
/* Removes all spots. */
virtual void clearSpots() = 0;
diff --git a/services/inputflinger/tests/FakePointerController.cpp b/services/inputflinger/tests/FakePointerController.cpp
index ca517f3..5475594 100644
--- a/services/inputflinger/tests/FakePointerController.cpp
+++ b/services/inputflinger/tests/FakePointerController.cpp
@@ -47,6 +47,8 @@
void FakePointerController::setDisplayViewport(const DisplayViewport& viewport) {
mDisplayId = viewport.displayId;
+ setBounds(viewport.logicalLeft, viewport.logicalTop, viewport.logicalRight - 1,
+ viewport.logicalBottom - 1);
}
void FakePointerController::assertPosition(float x, float y) {
@@ -55,6 +57,12 @@
ASSERT_NEAR(y, actualY, 1);
}
+void FakePointerController::assertSpotCount(int32_t displayId, int32_t count) {
+ auto it = mSpotsByDisplay.find(displayId);
+ ASSERT_TRUE(it != mSpotsByDisplay.end()) << "Spots not found for display " << displayId;
+ ASSERT_EQ(static_cast<size_t>(count), it->second.size());
+}
+
bool FakePointerController::isPointerShown() {
return mIsPointerShown;
}
diff --git a/services/inputflinger/tests/FakePointerController.h b/services/inputflinger/tests/FakePointerController.h
index c75f6ed..d7e40b3 100644
--- a/services/inputflinger/tests/FakePointerController.h
+++ b/services/inputflinger/tests/FakePointerController.h
@@ -37,6 +37,7 @@
void setDisplayViewport(const DisplayViewport& viewport) override;
void assertPosition(float x, float y);
+ void assertSpotCount(int32_t displayId, int32_t count);
bool isPointerShown();
private:
diff --git a/services/inputflinger/tests/PointerChoreographer_test.cpp b/services/inputflinger/tests/PointerChoreographer_test.cpp
index da2e205..68f5857 100644
--- a/services/inputflinger/tests/PointerChoreographer_test.cpp
+++ b/services/inputflinger/tests/PointerChoreographer_test.cpp
@@ -27,6 +27,7 @@
namespace android {
using ControllerType = PointerControllerInterface::ControllerType;
+using testing::AllOf;
namespace {
@@ -37,12 +38,18 @@
Visitor(V...) -> Visitor<V...>;
constexpr int32_t DEVICE_ID = 3;
+constexpr int32_t SECOND_DEVICE_ID = DEVICE_ID + 1;
constexpr int32_t DISPLAY_ID = 5;
constexpr int32_t ANOTHER_DISPLAY_ID = 10;
+constexpr int32_t DISPLAY_WIDTH = 480;
+constexpr int32_t DISPLAY_HEIGHT = 800;
const auto MOUSE_POINTER = PointerBuilder(/*id=*/0, ToolType::MOUSE)
.axis(AMOTION_EVENT_AXIS_RELATIVE_X, 10)
.axis(AMOTION_EVENT_AXIS_RELATIVE_Y, 20);
+const auto FIRST_TOUCH_POINTER = PointerBuilder(/*id=*/0, ToolType::FINGER).x(100).y(200);
+const auto SECOND_TOUCH_POINTER = PointerBuilder(/*id=*/1, ToolType::FINGER).x(200).y(300);
+const auto STYLUS_POINTER = PointerBuilder(/*id=*/0, ToolType::STYLUS).x(100).y(200);
static InputDeviceInfo generateTestDeviceInfo(int32_t deviceId, uint32_t source,
int32_t associatedDisplayId) {
@@ -60,6 +67,8 @@
for (auto displayId : displayIds) {
DisplayViewport viewport;
viewport.displayId = displayId;
+ viewport.logicalRight = DISPLAY_WIDTH;
+ viewport.logicalBottom = DISPLAY_HEIGHT;
viewports.push_back(viewport);
}
return viewports;
@@ -115,6 +124,7 @@
EXPECT_FALSE(mLastCreatedController.has_value())
<< "More than one PointerController created at a time";
std::shared_ptr<FakePointerController> pc = std::make_shared<FakePointerController>();
+ EXPECT_FALSE(pc->isPointerShown());
mLastCreatedController = {type, pc};
return pc;
}
@@ -385,4 +395,659 @@
assertPointerDisplayIdNotified(ANOTHER_DISPLAY_ID);
}
+TEST_F(PointerChoreographerTest, MouseMovesPointerAndReturnsNewArgs) {
+ 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());
+ mTestListener.assertNotifyMotionWasCalled(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE));
+ auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
+ ASSERT_EQ(DISPLAY_ID, pc->getDisplayId());
+
+ // Set bounds and initial position of the PointerController.
+ pc->setPosition(100, 200);
+
+ // Make NotifyMotionArgs and notify Choreographer.
+ mChoreographer.notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+ .pointer(MOUSE_POINTER)
+ .deviceId(DEVICE_ID)
+ .displayId(ADISPLAY_ID_NONE)
+ .build());
+
+ // Check that the PointerController updated the position and the pointer is shown.
+ pc->assertPosition(110, 220);
+ ASSERT_TRUE(pc->isPointerShown());
+
+ // Check that x-y cooridnates, displayId and cursor position are correctly updated.
+ mTestListener.assertNotifyMotionWasCalled(
+ AllOf(WithCoords(110, 220), WithDisplayId(DISPLAY_ID), WithCursorPosition(110, 220)));
+}
+
+TEST_F(PointerChoreographerTest,
+ AssociatedMouseMovesPointerOnAssociatedDisplayAndDoesNotMovePointerOnDefaultDisplay) {
+ // Add two displays and set one to default.
+ mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID, ANOTHER_DISPLAY_ID}));
+ mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+
+ // Add two devices, one unassociated and the other associated with non-default mouse display.
+ 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());
+ mTestListener.assertNotifyMotionWasCalled(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE));
+ auto unassociatedMousePc = assertPointerControllerCreated(ControllerType::MOUSE);
+ ASSERT_EQ(DISPLAY_ID, unassociatedMousePc->getDisplayId());
+
+ mChoreographer.notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+ .pointer(MOUSE_POINTER)
+ .deviceId(SECOND_DEVICE_ID)
+ .displayId(ANOTHER_DISPLAY_ID)
+ .build());
+ mTestListener.assertNotifyMotionWasCalled(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE));
+ auto associatedMousePc = assertPointerControllerCreated(ControllerType::MOUSE);
+ ASSERT_EQ(ANOTHER_DISPLAY_ID, associatedMousePc->getDisplayId());
+
+ // Set bounds and initial position for PointerControllers.
+ unassociatedMousePc->setPosition(100, 200);
+ associatedMousePc->setPosition(300, 400);
+
+ // Make NotifyMotionArgs from the associated mouse and notify Choreographer.
+ mChoreographer.notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+ .pointer(MOUSE_POINTER)
+ .deviceId(SECOND_DEVICE_ID)
+ .displayId(ANOTHER_DISPLAY_ID)
+ .build());
+
+ // Check the status of the PointerControllers.
+ unassociatedMousePc->assertPosition(100, 200);
+ ASSERT_EQ(DISPLAY_ID, unassociatedMousePc->getDisplayId());
+ associatedMousePc->assertPosition(310, 420);
+ ASSERT_EQ(ANOTHER_DISPLAY_ID, associatedMousePc->getDisplayId());
+ ASSERT_TRUE(associatedMousePc->isPointerShown());
+
+ // Check that x-y cooridnates, displayId and cursor position are correctly updated.
+ mTestListener.assertNotifyMotionWasCalled(
+ AllOf(WithCoords(310, 420), WithDeviceId(SECOND_DEVICE_ID),
+ WithDisplayId(ANOTHER_DISPLAY_ID), WithCursorPosition(310, 420)));
+}
+
+TEST_F(PointerChoreographerTest, DoesNotMovePointerForMouseRelativeSource) {
+ 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());
+ mTestListener.assertNotifyMotionWasCalled(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE));
+ auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
+ ASSERT_EQ(DISPLAY_ID, pc->getDisplayId());
+
+ // Set bounds and initial position of the PointerController.
+ pc->setPosition(100, 200);
+
+ // Assume that pointer capture is enabled.
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/1,
+ {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE_RELATIVE, ADISPLAY_ID_NONE)}});
+ mChoreographer.notifyPointerCaptureChanged(
+ NotifyPointerCaptureChangedArgs(/*id=*/2, systemTime(SYSTEM_TIME_MONOTONIC),
+ PointerCaptureRequest(/*enable=*/true, /*seq=*/0)));
+
+ // Notify motion as if pointer capture is enabled.
+ mChoreographer.notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_MOUSE_RELATIVE)
+ .pointer(PointerBuilder(/*id=*/0, ToolType::MOUSE)
+ .x(10)
+ .y(20)
+ .axis(AMOTION_EVENT_AXIS_RELATIVE_X, 10)
+ .axis(AMOTION_EVENT_AXIS_RELATIVE_Y, 20))
+ .deviceId(DEVICE_ID)
+ .displayId(ADISPLAY_ID_NONE)
+ .build());
+
+ // Check that there's no update on the PointerController.
+ pc->assertPosition(100, 200);
+ ASSERT_FALSE(pc->isPointerShown());
+
+ // Check x-y cooridnates, displayId and cursor position are not changed.
+ mTestListener.assertNotifyMotionWasCalled(
+ AllOf(WithCoords(10, 20), WithRelativeMotion(10, 20), WithDisplayId(ADISPLAY_ID_NONE),
+ WithCursorPosition(AMOTION_EVENT_INVALID_CURSOR_POSITION,
+ AMOTION_EVENT_INVALID_CURSOR_POSITION)));
+}
+
+TEST_F(PointerChoreographerTest, WhenPointerCaptureEnabledHidesPointer) {
+ 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());
+ mTestListener.assertNotifyMotionWasCalled(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE));
+ auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
+ ASSERT_EQ(DISPLAY_ID, pc->getDisplayId());
+
+ // Set bounds and initial position of the PointerController.
+ pc->setPosition(100, 200);
+
+ // Make NotifyMotionArgs and notify Choreographer.
+ mChoreographer.notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+ .pointer(MOUSE_POINTER)
+ .deviceId(DEVICE_ID)
+ .displayId(ADISPLAY_ID_NONE)
+ .build());
+
+ // Check that the PointerController updated the position and the pointer is shown.
+ pc->assertPosition(110, 220);
+ ASSERT_TRUE(pc->isPointerShown());
+
+ // Enable pointer capture and check if the PointerController hid the pointer.
+ mChoreographer.notifyPointerCaptureChanged(
+ NotifyPointerCaptureChangedArgs(/*id=*/1, systemTime(SYSTEM_TIME_MONOTONIC),
+ PointerCaptureRequest(/*enable=*/true, /*seq=*/0)));
+ ASSERT_FALSE(pc->isPointerShown());
+}
+
+TEST_F(PointerChoreographerTest, WhenShowTouchesEnabledAndDisabledDoesNotCreatePointerController) {
+ // Disable show touches and add a touch device.
+ mChoreographer.setShowTouchesEnabled(false);
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN, DISPLAY_ID)}});
+ assertPointerControllerNotCreated();
+
+ // Enable show touches. PointerController still should not be created.
+ mChoreographer.setShowTouchesEnabled(true);
+ assertPointerControllerNotCreated();
+}
+
+TEST_F(PointerChoreographerTest, WhenTouchEventOccursCreatesPointerController) {
+ // Add a touch device and enable show touches.
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN, DISPLAY_ID)}});
+ mChoreographer.setShowTouchesEnabled(true);
+
+ // Emit touch event. Now PointerController should be created.
+ mChoreographer.notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+ .pointer(FIRST_TOUCH_POINTER)
+ .deviceId(DEVICE_ID)
+ .displayId(DISPLAY_ID)
+ .build());
+ assertPointerControllerCreated(ControllerType::TOUCH);
+}
+
+TEST_F(PointerChoreographerTest,
+ WhenShowTouchesDisabledAndTouchEventOccursDoesNotCreatePointerController) {
+ // Add a touch device and disable show touches.
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN, DISPLAY_ID)}});
+ mChoreographer.setShowTouchesEnabled(false);
+ assertPointerControllerNotCreated();
+
+ // Emit touch event. Still, PointerController should not be created.
+ mChoreographer.notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+ .pointer(FIRST_TOUCH_POINTER)
+ .deviceId(DEVICE_ID)
+ .displayId(DISPLAY_ID)
+ .build());
+ assertPointerControllerNotCreated();
+}
+
+TEST_F(PointerChoreographerTest, WhenTouchDeviceIsRemovedRemovesPointerController) {
+ // Make sure the PointerController is created.
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN, DISPLAY_ID)}});
+ mChoreographer.setShowTouchesEnabled(true);
+ mChoreographer.notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+ .pointer(FIRST_TOUCH_POINTER)
+ .deviceId(DEVICE_ID)
+ .displayId(DISPLAY_ID)
+ .build());
+ auto pc = assertPointerControllerCreated(ControllerType::TOUCH);
+
+ // Remove the device.
+ mChoreographer.notifyInputDevicesChanged({/*id=*/1, {}});
+ assertPointerControllerRemoved(pc);
+}
+
+TEST_F(PointerChoreographerTest, WhenShowTouchesDisabledRemovesPointerController) {
+ // Make sure the PointerController is created.
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN, DISPLAY_ID)}});
+ mChoreographer.setShowTouchesEnabled(true);
+ assertPointerControllerNotCreated();
+ mChoreographer.notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+ .pointer(FIRST_TOUCH_POINTER)
+ .deviceId(DEVICE_ID)
+ .displayId(DISPLAY_ID)
+ .build());
+ auto pc = assertPointerControllerCreated(ControllerType::TOUCH);
+
+ // Disable show touches.
+ mChoreographer.setShowTouchesEnabled(false);
+ assertPointerControllerRemoved(pc);
+}
+
+TEST_F(PointerChoreographerTest, TouchSetsSpots) {
+ mChoreographer.setShowTouchesEnabled(true);
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN, DISPLAY_ID)}});
+
+ // Emit first pointer down.
+ mChoreographer.notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+ .pointer(FIRST_TOUCH_POINTER)
+ .deviceId(DEVICE_ID)
+ .displayId(DISPLAY_ID)
+ .build());
+ auto pc = assertPointerControllerCreated(ControllerType::TOUCH);
+ pc->assertSpotCount(DISPLAY_ID, 1);
+
+ // Emit second pointer down.
+ mChoreographer.notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_POINTER_DOWN |
+ (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+ AINPUT_SOURCE_TOUCHSCREEN)
+ .pointer(FIRST_TOUCH_POINTER)
+ .pointer(SECOND_TOUCH_POINTER)
+ .deviceId(DEVICE_ID)
+ .displayId(DISPLAY_ID)
+ .build());
+ pc->assertSpotCount(DISPLAY_ID, 2);
+
+ // Emit second pointer up.
+ mChoreographer.notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_POINTER_UP |
+ (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+ AINPUT_SOURCE_TOUCHSCREEN)
+ .pointer(FIRST_TOUCH_POINTER)
+ .pointer(SECOND_TOUCH_POINTER)
+ .deviceId(DEVICE_ID)
+ .displayId(DISPLAY_ID)
+ .build());
+ pc->assertSpotCount(DISPLAY_ID, 1);
+
+ // Emit first pointer up.
+ mChoreographer.notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN)
+ .pointer(FIRST_TOUCH_POINTER)
+ .deviceId(DEVICE_ID)
+ .displayId(DISPLAY_ID)
+ .build());
+ pc->assertSpotCount(DISPLAY_ID, 0);
+}
+
+TEST_F(PointerChoreographerTest, TouchSetsSpotsForStylusEvent) {
+ mChoreographer.setShowTouchesEnabled(true);
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0,
+ {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN | AINPUT_SOURCE_STYLUS,
+ DISPLAY_ID)}});
+
+ // Emit down event with stylus properties.
+ mChoreographer.notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN,
+ AINPUT_SOURCE_TOUCHSCREEN | AINPUT_SOURCE_STYLUS)
+ .pointer(STYLUS_POINTER)
+ .deviceId(DEVICE_ID)
+ .displayId(DISPLAY_ID)
+ .build());
+ auto pc = assertPointerControllerCreated(ControllerType::TOUCH);
+ pc->assertSpotCount(DISPLAY_ID, 1);
+}
+
+TEST_F(PointerChoreographerTest, TouchSetsSpotsForTwoDisplays) {
+ mChoreographer.setShowTouchesEnabled(true);
+ // Add two touch devices associated to different displays.
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0,
+ {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN, DISPLAY_ID),
+ generateTestDeviceInfo(SECOND_DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN,
+ ANOTHER_DISPLAY_ID)}});
+
+ // Emit touch event with first device.
+ mChoreographer.notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+ .pointer(FIRST_TOUCH_POINTER)
+ .deviceId(DEVICE_ID)
+ .displayId(DISPLAY_ID)
+ .build());
+ auto firstDisplayPc = assertPointerControllerCreated(ControllerType::TOUCH);
+ firstDisplayPc->assertSpotCount(DISPLAY_ID, 1);
+
+ // Emit touch events with second device.
+ mChoreographer.notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+ .pointer(FIRST_TOUCH_POINTER)
+ .deviceId(SECOND_DEVICE_ID)
+ .displayId(ANOTHER_DISPLAY_ID)
+ .build());
+ mChoreographer.notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_POINTER_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+ .pointer(FIRST_TOUCH_POINTER)
+ .pointer(SECOND_TOUCH_POINTER)
+ .deviceId(SECOND_DEVICE_ID)
+ .displayId(ANOTHER_DISPLAY_ID)
+ .build());
+
+ // There should be another PointerController created.
+ auto secondDisplayPc = assertPointerControllerCreated(ControllerType::TOUCH);
+
+ // Check if the spots are set for the second device.
+ secondDisplayPc->assertSpotCount(ANOTHER_DISPLAY_ID, 2);
+
+ // Check if there's no change on the spot of the first device.
+ firstDisplayPc->assertSpotCount(DISPLAY_ID, 1);
+}
+
+TEST_F(PointerChoreographerTest, WhenTouchDeviceIsResetClearsSpots) {
+ // Make sure the PointerController is created and there is a spot.
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN, DISPLAY_ID)}});
+ mChoreographer.setShowTouchesEnabled(true);
+ mChoreographer.notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+ .pointer(FIRST_TOUCH_POINTER)
+ .deviceId(DEVICE_ID)
+ .displayId(DISPLAY_ID)
+ .build());
+ auto pc = assertPointerControllerCreated(ControllerType::TOUCH);
+ pc->assertSpotCount(DISPLAY_ID, 1);
+
+ // Reset the device and ensure the touch pointer controller was removed.
+ mChoreographer.notifyDeviceReset(NotifyDeviceResetArgs(/*id=*/1, /*eventTime=*/0, DEVICE_ID));
+ assertPointerControllerRemoved(pc);
+}
+
+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);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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, WhenStylusDeviceIsResetRemovesPointer) {
+ // 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 controller was removed.
+ mChoreographer.notifyDeviceReset(NotifyDeviceResetArgs(/*id=*/1, /*eventTime=*/0, DEVICE_ID));
+ assertPointerControllerRemoved(pc);
+}
+
} // namespace android