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

Commit dd779c4c authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Wait until dispatcher is idle"

parents a71b59cb 2bfa9054
Loading
Loading
Loading
Loading
+24 −0
Original line number Diff line number Diff line
@@ -298,6 +298,12 @@ void InputDispatcher::dispatchOnce() {
        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 @@ void InputDispatcher::monitor() {
    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
+2 −0
Original line number Diff line number Diff line
@@ -81,6 +81,7 @@ public:

    virtual void dump(std::string& dump) override;
    virtual void monitor() override;
    virtual bool waitForIdle() override;

    virtual void dispatchOnce() override;

@@ -129,6 +130,7 @@ private:
    std::mutex mLock;

    std::condition_variable mDispatcherIsAlive;
    std::condition_variable mDispatcherEnteredIdle;

    sp<Looper> mLooper;

+8 −0
Original line number Diff line number Diff line
@@ -63,6 +63,14 @@ public:
    /* 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.
     *
+113 −25
Original line number Diff line number Diff line
@@ -51,7 +51,6 @@ protected:

public:
    FakeInputDispatcherPolicy() {
        mOnPointerDownToken.clear();
    }

    void assertFilterInputEventWasCalled(const NotifyKeyArgs& args) {
@@ -66,17 +65,41 @@ public:

    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 @@ private:
        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 @@ private:

        mFilteredEvent = nullptr;
    }

    void reset() { mOnPointerDownToken.clear(); }
};


@@ -351,9 +378,30 @@ TEST_F(InputDispatcherTest, InjectInputEvent_ValidatesMotionEvents) {
            << "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 @@ public:
        InputWindowHandle::releaseChannel();
    }
protected:
    virtual bool handled() {
        return true;
    }
    virtual bool handled() override { return true; }

    bool mFocused;
    Rect mFrame;
@@ -761,6 +807,51 @@ TEST_F(InputDispatcherTest, DispatchMouseEventsUnderCursor) {
    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 @@ protected:
        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 @@ protected:
        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 @@ TEST_F(InputDispatcherOnPointerDownOutsideFocus, OnPointerDownOutsideFocus_Succe
    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 @@ TEST_F(InputDispatcherOnPointerDownOutsideFocus, OnPointerDownOutsideFocus_NonPo
    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_NonPo
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 @@ TEST_F(InputDispatcherOnPointerDownOutsideFocus,
              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