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
diff --git a/services/inputflinger/PointerChoreographer.cpp b/services/inputflinger/PointerChoreographer.cpp
index 1092bdb..13fbf1d 100644
--- a/services/inputflinger/PointerChoreographer.cpp
+++ b/services/inputflinger/PointerChoreographer.cpp
@@ -40,7 +40,8 @@
       : 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 @@
     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 @@
  * 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::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 @@
     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 @@
     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 @@
                     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 @@
         }
         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 @@
     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 @@
     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
diff --git a/services/inputflinger/PointerChoreographer.h b/services/inputflinger/PointerChoreographer.h
index c1b900f..84eb1e7 100644
--- a/services/inputflinger/PointerChoreographer.h
+++ b/services/inputflinger/PointerChoreographer.h
@@ -56,6 +56,7 @@
     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 @@
     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 @@
     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 @@
 
     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
diff --git a/services/inputflinger/include/PointerControllerInterface.h b/services/inputflinger/include/PointerControllerInterface.h
index 8837b25..9038056 100644
--- a/services/inputflinger/include/PointerControllerInterface.h
+++ b/services/inputflinger/include/PointerControllerInterface.h
@@ -48,8 +48,8 @@
  */
 class PointerControllerInterface {
 protected:
-    PointerControllerInterface() { }
-    virtual ~PointerControllerInterface() { }
+    PointerControllerInterface() {}
+    virtual ~PointerControllerInterface() {}
 
 public:
     /**
@@ -63,6 +63,8 @@
         LEGACY,
         // Represents a single mouse pointer.
         MOUSE,
+        // Represents multiple touch spots.
+        TOUCH,
     };
 
     /* Dumps the state of the pointer controller. */
@@ -121,7 +123,7 @@
      * pressed (not hovering).
      */
     virtual void setSpots(const PointerCoords* spotCoords, const uint32_t* spotIdToIndex,
-            BitSet32 spotIdBits, int32_t displayId) = 0;
+                          BitSet32 spotIdBits, int32_t displayId) = 0;
 
     /* Removes all spots. */
     virtual void clearSpots() = 0;
diff --git a/services/inputflinger/tests/PointerChoreographer_test.cpp b/services/inputflinger/tests/PointerChoreographer_test.cpp
index 08f3b4c..d556d78 100644
--- a/services/inputflinger/tests/PointerChoreographer_test.cpp
+++ b/services/inputflinger/tests/PointerChoreographer_test.cpp
@@ -47,6 +47,9 @@
 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 @@
     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