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