Add setPointerIcon for PointerChoreographer

To set pointer icon for mouse or stylus, we are going to use
setPointerIcon with more parameters which will be useful for
multi-device experience and security.

Test: atest inputflinger_tests
Bug: 293587049
Change-Id: Ie0250928ed28c6dc238faa8dcae8ad154ed053fe
diff --git a/services/inputflinger/PointerChoreographer.cpp b/services/inputflinger/PointerChoreographer.cpp
index f1faf69..78ece3b 100644
--- a/services/inputflinger/PointerChoreographer.cpp
+++ b/services/inputflinger/PointerChoreographer.cpp
@@ -445,6 +445,57 @@
     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) {
+        ALOGW("Invalid device id %d. Cannot set pointer icon.", deviceId);
+        return false;
+    }
+    const InputDeviceInfo* info = findInputDeviceLocked(deviceId);
+    if (!info) {
+        ALOGW("No input device info found for id %d. Cannot set pointer icon.", deviceId);
+        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 {
+            ALOGW("No mouse pointer controller found for display %d, device %d.", displayId,
+                  deviceId);
+            return false;
+        }
+    } else {
+        ALOGW("Cannot set pointer icon for display %d, device %d.", displayId, 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..ce14adf 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.
@@ -58,6 +60,9 @@
     virtual FloatPoint getMouseCursorPosition(int32_t displayId) = 0;
     virtual void setShowTouchesEnabled(bool enabled) = 0;
     virtual void setStylusPointerIconEnabled(bool enabled) = 0;
+    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).
      */
@@ -77,6 +82,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/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/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 ""; }
 };