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

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

Pointer icon refactor for touch

When PointerChoreographer is enabled, PointerChoreographer can
create multiple TouchPointerControllers for each touch device
when 'Show taps' is enabled. A TouchPointerController is
reponsible for one touch device and it can show touch spots
on the associated display.

Test: atest inputflinger_tests
Bug: 293587049
Change-Id: I1a89a467c3c446cfe7f72444d9c425b45dc8d8ed
parent 1fe1f043
Loading
Loading
Loading
Loading
+100 −4
Original line number Diff line number Diff line
@@ -40,7 +40,8 @@ PointerChoreographer::PointerChoreographer(InputListenerInterface& listener,
      : mNextListener(listener),
        mPolicy(policy),
        mDefaultMouseDisplayId(ADISPLAY_ID_DEFAULT),
        mNotifiedPointerDisplayId(ADISPLAY_ID_NONE) {}
        mNotifiedPointerDisplayId(ADISPLAY_ID_NONE),
        mShowTouchesEnabled(false) {}

void PointerChoreographer::notifyInputDevicesChanged(const NotifyInputDevicesChangedArgs& args) {
    std::scoped_lock _l(mLock);
@@ -70,7 +71,7 @@ NotifyMotionArgs PointerChoreographer::processMotion(const NotifyMotionArgs& arg
    if (isFromMouse(args)) {
        return processMouseEventLocked(args);
    } else if (isFromSource(args.source, AINPUT_SOURCE_TOUCHSCREEN)) {
        return processTouchscreenEventLocked(args);
        processTouchscreenAndStylusEventLocked(args);
    }
    return args;
}
@@ -114,12 +115,44 @@ NotifyMotionArgs PointerChoreographer::processMouseEventLocked(const NotifyMotio
 * 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, getTouchControllerConstructor());

    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::notifySwitch(const NotifySwitchArgs& args) {
@@ -135,9 +168,28 @@ void PointerChoreographer::notifyVibratorState(const NotifyVibratorStateArgs& ar
}

void PointerChoreographer::notifyDeviceReset(const NotifyDeviceResetArgs& args) {
    processDeviceReset(args);

    mNextListener.notify(args);
}

void PointerChoreographer::processDeviceReset(const NotifyDeviceResetArgs& args) {
    std::scoped_lock _l(mLock);

    const InputDeviceInfo* info = findInputDeviceLocked(args.deviceId);
    if (info == nullptr) {
        return;
    }

    if (isFromSource(info->getSources(), AINPUT_SOURCE_TOUCHSCREEN) && mShowTouchesEnabled &&
        info->getAssociatedDisplayId() != ADISPLAY_ID_NONE) {
        if (const auto it = mTouchPointersByDevice.find(args.deviceId);
            it != mTouchPointersByDevice.end()) {
            it->second->clearSpots();
        }
    }
}

void PointerChoreographer::notifyPointerCaptureChanged(
        const NotifyPointerCaptureChangedArgs& args) {
    if (args.request.enable) {
@@ -153,12 +205,18 @@ void PointerChoreographer::dump(std::string& dump) {
    std::scoped_lock _l(mLock);

    dump += "PointerChoreographer:\n";
    dump += StringPrintf("show touches: %s\n", mShowTouchesEnabled ? "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 += "\n";
}

@@ -175,8 +233,18 @@ int32_t PointerChoreographer::getTargetMouseDisplayLocked(int32_t associatedDisp
    return associatedDisplayId == ADISPLAY_ID_NONE ? mDefaultMouseDisplayId : associatedDisplayId;
}

InputDeviceInfo* PointerChoreographer::findInputDeviceLocked(DeviceId deviceId) {
    for (auto& info : mInputDeviceInfos) {
        if (info.getId() == deviceId) {
            return &info;
        }
    }
    return nullptr;
}

void PointerChoreographer::updatePointerControllersLocked() {
    std::set<int32_t /*displayId*/> mouseDisplaysToKeep;
    std::set<DeviceId> touchDevicesToKeep;

    // Mark the displayIds or deviceIds of PointerControllers currently needed.
    for (const auto& info : mInputDeviceInfos) {
@@ -187,6 +255,10 @@ void PointerChoreographer::updatePointerControllersLocked() {
                    getTargetMouseDisplayLocked(info.getAssociatedDisplayId());
            mouseDisplaysToKeep.insert(resolvedDisplayId);
        }
        if (isFromSource(sources, AINPUT_SOURCE_TOUCHSCREEN) && mShowTouchesEnabled &&
            info.getAssociatedDisplayId() != ADISPLAY_ID_NONE) {
            touchDevicesToKeep.insert(info.getId());
        }
    }

    // Remove PointerControllers no longer needed.
@@ -199,6 +271,14 @@ void PointerChoreographer::updatePointerControllersLocked() {
        }
        return false;
    });
    std::erase_if(mTouchPointersByDevice, [&touchDevicesToKeep](const auto& pair) {
        auto& [deviceId, controller] = pair;
        if (touchDevicesToKeep.find(deviceId) == touchDevicesToKeep.end()) {
            controller->clearSpots();
            return true;
        }
        return false;
    });

    // Notify the policy if there's a change on the pointer display ID.
    notifyPointerDisplayIdChangedLocked();
@@ -263,6 +343,15 @@ FloatPoint PointerChoreographer::getMouseCursorPosition(int32_t displayId) {
    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();
}

PointerChoreographer::ControllerConstructor PointerChoreographer::getMouseControllerConstructor(
        int32_t displayId) {
    std::function<std::shared_ptr<PointerControllerInterface>()> ctor =
@@ -277,4 +366,11 @@ PointerChoreographer::ControllerConstructor PointerChoreographer::getMouseContro
    return ConstructorDelegate(std::move(ctor));
}

PointerChoreographer::ControllerConstructor PointerChoreographer::getTouchControllerConstructor() {
    std::function<std::shared_ptr<PointerControllerInterface>()> ctor = [this]() REQUIRES(mLock) {
        return mPolicy.createPointerController(PointerControllerInterface::ControllerType::TOUCH);
    };
    return ConstructorDelegate(std::move(ctor));
}

} // namespace android
+9 −1
Original line number Diff line number Diff line
@@ -56,6 +56,7 @@ public:
    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;
    /**
     * This method may be called on any thread (usually by the input manager on a binder thread).
     */
@@ -73,6 +74,7 @@ public:
    std::optional<DisplayViewport> getViewportForPointerDevice(
            int32_t associatedDisplayId) override;
    FloatPoint getMouseCursorPosition(int32_t displayId) override;
    void setShowTouchesEnabled(bool enabled) override;

    void notifyInputDevicesChanged(const NotifyInputDevicesChangedArgs& args) override;
    void notifyConfigurationChanged(const NotifyConfigurationChangedArgs& args) override;
@@ -91,14 +93,17 @@ private:
    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 processDeviceReset(const NotifyDeviceResetArgs& args);

    using ControllerConstructor =
            ConstructorDelegate<std::function<std::shared_ptr<PointerControllerInterface>()>>;
    ControllerConstructor getMouseControllerConstructor(int32_t displayId) REQUIRES(mLock);
    ControllerConstructor getTouchControllerConstructor() REQUIRES(mLock);

    std::mutex mLock;

@@ -107,11 +112,14 @@ private:

    std::map<int32_t, std::shared_ptr<PointerControllerInterface>> mMousePointersByDisplay
            GUARDED_BY(mLock);
    std::map<DeviceId, std::shared_ptr<PointerControllerInterface>> mTouchPointersByDevice
            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);
};

} // namespace android
+5 −3
Original line number Diff line number Diff line
@@ -63,6 +63,8 @@ public:
        LEGACY,
        // Represents a single mouse pointer.
        MOUSE,
        // Represents multiple touch spots.
        TOUCH,
    };

    /* Dumps the state of the pointer controller. */
