Merge "Rate limit user activity pokes in InputDispatcher" into main
diff --git a/libs/input/input_flags.aconfig b/libs/input/input_flags.aconfig
index 576d9d5..0c850fe 100644
--- a/libs/input/input_flags.aconfig
+++ b/libs/input/input_flags.aconfig
@@ -97,3 +97,10 @@
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 1085c94..c349a58 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 @@
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 @@
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 @@
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 @@
if (traceBackend) {
// TODO: Create input tracer instance.
}
+
+ mLastUserActivityTimes.fill(0);
}
InputDispatcher::~InputDispatcher() {
@@ -3140,6 +3164,21 @@
// 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 @@
}
}
- 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 @@
}
return;
}
- if (MotionEvent::isTouchEvent(motionEntry.source, motionEntry.action)) {
- eventType = USER_ACTIVITY_EVENT_TOUCH;
- }
break;
}
case EventEntry::Type::KEY: {
@@ -3190,7 +3225,6 @@
return;
}
- eventType = USER_ACTIVITY_EVENT_BUTTON;
break;
}
default: {
@@ -3200,6 +3234,7 @@
}
}
+ mLastUserActivityTimes[eventType] = eventEntry.eventTime;
auto command = [this, eventTime = eventEntry.eventTime, eventType, displayId]()
REQUIRES(mLock) {
scoped_unlock unlock(mLock);
@@ -5292,6 +5327,14 @@
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 1e11b27..e635852 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 @@
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 @@
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 001dc6c..c8f3d05 100644
--- a/services/inputflinger/dispatcher/include/InputDispatcherInterface.h
+++ b/services/inputflinger/dispatcher/include/InputDispatcherInterface.h
@@ -101,6 +101,9 @@
*/
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 b6ae0b3..d3bbfac 100644
--- a/services/inputflinger/tests/InputDispatcher_test.cpp
+++ b/services/inputflinger/tests/InputDispatcher_test.cpp
@@ -156,6 +156,20 @@
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 @@
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 @@
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 @@
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 @@
/*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();