Merge "Erase touch state if empty"
diff --git a/services/inputflinger/TEST_MAPPING b/services/inputflinger/TEST_MAPPING
index 4a0f2ec..b3b4fb1 100644
--- a/services/inputflinger/TEST_MAPPING
+++ b/services/inputflinger/TEST_MAPPING
@@ -41,6 +41,7 @@
           "include-filter": "android.view.cts.input",
           "include-filter": "android.view.cts.MotionEventTest",
           "include-filter": "android.view.cts.PointerCaptureTest",
+          "include-filter": "android.view.cts.TooltipTest",
           "include-filter": "android.view.cts.VerifyInputEventTest"
         }
       ]
@@ -128,6 +129,7 @@
         {
           "include-filter": "android.view.cts.MotionEventTest",
           "include-filter": "android.view.cts.PointerCaptureTest",
+          "include-filter": "android.view.cts.TooltipTest",
           "include-filter": "android.view.cts.VerifyInputEventTest"
         }
       ]
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index 87a4ff4..eb938c8 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -1769,15 +1769,16 @@
 
 void InputDispatcher::logOutboundMotionDetails(const char* prefix, const MotionEntry& entry) {
     if (DEBUG_OUTBOUND_EVENT_DETAILS) {
-        ALOGD("%seventTime=%" PRId64 ", deviceId=%d, source=0x%x, displayId=%" PRId32
+        ALOGD("%seventTime=%" PRId64 ", deviceId=%d, source=%s, displayId=%" PRId32
               ", policyFlags=0x%x, "
               "action=%s, actionButton=0x%x, flags=0x%x, "
               "metaState=0x%x, buttonState=0x%x,"
               "edgeFlags=0x%x, xPrecision=%f, yPrecision=%f, downTime=%" PRId64,
-              prefix, entry.eventTime, entry.deviceId, entry.source, entry.displayId,
-              entry.policyFlags, MotionEvent::actionToString(entry.action).c_str(),
-              entry.actionButton, entry.flags, entry.metaState, entry.buttonState, entry.edgeFlags,
-              entry.xPrecision, entry.yPrecision, entry.downTime);
+              prefix, entry.eventTime, entry.deviceId,
+              inputEventSourceToString(entry.source).c_str(), entry.displayId, entry.policyFlags,
+              MotionEvent::actionToString(entry.action).c_str(), entry.actionButton, entry.flags,
+              entry.metaState, entry.buttonState, entry.edgeFlags, entry.xPrecision,
+              entry.yPrecision, entry.downTime);
 
         for (uint32_t i = 0; i < entry.pointerCount; i++) {
             ALOGD("  Pointer %d: id=%d, toolType=%d, "
@@ -2502,6 +2503,10 @@
         }
     }
 
+    if (tempTouchState.windows.empty()) {
+        mTouchStatesByDisplay.erase(displayId);
+    }
+
     // Update hover state.
     mLastHoverWindowHandle = newHoverWindowHandle;
 
diff --git a/services/inputflinger/dispatcher/TouchState.cpp b/services/inputflinger/dispatcher/TouchState.cpp
index ee7da93..114e0bf 100644
--- a/services/inputflinger/dispatcher/TouchState.cpp
+++ b/services/inputflinger/dispatcher/TouchState.cpp
@@ -148,7 +148,8 @@
 
 std::string TouchState::dump() const {
     std::string out;
-    out += StringPrintf("deviceId=%d, source=0x%08x\n", deviceId, source);
+    out += StringPrintf("deviceId=%d, source=%s\n", deviceId,
+                        inputEventSourceToString(source).c_str());
     if (!windows.empty()) {
         out += "  Windows:\n";
         for (size_t i = 0; i < windows.size(); i++) {
diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp
index aaf50ce..689fe5c 100644
--- a/services/inputflinger/tests/InputDispatcher_test.cpp
+++ b/services/inputflinger/tests/InputDispatcher_test.cpp
@@ -22,6 +22,7 @@
 #include <android-base/thread_annotations.h>
 #include <binder/Binder.h>
 #include <fcntl.h>
+#include <gmock/gmock.h>
 #include <gtest/gtest.h>
 #include <input/Input.h>
 #include <linux/input.h>
@@ -43,6 +44,7 @@
 namespace android::inputdispatcher {
 
 using namespace ftl::flag_operators;
+using testing::AllOf;
 
 // An arbitrary time value.
 static constexpr nsecs_t ARBITRARY_TIME = 1234;
@@ -102,6 +104,28 @@
             << MotionEvent::actionToString(receivedAction);
 }
 
+MATCHER_P(WithMotionAction, action, "MotionEvent with specified action") {
+    bool matches = action == arg.getAction();
+    if (!matches) {
+        *result_listener << "expected action " << MotionEvent::actionToString(action)
+                         << ", but got " << MotionEvent::actionToString(arg.getAction());
+    }
+    if (action == AMOTION_EVENT_ACTION_CANCEL) {
+        if (!matches) {
+            *result_listener << "; ";
+        }
+        *result_listener << "expected FLAG_CANCELED to be set with ACTION_CANCEL, but was not set";
+        matches &= (arg.getFlags() & AMOTION_EVENT_FLAG_CANCELED) != 0;
+    }
+    return matches;
+}
+
+MATCHER_P(WithSource, source, "InputEvent with specified source") {
+    *result_listener << "expected source " << inputEventSourceToString(source) << ", but got "
+                     << inputEventSourceToString(arg.getSource());
+    return arg.getSource() == source;
+}
+
 // --- FakeInputDispatcherPolicy ---
 
 class FakeInputDispatcherPolicy : public InputDispatcherPolicyInterface {
@@ -1205,6 +1229,12 @@
         mInputReceiver->consumeCaptureEvent(hasCapture);
     }
 
+    void consumeMotionEvent(const ::testing::Matcher<MotionEvent>& matcher) {
+        MotionEvent* motionEvent = consumeMotion();
+        ASSERT_NE(nullptr, motionEvent) << "Did not get a motion event";
+        ASSERT_THAT(*motionEvent, matcher);
+    }
+
     void consumeEvent(int32_t expectedEventType, int32_t expectedAction,
                       std::optional<int32_t> expectedDisplayId,
                       std::optional<int32_t> expectedFlags) {
@@ -2207,6 +2237,48 @@
                          ADISPLAY_ID_DEFAULT, 0 /* expectedFlag */);
 }
 
+/**
+ * Inject a mouse hover event followed by a tap from touchscreen.
+ * In the current implementation, the tap does not cause a HOVER_EXIT event.
+ */
+TEST_F(InputDispatcherTest, MouseHoverAndTouchTap) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> window =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+    window->setFrame(Rect(0, 0, 100, 100));
+
+    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+
+    // Inject a hover_move from mouse.
+    NotifyMotionArgs motionArgs =
+            generateMotionArgs(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE,
+                               ADISPLAY_ID_DEFAULT, {{50, 50}});
+    motionArgs.xCursorPosition = 50;
+    motionArgs.yCursorPosition = 50;
+    mDispatcher->notifyMotion(&motionArgs);
+    ASSERT_NO_FATAL_FAILURE(
+            window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
+                                             WithSource(AINPUT_SOURCE_MOUSE))));
+    ASSERT_NO_FATAL_FAILURE(
+            window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                                             WithSource(AINPUT_SOURCE_MOUSE))));
+
+    // Tap on the window
+    motionArgs = generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
+                                    ADISPLAY_ID_DEFAULT, {{10, 10}});
+    mDispatcher->notifyMotion(&motionArgs);
+    ASSERT_NO_FATAL_FAILURE(
+            window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                                             WithSource(AINPUT_SOURCE_TOUCHSCREEN))));
+
+    motionArgs = generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN,
+                                    ADISPLAY_ID_DEFAULT, {{10, 10}});
+    mDispatcher->notifyMotion(&motionArgs);
+    ASSERT_NO_FATAL_FAILURE(
+            window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                                             WithSource(AINPUT_SOURCE_TOUCHSCREEN))));
+}
+
 TEST_F(InputDispatcherTest, DispatchMouseEventsUnderCursor) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
 
diff --git a/services/inputflinger/tests/TestInputListenerMatchers.h b/services/inputflinger/tests/TestInputListenerMatchers.h
index 8721bd8..9db3422 100644
--- a/services/inputflinger/tests/TestInputListenerMatchers.h
+++ b/services/inputflinger/tests/TestInputListenerMatchers.h
@@ -46,7 +46,8 @@
 }
 
 MATCHER_P(WithSource, source, "InputEvent with specified source") {
-    *result_listener << "expected source " << source << ", but got " << arg.source;
+    *result_listener << "expected source " << inputEventSourceToString(source) << ", but got "
+                     << inputEventSourceToString(arg.source);
     return arg.source == source;
 }