+237 −0
Original line number Diff line number Diff line
@@ -47,6 +47,9 @@ 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) {
@@ -569,4 +572,238 @@ TEST_F(PointerChoreographerTest, WhenPointerCaptureEnabledHidesPointer) {
    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);
    assertPointerControllerNotCreated();

    // 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);
    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);

    // 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);
    auto it = pc->getSpots().find(DISPLAY_ID);
    ASSERT_TRUE(it != pc->getSpots().end());
    ASSERT_EQ(size_t(1), it->second.size());

    // 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());
    it = pc->getSpots().find(DISPLAY_ID);
    ASSERT_TRUE(it != pc->getSpots().end());
    ASSERT_EQ(size_t(2), it->second.size());

    // 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());
    it = pc->getSpots().find(DISPLAY_ID);
    ASSERT_TRUE(it != pc->getSpots().end());
    ASSERT_EQ(size_t(1), it->second.size());

    // 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());
    it = pc->getSpots().find(DISPLAY_ID);
    ASSERT_TRUE(it != pc->getSpots().end());
    ASSERT_EQ(size_t(0), it->second.size());
}

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);
    auto it = pc->getSpots().find(DISPLAY_ID);
    ASSERT_TRUE(it != pc->getSpots().end());
    ASSERT_EQ(size_t(1), it->second.size());
}

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);
    auto firstSpotsIt = firstDisplayPc->getSpots().find(DISPLAY_ID);
    ASSERT_TRUE(firstSpotsIt != firstDisplayPc->getSpots().end());
    ASSERT_EQ(size_t(1), firstSpotsIt->second.size());

    // 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.
    auto secondSpotsIt = secondDisplayPc->getSpots().find(ANOTHER_DISPLAY_ID);
    ASSERT_TRUE(secondSpotsIt != secondDisplayPc->getSpots().end());
    ASSERT_EQ(size_t(2), secondSpotsIt->second.size());

    // Check if there's no change on the spot of the first device.
    firstSpotsIt = firstDisplayPc->getSpots().find(DISPLAY_ID);
    ASSERT_TRUE(firstSpotsIt != firstDisplayPc->getSpots().end());
    ASSERT_EQ(size_t(1), firstSpotsIt->second.size());
}

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);
    auto it = pc->getSpots().find(DISPLAY_ID);
    ASSERT_TRUE(it != pc->getSpots().end());
    ASSERT_EQ(size_t(1), it->second.size());

    // Reset the device and see there's no spot.
    mChoreographer.notifyDeviceReset(NotifyDeviceResetArgs(/*id=*/1, /*eventTime=*/0, DEVICE_ID));
    it = pc->getSpots().find(DISPLAY_ID);
    ASSERT_TRUE(it == pc->getSpots().end());
}

} // namespace android