Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit d6fe27b5 authored by Byoungho Jung's avatar Byoungho Jung Committed by Prabir Pradhan
Browse files

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
parent 6f5b16bf
Loading
Loading
Loading
Loading
+101 −4
Original line number Diff line number Diff line
@@ -31,6 +31,14 @@ bool isFromMouse(const NotifyMotionArgs& args) {
            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 @@ PointerChoreographer::PointerChoreographer(InputListenerInterface& listener,
        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 @@ NotifyMotionArgs PointerChoreographer::processMotion(const NotifyMotionArgs& arg

    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 @@ void PointerChoreographer::processTouchscreenAndStylusEventLocked(const NotifyMo
    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 @@ void PointerChoreographer::processDeviceReset(const NotifyDeviceResetArgs& args)
        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 @@ void PointerChoreographer::dump(std::string& dump) {

    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 @@ void PointerChoreographer::dump(std::string& dump) {
        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 @@ InputDeviceInfo* PointerChoreographer::findInputDeviceLocked(DeviceId deviceId)
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 @@ void PointerChoreographer::updatePointerControllersLocked() {
            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 @@ void PointerChoreographer::updatePointerControllersLocked() {
        }
        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::setDefaultMouseDisplayId(int32_t displayId) {
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 @@ void PointerChoreographer::setShowTouchesEnabled(bool 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 =
@@ -373,4 +456,18 @@ PointerChoreographer::ControllerConstructor PointerChoreographer::getTouchContro
    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
+7 −0
Original line number Diff line number Diff line
@@ -57,6 +57,7 @@ public:
            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 @@ public:
            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 @@ private:
    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 @@ private:
            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
+2 −0
Original line number Diff line number Diff line
@@ -65,6 +65,8 @@ public:
        MOUSE,
        // Represents multiple touch spots.
        TOUCH,
        // Represents a single stylus pointer.
        STYLUS,
    };

    /* Dumps the state of the pointer controller. */
+270 −0
Original line number Diff line number Diff line
@@ -806,4 +806,274 @@ TEST_F(PointerChoreographerTest, WhenTouchDeviceIsResetClearsSpots) {
    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