Move external MultiTouch test into InputMapperUnitTest

This test is broken because it relies on an incorrect ACTION_CANCEL
event being generated. Move this test over to the new
InputMapperUnitTest infra before fixing it in subsequent CLs.

Bug: 378308551
Test: TEST=inputflinger_tests; m $TEST && $ANDROID_HOST_OUT/nativetest64/$TEST/$TEST
Flag: TEST_ONLY
Change-Id: I016fb66f7fcbe957cd5b6e8ca86513cb8c372cd3
diff --git a/include/input/PrintTools.h b/include/input/PrintTools.h
index 3470be4..71c215f 100644
--- a/include/input/PrintTools.h
+++ b/include/input/PrintTools.h
@@ -19,13 +19,18 @@
 #include <bitset>
 #include <map>
 #include <optional>
-#include <set>
+#include <ranges>
 #include <sstream>
 #include <string>
 #include <vector>
 
 namespace android {
 
+namespace internal {
+template <typename T>
+concept Container = std::ranges::range<T>;
+}
+
 template <size_t N>
 std::string bitsetToString(const std::bitset<N>& bitset) {
     if (bitset.none()) {
@@ -72,10 +77,12 @@
 /**
  * Convert a set of integral types to string.
  */
-template <typename T>
-std::string dumpSet(const std::set<T>& v, std::string (*toString)(const T&) = constToString) {
+template <internal::Container T>
+std::string dumpContainer(
+        const T& container,
+        std::string (*toString)(const std::ranges::range_value_t<T>&) = constToString) {
     std::string out;
-    for (const T& entry : v) {
+    for (const auto& entry : container) {
         out += out.empty() ? "{" : ", ";
         out += toString(entry);
     }
diff --git a/libs/input/InputConsumerNoResampling.cpp b/libs/input/InputConsumerNoResampling.cpp
index cd85821..6087461 100644
--- a/libs/input/InputConsumerNoResampling.cpp
+++ b/libs/input/InputConsumerNoResampling.cpp
@@ -18,6 +18,7 @@
 #define ATRACE_TAG ATRACE_TAG_INPUT
 
 #include <inttypes.h>
+#include <set>
 
 #include <android-base/logging.h>
 #include <android-base/properties.h>
diff --git a/services/inputflinger/NotifyArgs.cpp b/services/inputflinger/NotifyArgs.cpp
index b2680a2..3de639f 100644
--- a/services/inputflinger/NotifyArgs.cpp
+++ b/services/inputflinger/NotifyArgs.cpp
@@ -206,4 +206,9 @@
     return std::visit(toStringVisitor, args);
 }
 
+std::ostream& operator<<(std::ostream& out, const NotifyArgs& args) {
+    out << toString(args);
+    return out;
+}
+
 } // namespace android
diff --git a/services/inputflinger/PreferStylusOverTouchBlocker.cpp b/services/inputflinger/PreferStylusOverTouchBlocker.cpp
index d9d0450..6864947 100644
--- a/services/inputflinger/PreferStylusOverTouchBlocker.cpp
+++ b/services/inputflinger/PreferStylusOverTouchBlocker.cpp
@@ -216,10 +216,10 @@
 
 std::string PreferStylusOverTouchBlocker::dump() const {
     std::string out;
-    out += "mActiveStyli: " + dumpSet(mActiveStyli) + "\n";
+    out += "mActiveStyli: " + dumpContainer(mActiveStyli) + "\n";
     out += "mLastTouchEvents: " + dumpMap(mLastTouchEvents, constToString, dumpArgs) + "\n";
-    out += "mDevicesWithMixedToolType: " + dumpSet(mDevicesWithMixedToolType) + "\n";
-    out += "mCanceledDevices: " + dumpSet(mCanceledDevices) + "\n";
+    out += "mDevicesWithMixedToolType: " + dumpContainer(mDevicesWithMixedToolType) + "\n";
+    out += "mCanceledDevices: " + dumpContainer(mCanceledDevices) + "\n";
     return out;
 }
 
diff --git a/services/inputflinger/UnwantedInteractionBlocker.cpp b/services/inputflinger/UnwantedInteractionBlocker.cpp
index 0e9ec91..29de635 100644
--- a/services/inputflinger/UnwantedInteractionBlocker.cpp
+++ b/services/inputflinger/UnwantedInteractionBlocker.cpp
@@ -727,7 +727,7 @@
     if (!std::includes(oldSuppressedIds.begin(), oldSuppressedIds.end(),
                        mSuppressedPointerIds.begin(), mSuppressedPointerIds.end())) {
         ALOGI("Palm detected, removing pointer ids %s after %" PRId64 "ms from %s",
-              dumpSet(mSuppressedPointerIds).c_str(), ns2ms(args.eventTime - args.downTime),
+              dumpContainer(mSuppressedPointerIds).c_str(), ns2ms(args.eventTime - args.downTime),
               args.dump().c_str());
     }
 
@@ -748,7 +748,7 @@
     out += "mSlotState:\n";
     out += addLinePrefix(mSlotState.dump(), "  ");
     out += "mSuppressedPointerIds: ";
-    out += dumpSet(mSuppressedPointerIds) + "\n";
+    out += dumpContainer(mSuppressedPointerIds) + "\n";
     std::stringstream state;
     state << *mSharedPalmState;
     out += "mSharedPalmState: " + state.str() + "\n";
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index beb4c92..0412dc5 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -5802,8 +5802,8 @@
         }
         std::set<DeviceId> deviceIds = touchedWindow->getTouchingDeviceIds();
         if (deviceIds.size() != 1) {
-            LOG(INFO) << "Can't transfer touch. Currently touching devices: " << dumpSet(deviceIds)
-                      << " for window: " << touchedWindow->dump();
+            LOG(INFO) << "Can't transfer touch. Currently touching devices: "
+                      << dumpContainer(deviceIds) << " for window: " << touchedWindow->dump();
             return false;
         }
         const DeviceId deviceId = *deviceIds.begin();
diff --git a/services/inputflinger/include/NotifyArgs.h b/services/inputflinger/include/NotifyArgs.h
index 14487fe..c513bfc 100644
--- a/services/inputflinger/include/NotifyArgs.h
+++ b/services/inputflinger/include/NotifyArgs.h
@@ -225,4 +225,6 @@
 
 const char* toString(const NotifyArgs& args);
 
+std::ostream& operator<<(std::ostream& out, const NotifyArgs& args);
+
 } // namespace android
diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp
index a8e4736..d9a75a5 100644
--- a/services/inputflinger/tests/InputReader_test.cpp
+++ b/services/inputflinger/tests/InputReader_test.cpp
@@ -8441,41 +8441,6 @@
                   WithToolType(ToolType::STYLUS))));
 }
 
