Wait until dispatcher is idle

Add a mechanism for waiting until the dispatcher is idle. This will
allow tests to not have to wait until a specific timeout is reached when
asserting that a callback did not happen.

For example, the current test approach is like this: 1) do something 2)
monitor policy for a certain amount of time 3) check that the thing you
are interested in did not happen.

With this approach, we will modify this behaviour to be as follows: 1)
do something 2) wait until dispatcher is idle 3) check that the thing
didn't happen.

In this case, the dispatcher is idle condition will happen sooner than
the timeout in the original approach. This will help keep the tests
fast.

Bug: 70668286
Test: atest inputflinger_tests
Change-Id: I2ac5e38ccbc388fe6d94832405b68ef9e52d25f4
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index 9c0e08e..b9bec44 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -298,6 +298,12 @@
         if (runCommandsLockedInterruptible()) {
             nextWakeupTime = LONG_LONG_MIN;
         }
+
+        // We are about to enter an infinitely long sleep, because we have no commands or
+        // pending or queued events
+        if (nextWakeupTime == LONG_LONG_MAX) {
+            mDispatcherEnteredIdle.notify_all();
+        }
     } // release lock
 
     // Wait for callback or timeout or wake.  (make sure we round up, not down)
@@ -4582,4 +4588,22 @@
     mDispatcherIsAlive.wait(_l);
 }
 
+/**
+ * Wake up the dispatcher and wait until it processes all events and commands.
+ * The notification of mDispatcherEnteredIdle is guaranteed to happen after wake(), so
+ * this method can be safely called from any thread, as long as you've ensured that
+ * the work you are interested in completing has already been queued.
+ */
+bool InputDispatcher::waitForIdle() {
+    /**
+     * Timeout should represent the longest possible time that a device might spend processing
+     * events and commands.
+     */
+    constexpr std::chrono::duration TIMEOUT = 100ms;
+    std::unique_lock lock(mLock);
+    mLooper->wake();
+    std::cv_status result = mDispatcherEnteredIdle.wait_for(lock, TIMEOUT);
+    return result == std::cv_status::no_timeout;
+}
+
 } // namespace android::inputdispatcher
diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h
index 5d7d812..38f8674 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.h
+++ b/services/inputflinger/dispatcher/InputDispatcher.h
@@ -81,6 +81,7 @@
 
     virtual void dump(std::string& dump) override;
     virtual void monitor() override;
+    virtual bool waitForIdle() override;
 
     virtual void dispatchOnce() override;
 
@@ -129,6 +130,7 @@
     std::mutex mLock;
 
     std::condition_variable mDispatcherIsAlive;
+    std::condition_variable mDispatcherEnteredIdle;
 
     sp<Looper> mLooper;
 
diff --git a/services/inputflinger/dispatcher/include/InputDispatcherInterface.h b/services/inputflinger/dispatcher/include/InputDispatcherInterface.h
index 3082738..db9fba6 100644
--- a/services/inputflinger/dispatcher/include/InputDispatcherInterface.h
+++ b/services/inputflinger/dispatcher/include/InputDispatcherInterface.h
@@ -63,6 +63,14 @@
     /* Called by the heatbeat to ensures that the dispatcher has not deadlocked. */
     virtual void monitor() = 0;
 
