From b8aadfa28ed77ac586be838171709626be01b175 Mon Sep 17 00:00:00 2001 From: Yeabkal Wubshit Date: Wed, 17 Jan 2024 17:03:42 -0800 Subject: Rate limit user activity pokes in InputDispatcher This rate limiting used to exist in PowerManager's JNI entry point. Moving it to InputDispatcher helps us avoid building and sending the request to the PowerManager JNI layer if the request is not going to be sent to the Java layer. We are also enabling configuration of this rate. Previously, a default rate of 100ms per use-activity type was used. We have added an XML config to allow overriding this value device-wide, for all user-activity types. This helps devices adjust the rate as per their use cases. Bug: 320499729 Test: atest InputDispatcherUserActivityPokeTests Change-Id: I66fce1082e857b4d74b69e2d87d1a5fe1d9eb57c --- libs/input/input_flags.aconfig | 7 + .../inputflinger/dispatcher/InputDispatcher.cpp | 55 ++++++- services/inputflinger/dispatcher/InputDispatcher.h | 7 + .../dispatcher/include/InputDispatcherInterface.h | 3 + .../inputflinger/tests/InputDispatcher_test.cpp | 181 ++++++++++++++++++++- 5 files changed, 238 insertions(+), 15 deletions(-) diff --git a/libs/input/input_flags.aconfig b/libs/input/input_flags.aconfig index 576d9d569d..0c850fe0ee 100644 --- a/libs/input/input_flags.aconfig +++ b/libs/input/input_flags.aconfig @@ -97,3 +97,10 @@ flag { description: "Change the acceleration curves for mouse pointer movements to match the touchpad ones" bug: "315313622" } + +flag { + name: "rate_limit_user_activity_poke_in_dispatcher" + namespace: "input" + description: "Move user-activity poke rate-limiting from PowerManagerService to InputDispatcher." + bug: "320499729" +} diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index 1085c942dd..c349a5857f 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -35,7 +35,6 @@ #include #include #include -#include #include #include @@ -100,6 +99,9 @@ const std::chrono::duration DEFAULT_INPUT_DISPATCHING_TIMEOUT = std::chrono::mil android::os::IInputConstants::UNMULTIPLIED_DEFAULT_DISPATCHING_TIMEOUT_MILLIS * HwTimeoutMultiplier()); +// The default minimum time gap between two user activity poke events. +const std::chrono::milliseconds DEFAULT_USER_ACTIVITY_POKE_INTERVAL = 100ms; + const std::chrono::duration STALE_EVENT_TIMEOUT = std::chrono::seconds(10) * HwTimeoutMultiplier(); // Log a warning when an event takes longer than this to process, even if an ANR does not occur. @@ -778,6 +780,25 @@ Result validateWindowInfosUpdate(const gui::WindowInfosUpdate& update) { return {}; } +int32_t getUserActivityEventType(const EventEntry& eventEntry) { + switch (eventEntry.type) { + case EventEntry::Type::KEY: { + return USER_ACTIVITY_EVENT_BUTTON; + } + case EventEntry::Type::MOTION: { + const MotionEntry& motionEntry = static_cast(eventEntry); + if (MotionEvent::isTouchEvent(motionEntry.source, motionEntry.action)) { + return USER_ACTIVITY_EVENT_TOUCH; + } + return USER_ACTIVITY_EVENT_OTHER; + } + default: { + LOG_ALWAYS_FATAL("%s events are not user activity", + ftl::enum_string(eventEntry.type).c_str()); + } + } +} + } // namespace // --- InputDispatcher --- @@ -791,6 +812,7 @@ InputDispatcher::InputDispatcher(InputDispatcherPolicyInterface& policy, mPendingEvent(nullptr), mLastDropReason(DropReason::NOT_DROPPED), mIdGenerator(IdGenerator::Source::INPUT_DISPATCHER), + mMinTimeBetweenUserActivityPokes(DEFAULT_USER_ACTIVITY_POKE_INTERVAL), mNextUnblockedEvent(nullptr), mMonitorDispatchingTimeout(DEFAULT_INPUT_DISPATCHING_TIMEOUT), mDispatchEnabled(false), @@ -813,6 +835,8 @@ InputDispatcher::InputDispatcher(InputDispatcherPolicyInterface& policy, if (traceBackend) { // TODO: Create input tracer instance. } + + mLastUserActivityTimes.fill(0); } InputDispatcher::~InputDispatcher() { @@ -3140,6 +3164,21 @@ void InputDispatcher::pokeUserActivityLocked(const EventEntry& eventEntry) { // Not poking user activity if the event type does not represent a user activity return; } + + const int32_t eventType = getUserActivityEventType(eventEntry); + if (input_flags::rate_limit_user_activity_poke_in_dispatcher()) { + // Note that we're directly getting the time diff between the current event and the previous + // event. This is assuming that the first user event always happens at a timestamp that is + // greater than `mMinTimeBetweenUserActivityPokes` (otherwise, the first user event will + // wrongly be dropped). In real life, `mMinTimeBetweenUserActivityPokes` is a much smaller + // value than the potential first user activity event time, so this is ok. + std::chrono::nanoseconds timeSinceLastEvent = + std::chrono::nanoseconds(eventEntry.eventTime - mLastUserActivityTimes[eventType]); + if (timeSinceLastEvent < mMinTimeBetweenUserActivityPokes) { + return; + } + } + int32_t displayId = getTargetDisplayId(eventEntry); sp focusedWindowHandle = getFocusedWindowHandleLocked(displayId); const WindowInfo* windowDisablingUserActivityInfo = nullptr; @@ -3150,7 +3189,6 @@ void InputDispatcher::pokeUserActivityLocked(const EventEntry& eventEntry) { } } - int32_t eventType = USER_ACTIVITY_EVENT_OTHER; switch (eventEntry.type) { case EventEntry::Type::MOTION: { const MotionEntry& motionEntry = static_cast(eventEntry); @@ -3164,9 +3202,6 @@ void InputDispatcher::pokeUserActivityLocked(const EventEntry& eventEntry) { } return; } - if (MotionEvent::isTouchEvent(motionEntry.source, motionEntry.action)) { - eventType = USER_ACTIVITY_EVENT_TOUCH; - } break; } case EventEntry::Type::KEY: { @@ -3190,7 +3225,6 @@ void InputDispatcher::pokeUserActivityLocked(const EventEntry& eventEntry) { return; } - eventType = USER_ACTIVITY_EVENT_BUTTON; break; } default: { @@ -3200,6 +3234,7 @@ void InputDispatcher::pokeUserActivityLocked(const EventEntry& eventEntry) { } } + mLastUserActivityTimes[eventType] = eventEntry.eventTime; auto command = [this, eventTime = eventEntry.eventTime, eventType, displayId]() REQUIRES(mLock) { scoped_unlock unlock(mLock); @@ -5292,6 +5327,14 @@ void InputDispatcher::setFocusedApplicationLocked( resetNoFocusedWindowTimeoutLocked(); } +void InputDispatcher::setMinTimeBetweenUserActivityPokes(std::chrono::milliseconds interval) { + if (interval.count() < 0) { + LOG_ALWAYS_FATAL("Minimum time between user activity pokes should be >= 0"); + } + std::scoped_lock _l(mLock); + mMinTimeBetweenUserActivityPokes = interval; +} + /** * Sets the focused display, which is responsible for receiving focus-dispatched input events where * the display not specified. diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h index 1e11b27d2f..e635852662 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.h +++ b/services/inputflinger/dispatcher/InputDispatcher.h @@ -41,6 +41,7 @@ #include #include #include +#include #include #include #include @@ -116,6 +117,7 @@ public: int32_t displayId, const std::shared_ptr& inputApplicationHandle) override; void setFocusedDisplay(int32_t displayId) override; + void setMinTimeBetweenUserActivityPokes(std::chrono::milliseconds interval) override; void setInputDispatchMode(bool enabled, bool frozen) override; void setInputFilterEnabled(bool enabled) override; bool setInTouchMode(bool inTouchMode, gui::Pid pid, gui::Uid uid, bool hasPermission, @@ -211,6 +213,11 @@ private: int64_t mWindowInfosVsyncId GUARDED_BY(mLock); + std::chrono::milliseconds mMinTimeBetweenUserActivityPokes GUARDED_BY(mLock); + + /** Stores the latest user-activity poke event times per user activity types. */ + std::array mLastUserActivityTimes GUARDED_BY(mLock); + // With each iteration, InputDispatcher nominally processes one queued event, // a timeout, or a response from an input consumer. // This method should only be called on the input dispatcher's own thread. diff --git a/services/inputflinger/dispatcher/include/InputDispatcherInterface.h b/services/inputflinger/dispatcher/include/InputDispatcherInterface.h index 001dc6cf7b..c8f3d05ade 100644 --- a/services/inputflinger/dispatcher/include/InputDispatcherInterface.h +++ b/services/inputflinger/dispatcher/include/InputDispatcherInterface.h @@ -101,6 +101,9 @@ public: */ virtual void setFocusedDisplay(int32_t displayId) = 0; + /** Sets the minimum time between user activity pokes. */ + virtual void setMinTimeBetweenUserActivityPokes(std::chrono::milliseconds interval) = 0; + /* Sets the input dispatching mode. * * This method may be called on any thread (usually by the input manager). diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp index b6ae0b3d69..d3bbfac50f 100644 --- a/services/inputflinger/tests/InputDispatcher_test.cpp +++ b/services/inputflinger/tests/InputDispatcher_test.cpp @@ -156,6 +156,20 @@ class FakeInputDispatcherPolicy : public InputDispatcherPolicyInterface { sp token{}; gui::Pid pid{gui::Pid::INVALID}; }; + /* Stores data about a user-activity-poke event from the dispatcher. */ + struct UserActivityPokeEvent { + nsecs_t eventTime; + int32_t eventType; + int32_t displayId; + + bool operator==(const UserActivityPokeEvent& rhs) const = default; + + friend std::ostream& operator<<(std::ostream& os, const UserActivityPokeEvent& ev) { + os << "UserActivityPokeEvent[time=" << ev.eventTime << ", eventType=" << ev.eventType + << ", displayId=" << ev.displayId << "]"; + return os; + } + }; public: FakeInputDispatcherPolicy() = default; @@ -351,14 +365,36 @@ public: void setStaleEventTimeout(std::chrono::nanoseconds timeout) { mStaleEventTimeout = timeout; } - void assertUserActivityPoked() { - std::scoped_lock lock(mLock); - ASSERT_TRUE(mPokedUserActivity) << "Expected user activity to have been poked"; + void assertUserActivityNotPoked() { + std::unique_lock lock(mLock); + base::ScopedLockAssertion assumeLocked(mLock); + + std::optional pokeEvent = + getItemFromStorageLockedInterruptible(500ms, mUserActivityPokeEvents, lock, + mNotifyUserActivity); + + ASSERT_FALSE(pokeEvent) << "Expected user activity not to have been poked"; } - void assertUserActivityNotPoked() { - std::scoped_lock lock(mLock); - ASSERT_FALSE(mPokedUserActivity) << "Expected user activity not to have been poked"; + /** + * Asserts that a user activity poke has happened. The earliest recorded poke event will be + * cleared after this call. + * + * If an expected UserActivityPokeEvent is provided, asserts that the given event is the + * earliest recorded poke event. + */ + void assertUserActivityPoked(std::optional expectedPokeEvent = {}) { + std::unique_lock lock(mLock); + base::ScopedLockAssertion assumeLocked(mLock); + + std::optional pokeEvent = + getItemFromStorageLockedInterruptible(500ms, mUserActivityPokeEvents, lock, + mNotifyUserActivity); + ASSERT_TRUE(pokeEvent) << "Expected a user poke event"; + + if (expectedPokeEvent) { + ASSERT_EQ(expectedPokeEvent, *pokeEvent); + } } void assertNotifyDeviceInteractionWasCalled(int32_t deviceId, std::set uids) { @@ -414,7 +450,9 @@ private: sp mDropTargetWindowToken GUARDED_BY(mLock); bool mNotifyDropWindowWasCalled GUARDED_BY(mLock) = false; - bool mPokedUserActivity GUARDED_BY(mLock) = false; + + std::condition_variable mNotifyUserActivity; + std::queue mUserActivityPokeEvents; std::chrono::milliseconds mInterceptKeyTimeout = 0ms; @@ -576,9 +614,10 @@ private: NotifySwitchArgs(InputEvent::nextId(), when, policyFlags, switchValues, switchMask); } - void pokeUserActivity(nsecs_t, int32_t, int32_t) override { + void pokeUserActivity(nsecs_t eventTime, int32_t eventType, int32_t displayId) override { std::scoped_lock lock(mLock); - mPokedUserActivity = true; + mNotifyUserActivity.notify_all(); + mUserActivityPokeEvents.push({eventTime, eventType, displayId}); } bool isStaleEvent(nsecs_t currentTime, nsecs_t eventTime) override { @@ -7690,6 +7729,130 @@ TEST_F(InputFilterInjectionPolicyTest, RegularInjectedEvents_ReceiveVirtualDevic /*resolvedDeviceId=*/VIRTUAL_KEYBOARD_ID, /*flags=*/0); } +class InputDispatcherUserActivityPokeTests : public InputDispatcherTest { +protected: + virtual void SetUp() override { + InputDispatcherTest::SetUp(); + + std::shared_ptr application = + std::make_shared(); + application->setDispatchingTimeout(100ms); + mWindow = sp::make(application, mDispatcher, "TestWindow", + ADISPLAY_ID_DEFAULT); + mWindow->setFrame(Rect(0, 0, 30, 30)); + mWindow->setDispatchingTimeout(100ms); + mWindow->setFocusable(true); + + // Set focused application. + mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application); + + mDispatcher->onWindowInfosChanged({{*mWindow->getInfo()}, {}, 0, 0}); + setFocusedWindow(mWindow); + mWindow->consumeFocusEvent(true); + } + + void notifyAndConsumeMotion(int32_t action, uint32_t source, int32_t displayId, + nsecs_t eventTime) { + mDispatcher->notifyMotion(MotionArgsBuilder(action, source) + .displayId(displayId) + .eventTime(eventTime) + .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50)) + .build()); + mWindow->consumeMotionEvent(WithMotionAction(action)); + } + +private: + sp mWindow; +}; + +TEST_F_WITH_FLAGS( + InputDispatcherUserActivityPokeTests, MinPokeTimeObserved, + REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(com::android::input::flags, + rate_limit_user_activity_poke_in_dispatcher))) { + mDispatcher->setMinTimeBetweenUserActivityPokes(50ms); + + // First event of type TOUCH. Should poke. + notifyAndConsumeMotion(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, + milliseconds_to_nanoseconds(50)); + mFakePolicy->assertUserActivityPoked( + {{milliseconds_to_nanoseconds(50), USER_ACTIVITY_EVENT_TOUCH, ADISPLAY_ID_DEFAULT}}); + + // 80ns > 50ns has passed since previous TOUCH event. Should poke. + notifyAndConsumeMotion(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, + milliseconds_to_nanoseconds(130)); + mFakePolicy->assertUserActivityPoked( + {{milliseconds_to_nanoseconds(130), USER_ACTIVITY_EVENT_TOUCH, ADISPLAY_ID_DEFAULT}}); + + // First event of type OTHER. Should poke (despite being within 50ns of previous TOUCH event). + notifyAndConsumeMotion(ACTION_SCROLL, AINPUT_SOURCE_ROTARY_ENCODER, ADISPLAY_ID_DEFAULT, + milliseconds_to_nanoseconds(135)); + mFakePolicy->assertUserActivityPoked( + {{milliseconds_to_nanoseconds(135), USER_ACTIVITY_EVENT_OTHER, ADISPLAY_ID_DEFAULT}}); + + // Within 50ns of previous TOUCH event. Should NOT poke. + notifyAndConsumeMotion(ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, + milliseconds_to_nanoseconds(140)); + mFakePolicy->assertUserActivityNotPoked(); + + // Within 50ns of previous OTHER event. Should NOT poke. + notifyAndConsumeMotion(ACTION_SCROLL, AINPUT_SOURCE_ROTARY_ENCODER, ADISPLAY_ID_DEFAULT, + milliseconds_to_nanoseconds(150)); + mFakePolicy->assertUserActivityNotPoked(); + + // Within 50ns of previous TOUCH event (which was at time 130). Should NOT poke. + // Note that STYLUS is mapped to TOUCH user activity, since it's a pointer-type source. + notifyAndConsumeMotion(ACTION_DOWN, AINPUT_SOURCE_STYLUS, ADISPLAY_ID_DEFAULT, + milliseconds_to_nanoseconds(160)); + mFakePolicy->assertUserActivityNotPoked(); + + // 65ns > 50ns has passed since previous OTHER event. Should poke. + notifyAndConsumeMotion(ACTION_SCROLL, AINPUT_SOURCE_ROTARY_ENCODER, ADISPLAY_ID_DEFAULT, + milliseconds_to_nanoseconds(200)); + mFakePolicy->assertUserActivityPoked( + {{milliseconds_to_nanoseconds(200), USER_ACTIVITY_EVENT_OTHER, ADISPLAY_ID_DEFAULT}}); + + // 170ns > 50ns has passed since previous TOUCH event. Should poke. + notifyAndConsumeMotion(ACTION_UP, AINPUT_SOURCE_STYLUS, ADISPLAY_ID_DEFAULT, + milliseconds_to_nanoseconds(300)); + mFakePolicy->assertUserActivityPoked( + {{milliseconds_to_nanoseconds(300), USER_ACTIVITY_EVENT_TOUCH, ADISPLAY_ID_DEFAULT}}); + + // Assert that there's no more user activity poke event. + mFakePolicy->assertUserActivityNotPoked(); +} + +TEST_F_WITH_FLAGS( + InputDispatcherUserActivityPokeTests, DefaultMinPokeTimeOf100MsUsed, + REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(com::android::input::flags, + rate_limit_user_activity_poke_in_dispatcher))) { + notifyAndConsumeMotion(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, + milliseconds_to_nanoseconds(200)); + mFakePolicy->assertUserActivityPoked( + {{milliseconds_to_nanoseconds(200), USER_ACTIVITY_EVENT_TOUCH, ADISPLAY_ID_DEFAULT}}); + + notifyAndConsumeMotion(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, + milliseconds_to_nanoseconds(280)); + mFakePolicy->assertUserActivityNotPoked(); + + notifyAndConsumeMotion(ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, + milliseconds_to_nanoseconds(340)); + mFakePolicy->assertUserActivityPoked( + {{milliseconds_to_nanoseconds(340), USER_ACTIVITY_EVENT_TOUCH, ADISPLAY_ID_DEFAULT}}); +} + +TEST_F_WITH_FLAGS( + InputDispatcherUserActivityPokeTests, ZeroMinPokeTimeDisablesRateLimiting, + REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(com::android::input::flags, + rate_limit_user_activity_poke_in_dispatcher))) { + mDispatcher->setMinTimeBetweenUserActivityPokes(0ms); + + notifyAndConsumeMotion(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, 20); + mFakePolicy->assertUserActivityPoked(); + + notifyAndConsumeMotion(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, 30); + mFakePolicy->assertUserActivityPoked(); +} + class InputDispatcherOnPointerDownOutsideFocus : public InputDispatcherTest { virtual void SetUp() override { InputDispatcherTest::SetUp(); -- cgit v1.2.3-59-g8ed1b