-// --- MultiTouchInputMapperTest_ExternalDevice ---
-
-class MultiTouchInputMapperTest_ExternalDevice : public MultiTouchInputMapperTest {
-protected:
-    void SetUp() override { InputMapperTest::SetUp(DEVICE_CLASSES | InputDeviceClass::EXTERNAL); }
-};
-
-/**
- * Expect fallback to internal viewport if device is external and external viewport is not present.
- */
-TEST_F(MultiTouchInputMapperTest_ExternalDevice, Viewports_Fallback) {
-    prepareAxes(POSITION);
-    addConfigurationProperty("touch.deviceType", "touchScreen");
-    prepareDisplay(ui::ROTATION_0);
-    MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
-
-    ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, mapper.getSources());
-
-    NotifyMotionArgs motionArgs;
-
-    // Expect the event to be sent to the internal viewport,
-    // because an external viewport is not present.
-    processPosition(mapper, 100, 100);
-    processSync(mapper);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(ui::LogicalDisplayId::DEFAULT, motionArgs.displayId);
-
-    // Expect the event to be sent to the external viewport if it is present.
-    prepareSecondaryDisplay(ViewportType::EXTERNAL);
-    processPosition(mapper, 100, 100);
-    processSync(mapper);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(SECONDARY_DISPLAY_ID, motionArgs.displayId);
-}
-
 // TODO(b/281840344): Remove the test when the old touchpad stack is removed. It is currently
 //  unclear what the behavior of the touchpad logic in TouchInputMapper should do after the
 //  PointerChoreographer refactor.
