diff options
| author | 2024-01-24 00:35:05 +0000 | |
|---|---|---|
| committer | 2024-01-24 00:35:05 +0000 | |
| commit | 9e14fec232dcea20ed67081653b694dbe5cf525e (patch) | |
| tree | d17c0f3d1fa11fe73e47b751c7f788cad6013fff | |
| parent | 4da99064118fff1441511b93a2e16207983185bc (diff) | |
| parent | b8aadfa28ed77ac586be838171709626be01b175 (diff) | |
Merge "Rate limit user activity pokes in InputDispatcher" into main
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 <input/PrintTools.h> #include <input/TraceTools.h> #include <openssl/mem.h> -#include <powermanager/PowerManager.h> #include <unistd.h> #include <utils/Trace.h> @@ -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<void> 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<const MotionEntry&>(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<WindowInfoHandle> 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<const MotionEntry&>(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 <input/Input.h> #include <input/InputTransport.h> #include <limits.h> +#include <powermanager/PowerManager.h> #include <stddef.h> #include <unistd.h> #include <utils/BitSet.h> @@ -116,6 +117,7 @@ public: int32_t displayId, const std::shared_ptr<InputApplicationHandle>& 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<nsecs_t, USER_ACTIVITY_EVENT_LAST + 1> 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<IBinder> 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<UserActivityPokeEvent> 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<UserActivityPokeEvent> expectedPokeEvent = {}) { + std::unique_lock lock(mLock); + base::ScopedLockAssertion assumeLocked(mLock); + + std::optional<UserActivityPokeEvent> 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<gui::Uid> uids) { @@ -414,7 +450,9 @@ private: sp<IBinder> mDropTargetWindowToken GUARDED_BY(mLock); bool mNotifyDropWindowWasCalled GUARDED_BY(mLock) = false; - bool mPokedUserActivity GUARDED_BY(mLock) = false; + + std::condition_variable mNotifyUserActivity; + std::queue<UserActivityPokeEvent> 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<FakeApplicationHandle> application = + std::make_shared<FakeApplicationHandle>(); + application->setDispatchingTimeout(100ms); + mWindow = sp<FakeWindowHandle>::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<FakeWindowHandle> 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(); |