+    /**
+     * Wait until dispatcher is idle. That means, there are no further events to be processed,
+     * and all of the policy callbacks have been completed.
+     * Return true if the dispatcher is idle.
+     * Return false if the timeout waiting for the dispatcher to become idle has expired.
+     */
+    virtual bool waitForIdle() = 0;
+
     /* Runs a single iteration of the dispatch loop.
      * Nominally processes one queued event, a timeout, or a response from an input consumer.
      *
diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp
index 4f28261..c8d39cf 100644
--- a/services/inputflinger/tests/InputDispatcher_test.cpp
+++ b/services/inputflinger/tests/InputDispatcher_test.cpp
@@ -51,7 +51,6 @@
 
 public:
     FakeInputDispatcherPolicy() {
-        mOnPointerDownToken.clear();
     }
 
     void assertFilterInputEventWasCalled(const NotifyKeyArgs& args) {
@@ -66,17 +65,41 @@
 
     void assertFilterInputEventWasNotCalled() { ASSERT_EQ(nullptr, mFilteredEvent); }
 
+    void assertNotifyConfigurationChangedWasCalled(nsecs_t when) {
+        ASSERT_TRUE(mConfigurationChangedTime)
+                << "Timed out waiting for configuration changed call";
+        ASSERT_EQ(*mConfigurationChangedTime, when);
+        mConfigurationChangedTime = std::nullopt;
+    }
+
+    void assertNotifySwitchWasCalled(const NotifySwitchArgs& args) {
+        ASSERT_TRUE(mLastNotifySwitch);
+        // We do not check sequenceNum because it is not exposed to the policy
+        EXPECT_EQ(args.eventTime, mLastNotifySwitch->eventTime);
+        EXPECT_EQ(args.policyFlags, mLastNotifySwitch->policyFlags);
+        EXPECT_EQ(args.switchValues, mLastNotifySwitch->switchValues);
+        EXPECT_EQ(args.switchMask, mLastNotifySwitch->switchMask);
+        mLastNotifySwitch = std::nullopt;
+    }
+
     void assertOnPointerDownEquals(const sp<IBinder>& touchedToken) {
-        ASSERT_EQ(mOnPointerDownToken, touchedToken)
-                << "Expected token from onPointerDownOutsideFocus was not matched";
-        reset();
+        ASSERT_EQ(touchedToken, mOnPointerDownToken);
+        mOnPointerDownToken.clear();
+    }
+
+    void assertOnPointerDownWasNotCalled() {
+        ASSERT_TRUE(mOnPointerDownToken == nullptr)
+                << "Expected onPointerDownOutsideFocus to not have been called";
     }
 
 private:
     std::unique_ptr<InputEvent> mFilteredEvent;
+    std::optional<nsecs_t> mConfigurationChangedTime;
     sp<IBinder> mOnPointerDownToken;
+    std::optional<NotifySwitchArgs> mLastNotifySwitch;
 
-    virtual void notifyConfigurationChanged(nsecs_t) {
+    virtual void notifyConfigurationChanged(nsecs_t when) override {
+        mConfigurationChangedTime = when;
     }
 
     virtual nsecs_t notifyANR(const sp<InputApplicationHandle>&,
@@ -128,7 +151,13 @@
         return false;
     }
 
-    virtual void notifySwitch(nsecs_t, uint32_t, uint32_t, uint32_t) {
+    virtual void notifySwitch(nsecs_t when, uint32_t switchValues, uint32_t switchMask,
+                              uint32_t policyFlags) override {
+        /** We simply reconstruct NotifySwitchArgs in policy because InputDispatcher is
+         * essentially a passthrough for notifySwitch.
+         */
+        mLastNotifySwitch =
+                NotifySwitchArgs(1 /*sequenceNum*/, when, policyFlags, switchValues, switchMask);
     }
 
     virtual void pokeUserActivity(nsecs_t, int32_t) {
@@ -163,8 +192,6 @@
 
         mFilteredEvent = nullptr;
     }
-
-    void reset() { mOnPointerDownToken.clear(); }
 };
 
 
@@ -351,9 +378,30 @@
             << "Should reject motion events with duplicate pointer ids.";
 }
 