diff --git a/services/inputflinger/tests/LatencyTracker_test.cpp b/services/inputflinger/tests/LatencyTracker_test.cpp
index 1cfaaa8..d8c5eac 100644
--- a/services/inputflinger/tests/LatencyTracker_test.cpp
+++ b/services/inputflinger/tests/LatencyTracker_test.cpp
@@ -80,8 +80,9 @@
             << "Received timeline with productId=" << received.productId
             << " instead of expected productId=" << expected.productId;
     LOG_IF(ERROR, expected.sources != received.sources)
-            << "Received timeline with sources=" << dumpSet(received.sources, ftl::enum_string)
-            << " instead of expected sources=" << dumpSet(expected.sources, ftl::enum_string);
+            << "Received timeline with sources="
+            << dumpContainer(received.sources, ftl::enum_string)
+            << " instead of expected sources=" << dumpContainer(expected.sources, ftl::enum_string);
     LOG_IF(ERROR, expected.inputEventActionType != received.inputEventActionType)
             << "Received timeline with inputEventActionType="
             << ftl::enum_string(received.inputEventActionType)
diff --git a/services/inputflinger/tests/MultiTouchInputMapper_test.cpp b/services/inputflinger/tests/MultiTouchInputMapper_test.cpp
index b7cb348..e8cca5f 100644
--- a/services/inputflinger/tests/MultiTouchInputMapper_test.cpp
+++ b/services/inputflinger/tests/MultiTouchInputMapper_test.cpp
@@ -37,17 +37,30 @@
 using testing::SetArgPointee;
 using testing::VariantWith;
 
-static constexpr ui::LogicalDisplayId DISPLAY_ID = ui::LogicalDisplayId::DEFAULT;
-static constexpr int32_t DISPLAY_WIDTH = 480;
-static constexpr int32_t DISPLAY_HEIGHT = 800;
-static constexpr std::optional<uint8_t> NO_PORT = std::nullopt; // no physical port is specified
-static constexpr int32_t SLOT_COUNT = 5;
+namespace {
 
-static constexpr int32_t ACTION_POINTER_0_UP =
+constexpr ui::LogicalDisplayId DISPLAY_ID = ui::LogicalDisplayId::DEFAULT;
+constexpr ui::LogicalDisplayId SECOND_DISPLAY_ID = ui::LogicalDisplayId{DISPLAY_ID.val() + 1};
+constexpr int32_t DISPLAY_WIDTH = 480;
+constexpr int32_t DISPLAY_HEIGHT = 800;
+constexpr std::optional<uint8_t> NO_PORT = std::nullopt; // no physical port is specified
+constexpr int32_t SLOT_COUNT = 5;
+
+constexpr int32_t ACTION_DOWN = AMOTION_EVENT_ACTION_DOWN;
+constexpr int32_t ACTION_CANCEL = AMOTION_EVENT_ACTION_CANCEL;
+constexpr int32_t ACTION_POINTER_0_UP =
         AMOTION_EVENT_ACTION_POINTER_UP | (0 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
-static constexpr int32_t ACTION_POINTER_1_DOWN =
+constexpr int32_t ACTION_POINTER_1_DOWN =
         AMOTION_EVENT_ACTION_POINTER_DOWN | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
 
+template <typename... Args>
+void assertNotifyArgs(const std::list<NotifyArgs>& args, Args... matchers) {
+    ASSERT_THAT(args, ElementsAre(matchers...))
+            << "Got instead: " << dumpContainer(args, streamableToString);
+}
+
+} // namespace
+
 /**
  * Unit tests for MultiTouchInputMapper.
  */
@@ -270,6 +283,58 @@
                         VariantWith<NotifyMotionArgs>(WithMotionAction(AMOTION_EVENT_ACTION_UP))));
 }
 