+/* Test InputDispatcher for notifyConfigurationChanged and notifySwitch events */
+
+TEST_F(InputDispatcherTest, NotifyConfigurationChanged_CallsPolicy) {
+    constexpr nsecs_t eventTime = 20;
+    NotifyConfigurationChangedArgs args(10 /*sequenceNum*/, eventTime);
+    mDispatcher->notifyConfigurationChanged(&args);
+    ASSERT_TRUE(mDispatcher->waitForIdle());
+
+    mFakePolicy->assertNotifyConfigurationChangedWasCalled(eventTime);
+}
+
+TEST_F(InputDispatcherTest, NotifySwitch_CallsPolicy) {
+    NotifySwitchArgs args(10 /*sequenceNum*/, 20 /*eventTime*/, 0 /*policyFlags*/,
+                          1 /*switchValues*/, 2 /*switchMask*/);
+    mDispatcher->notifySwitch(&args);
+
+    // InputDispatcher adds POLICY_FLAG_TRUSTED because the event went through InputListener
+    args.policyFlags |= POLICY_FLAG_TRUSTED;
+    mFakePolicy->assertNotifySwitchWasCalled(args);
+}
+
 // --- InputDispatcherTest SetInputWindowTest ---
-static const int32_t INJECT_EVENT_TIMEOUT = 500;
-static const int32_t DISPATCHING_TIMEOUT = 100;
+static constexpr int32_t INJECT_EVENT_TIMEOUT = 500;
+static constexpr int32_t DISPATCHING_TIMEOUT = 100;
 
 class FakeApplicationHandle : public InputApplicationHandle {
 public:
@@ -536,9 +584,7 @@
         InputWindowHandle::releaseChannel();
     }
 protected:
-    virtual bool handled() {
-        return true;
-    }
+    virtual bool handled() override { return true; }
 
     bool mFocused;
     Rect mFrame;
@@ -761,6 +807,51 @@
     windowRight->assertNoEvents();
 }
 
+TEST_F(InputDispatcherTest, NotifyDeviceReset_CancelsKeyStream) {
+    sp<FakeApplicationHandle> application = new FakeApplicationHandle();
+    sp<FakeWindowHandle> window =
+            new FakeWindowHandle(application, mDispatcher, "Fake Window", ADISPLAY_ID_DEFAULT);
+    window->setFocus();
+
+    mDispatcher->setInputWindows({window}, ADISPLAY_ID_DEFAULT);
+
+    NotifyKeyArgs keyArgs = generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT);
+    mDispatcher->notifyKey(&keyArgs);
+
+    // Window should receive key down event.
+    window->consumeKeyDown(ADISPLAY_ID_DEFAULT);
+
+    // When device reset happens, that key stream should be terminated with FLAG_CANCELED
+    // on the app side.
+    NotifyDeviceResetArgs args(10 /*sequenceNum*/, 20 /*eventTime*/, DEVICE_ID);
+    mDispatcher->notifyDeviceReset(&args);
+    window->consumeEvent(AINPUT_EVENT_TYPE_KEY, AKEY_EVENT_ACTION_UP, ADISPLAY_ID_DEFAULT,
+                         AKEY_EVENT_FLAG_CANCELED);
+}
+
+TEST_F(InputDispatcherTest, NotifyDeviceReset_CancelsMotionStream) {
+    sp<FakeApplicationHandle> application = new FakeApplicationHandle();
+    sp<FakeWindowHandle> window =
+            new FakeWindowHandle(application, mDispatcher, "Fake Window", ADISPLAY_ID_DEFAULT);
+
+    mDispatcher->setInputWindows({window}, ADISPLAY_ID_DEFAULT);
+
+    NotifyMotionArgs motionArgs =
+            generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
+                               ADISPLAY_ID_DEFAULT);
+    mDispatcher->notifyMotion(&motionArgs);
+
+    // Window should receive motion down event.
+    window->consumeMotionDown(ADISPLAY_ID_DEFAULT);
+
+    // When device reset happens, that motion stream should be terminated with ACTION_CANCEL
+    // on the app side.
+    NotifyDeviceResetArgs args(10 /*sequenceNum*/, 20 /*eventTime*/, DEVICE_ID);
+    mDispatcher->notifyDeviceReset(&args);
+    window->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_CANCEL, ADISPLAY_ID_DEFAULT,
+                         0 /*expectedFlags*/);
+}
+
 /* Test InputDispatcher for MultiDisplay */
 class InputDispatcherFocusOnTwoDisplaysTest : public InputDispatcherTest {
 public:
@@ -924,7 +1015,7 @@
         motionArgs = generateMotionArgs(
                 AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN, displayId);
         mDispatcher->notifyMotion(&motionArgs);
-
+        ASSERT_TRUE(mDispatcher->waitForIdle());
         if (expectToBeFiltered) {
             mFakePolicy->assertFilterInputEventWasCalled(motionArgs);
         } else {
@@ -939,6 +1030,7 @@
         mDispatcher->notifyKey(&keyArgs);
         keyArgs = generateKeyArgs(AKEY_EVENT_ACTION_UP);
         mDispatcher->notifyKey(&keyArgs);
+        ASSERT_TRUE(mDispatcher->waitForIdle());
 
         if (expectToBeFiltered) {
             mFakePolicy->assertFilterInputEventWasCalled(keyArgs);
@@ -1029,9 +1121,8 @@
     ASSERT_EQ(INPUT_EVENT_INJECTION_SUCCEEDED, injectMotionDown(mDispatcher,
             AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, 20, 20))
             << "Inject motion event should return INPUT_EVENT_INJECTION_SUCCEEDED";
-    // Call monitor to wait for the command queue to get flushed.
-    mDispatcher->monitor();
 
+    ASSERT_TRUE(mDispatcher->waitForIdle());
     mFakePolicy->assertOnPointerDownEquals(mUnfocusedWindow->getToken());
 }
 
@@ -1042,10 +1133,9 @@
     ASSERT_EQ(INPUT_EVENT_INJECTION_SUCCEEDED, injectMotionDown(mDispatcher,
             AINPUT_SOURCE_TRACKBALL, ADISPLAY_ID_DEFAULT, 20, 20))
             << "Inject motion event should return INPUT_EVENT_INJECTION_SUCCEEDED";
-    // Call monitor to wait for the command queue to get flushed.
-    mDispatcher->monitor();
 
-    mFakePolicy->assertOnPointerDownEquals(nullptr);
+    ASSERT_TRUE(mDispatcher->waitForIdle());
+    mFakePolicy->assertOnPointerDownWasNotCalled();
 }
 
 // Have two windows, one with focus. Inject KeyEvent with action DOWN on the window that doesn't
@@ -1053,10 +1143,9 @@
 TEST_F(InputDispatcherOnPointerDownOutsideFocus, OnPointerDownOutsideFocus_NonMotionFailure) {
     ASSERT_EQ(INPUT_EVENT_INJECTION_SUCCEEDED, injectKeyDown(mDispatcher, ADISPLAY_ID_DEFAULT))
             << "Inject key event should return INPUT_EVENT_INJECTION_SUCCEEDED";
-    // Call monitor to wait for the command queue to get flushed.
-    mDispatcher->monitor();
 
-    mFakePolicy->assertOnPointerDownEquals(nullptr);
+    ASSERT_TRUE(mDispatcher->waitForIdle());
+    mFakePolicy->assertOnPointerDownWasNotCalled();
 }
 
 // Have two windows, one with focus. Inject MotionEvent with source TOUCHSCREEN and action
@@ -1068,10 +1157,9 @@
               injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
                                mFocusedWindowTouchPoint, mFocusedWindowTouchPoint))
             << "Inject motion event should return INPUT_EVENT_INJECTION_SUCCEEDED";
-    // Call monitor to wait for the command queue to get flushed.
-    mDispatcher->monitor();
 
-    mFakePolicy->assertOnPointerDownEquals(nullptr);
+    ASSERT_TRUE(mDispatcher->waitForIdle());
+    mFakePolicy->assertOnPointerDownWasNotCalled();
 }
 
 } // namespace android::inputdispatcher