+class ExternalMultiTouchInputMapperTest : public MultiTouchInputMapperUnitTest {
+protected:
+    void SetUp() override { MultiTouchInputMapperUnitTest::SetUp(/*bus=*/0, /*isExternal=*/true); }
+};
+
+/**
+ * Expect fallback to internal viewport if device is external and external viewport is not present.
+ */
+TEST_F(ExternalMultiTouchInputMapperTest, Viewports_Fallback) {
+    std::list<NotifyArgs> args;
+
+    // Expect the event to be sent to the internal viewport,
+    // because an external viewport is not present.
+    args += processKey(BTN_TOUCH, 1);
+    args += processId(1);
+    args += processPosition(100, 200);
+    args += processSync();
+
+    assertNotifyArgs(args,
+                     VariantWith<NotifyMotionArgs>(
+                             AllOf(WithMotionAction(ACTION_DOWN), WithDisplayId(DISPLAY_ID))));
+
+    // Expect the event to be sent to the external viewport if it is present.
+    DisplayViewport externalViewport =
+            createViewport(SECOND_DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
+                           /*isActive=*/true, "local:1", NO_PORT, ViewportType::EXTERNAL);
+    mFakePolicy->addDisplayViewport(externalViewport);
+    std::optional<DisplayViewport> internalViewport =
+            mFakePolicy->getDisplayViewportByUniqueId("local:0");
+    mReaderConfiguration.setDisplayViewports({*internalViewport, externalViewport});
+    args = mMapper->reconfigure(systemTime(SYSTEM_TIME_MONOTONIC), mReaderConfiguration,
+                                InputReaderConfiguration::Change::DISPLAY_INFO);
+
+    assertNotifyArgs(args,
+                     VariantWith<NotifyMotionArgs>(AllOf(WithMotionAction(ACTION_CANCEL),
+                                                         WithDisplayId(SECOND_DISPLAY_ID))),
+                     VariantWith<NotifyDeviceResetArgs>(WithDeviceId(DEVICE_ID)));
+    // Lift up the old pointer.
+    processKey(BTN_TOUCH, 0);
+    args = processId(-1);
+    args += processSync();
+
+    // Send new pointer
+    args += processKey(BTN_TOUCH, 1);
+    args += processId(2);
+    args += processPosition(111, 211);
+    args += processSync();
+    assertNotifyArgs(args,
+                     VariantWith<NotifyMotionArgs>(AllOf(WithMotionAction(ACTION_DOWN),
+                                                         WithDisplayId(SECOND_DISPLAY_ID))));
+}
+
 class MultiTouchInputMapperPointerModeUnitTest : public MultiTouchInputMapperUnitTest {
 protected:
     void SetUp() override {
diff --git a/services/inputflinger/tests/TestEventMatchers.h b/services/inputflinger/tests/TestEventMatchers.h
index 7fb8895..65bc278 100644
--- a/services/inputflinger/tests/TestEventMatchers.h
+++ b/services/inputflinger/tests/TestEventMatchers.h
@@ -505,8 +505,8 @@
         }
 
         if (mPointerIds != actualPointerIds) {
-            *os << "expected pointer ids " << dumpSet(mPointerIds) << ", but got "
-                << dumpSet(actualPointerIds);
+            *os << "expected pointer ids " << dumpContainer(mPointerIds) << ", but got "
+                << dumpContainer(actualPointerIds);
             return false;
         }
         return true;
@@ -519,14 +519,16 @@
         }
 
         if (mPointerIds != actualPointerIds) {
-            *os << "expected pointer ids " << dumpSet(mPointerIds) << ", but got "
-                << dumpSet(actualPointerIds);
+            *os << "expected pointer ids " << dumpContainer(mPointerIds) << ", but got "
+                << dumpContainer(actualPointerIds);
             return false;
         }
         return true;
     }
 
-    void DescribeTo(std::ostream* os) const { *os << "with pointer ids " << dumpSet(mPointerIds); }
+    void DescribeTo(std::ostream* os) const {
+        *os << "with pointer ids " << dumpContainer(mPointerIds);
+    }
 
     void DescribeNegationTo(std::ostream* os) const { *os << "wrong pointer ids"; }