diff options
| author | 2022-11-11 23:00:53 +0000 | |
|---|---|---|
| committer | 2022-11-11 23:00:53 +0000 | |
| commit | 324b35fc3a46abe9936069712d6b149d25dbf504 (patch) | |
| tree | a70a438c644b0e28c5a62ee7b78abe31b56dd364 | |
| parent | ad75ba36f39bcffa563c8699fa84b1dc157f85f2 (diff) | |
| parent | 2f37bcbe4f4c32a3457ebb9821fa8d8f8763421b (diff) | |
Merge "Add timestamp smoothening for Bluetooth mice and touchpads"
7 files changed, 211 insertions, 12 deletions
diff --git a/services/inputflinger/reader/mapper/CursorInputMapper.cpp b/services/inputflinger/reader/mapper/CursorInputMapper.cpp index c691ca943f..a4f257c4b6 100644 --- a/services/inputflinger/reader/mapper/CursorInputMapper.cpp +++ b/services/inputflinger/reader/mapper/CursorInputMapper.cpp @@ -67,7 +67,7 @@ void CursorMotionAccumulator::finishSync() { // --- CursorInputMapper --- CursorInputMapper::CursorInputMapper(InputDeviceContext& deviceContext) - : InputMapper(deviceContext) {} + : InputMapper(deviceContext), mLastEventTime(std::numeric_limits<nsecs_t>::min()) {} CursorInputMapper::~CursorInputMapper() { if (mPointerController != nullptr) { @@ -276,6 +276,7 @@ void CursorInputMapper::dumpParameters(std::string& dump) { std::list<NotifyArgs> CursorInputMapper::reset(nsecs_t when) { mButtonState = 0; mDownTime = 0; + mLastEventTime = std::numeric_limits<nsecs_t>::min(); mPointerVelocityControl.reset(); mWheelXVelocityControl.reset(); @@ -295,7 +296,11 @@ std::list<NotifyArgs> CursorInputMapper::process(const RawEvent* rawEvent) { mCursorScrollAccumulator.process(rawEvent); if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) { - out += sync(rawEvent->when, rawEvent->readTime); + const nsecs_t eventTime = + applyBluetoothTimestampSmoothening(getDeviceContext().getDeviceIdentifier(), + rawEvent->when, mLastEventTime); + out += sync(eventTime, rawEvent->readTime); + mLastEventTime = eventTime; } return out; } diff --git a/services/inputflinger/reader/mapper/CursorInputMapper.h b/services/inputflinger/reader/mapper/CursorInputMapper.h index 6a4275ed54..20746e5bb0 100644 --- a/services/inputflinger/reader/mapper/CursorInputMapper.h +++ b/services/inputflinger/reader/mapper/CursorInputMapper.h @@ -121,6 +121,7 @@ private: int32_t mButtonState; nsecs_t mDownTime; + nsecs_t mLastEventTime; void configureParameters(); void dumpParameters(std::string& dump); diff --git a/services/inputflinger/reader/mapper/TouchCursorInputMapperCommon.h b/services/inputflinger/reader/mapper/TouchCursorInputMapperCommon.h index 5a7ba9a6ed..0b7ff84c99 100644 --- a/services/inputflinger/reader/mapper/TouchCursorInputMapperCommon.h +++ b/services/inputflinger/reader/mapper/TouchCursorInputMapperCommon.h @@ -101,4 +101,30 @@ static bool isPointerDown(int32_t buttonState) { return out; } +// For devices connected over Bluetooth, although they may produce events at a consistent rate, +// the events might end up reaching Android in a "batched" manner through the Bluetooth +// stack, where a few events may be clumped together and processed around the same time. +// In this case, if the input device or its driver does not send or process the actual event +// generation timestamps, the event time will set to whenever the kernel received the event. +// When the timestamp deltas are minuscule for these batched events, any changes in x or y +// coordinates result in extremely large instantaneous velocities, which can negatively impact +// user experience. To avoid this, we augment the timestamps so that subsequent event timestamps +// differ by at least a minimum delta value. +static nsecs_t applyBluetoothTimestampSmoothening(const InputDeviceIdentifier& identifier, + nsecs_t currentEventTime, nsecs_t lastEventTime) { + if (identifier.bus != BUS_BLUETOOTH) { + return currentEventTime; + } + + // Assume the fastest rate at which a Bluetooth touch device can report input events is one + // every 4 milliseconds, or 250 Hz. Timestamps for successive events from a Bluetooth device + // will be separated by at least this amount. + constexpr static nsecs_t MIN_BLUETOOTH_TIMESTAMP_DELTA = ms2ns(4); + // We define a maximum smoothing time delta so that we don't generate events too far into the + // future. + constexpr static nsecs_t MAX_BLUETOOTH_SMOOTHING_DELTA = ms2ns(32); + return std::min(std::max(currentEventTime, lastEventTime + MIN_BLUETOOTH_TIMESTAMP_DELTA), + currentEventTime + MAX_BLUETOOTH_SMOOTHING_DELTA); +} + } // namespace android diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp index 3947cf7b9f..bf73ce5db8 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp +++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp @@ -1469,6 +1469,9 @@ std::list<NotifyArgs> TouchInputMapper::sync(nsecs_t when, nsecs_t readTime) { const RawState& last = mRawStatesPending.size() == 1 ? mCurrentRawState : mRawStatesPending.rbegin()[1]; + next.when = applyBluetoothTimestampSmoothening(getDeviceContext().getDeviceIdentifier(), when, + last.when); + // Assign pointer ids. if (!mHavePointerIds) { assignPointerIds(last, next); diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.h b/services/inputflinger/reader/mapper/TouchInputMapper.h index d5e4d5ae28..c20f28bc19 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.h +++ b/services/inputflinger/reader/mapper/TouchInputMapper.h @@ -318,7 +318,7 @@ protected: RawPointerAxes mRawPointerAxes; struct RawState { - nsecs_t when{}; + nsecs_t when{std::numeric_limits<nsecs_t>::min()}; nsecs_t readTime{}; // Raw pointer sample data. diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp index e4e22df5de..879d36e6fd 100644 --- a/services/inputflinger/tests/InputReader_test.cpp +++ b/services/inputflinger/tests/InputReader_test.cpp @@ -99,6 +99,11 @@ static constexpr int32_t ACTION_POINTER_1_UP = // Error tolerance for floating point assertions. static const float EPSILON = 0.001f; +// Minimum timestamp separation between subsequent input events from a Bluetooth device. +static constexpr nsecs_t MIN_BLUETOOTH_TIMESTAMP_DELTA = ms2ns(4); +// Maximum smoothing time delta so that we don't generate events too far into the future. +constexpr static nsecs_t MAX_BLUETOOTH_SMOOTHING_DELTA = ms2ns(32); + template<typename T> static inline T min(T a, T b) { return a < b ? a : b; @@ -530,10 +535,11 @@ public: FakeEventHub() { } - void addDevice(int32_t deviceId, const std::string& name, - ftl::Flags<InputDeviceClass> classes) { + void addDevice(int32_t deviceId, const std::string& name, ftl::Flags<InputDeviceClass> classes, + int bus = 0) { Device* device = new Device(classes); device->identifier.name = name; + device->identifier.bus = bus; mDevices.add(deviceId, device); enqueueEvent(ARBITRARY_TIME, READ_TIME, deviceId, EventHubInterface::DEVICE_ADDED, 0, 0); @@ -3255,13 +3261,13 @@ protected: std::unique_ptr<InstrumentedInputReader> mReader; std::shared_ptr<InputDevice> mDevice; - virtual void SetUp(ftl::Flags<InputDeviceClass> classes) { + virtual void SetUp(ftl::Flags<InputDeviceClass> classes, int bus = 0) { mFakeEventHub = std::make_unique<FakeEventHub>(); mFakePolicy = sp<FakeInputReaderPolicy>::make(); mFakeListener = std::make_unique<TestInputListener>(); mReader = std::make_unique<InstrumentedInputReader>(mFakeEventHub, mFakePolicy, *mFakeListener); - mDevice = newDevice(DEVICE_ID, DEVICE_NAME, DEVICE_LOCATION, EVENTHUB_ID, classes); + mDevice = newDevice(DEVICE_ID, DEVICE_NAME, DEVICE_LOCATION, EVENTHUB_ID, classes, bus); // Consume the device reset notification generated when adding a new device. mFakeListener->assertNotifyDeviceResetWasCalled(); } @@ -3299,15 +3305,16 @@ protected: std::shared_ptr<InputDevice> newDevice(int32_t deviceId, const std::string& name, const std::string& location, int32_t eventHubId, - ftl::Flags<InputDeviceClass> classes) { + ftl::Flags<InputDeviceClass> classes, int bus = 0) { InputDeviceIdentifier identifier; identifier.name = name; identifier.location = location; + identifier.bus = bus; std::shared_ptr<InputDevice> device = std::make_shared<InputDevice>(mReader->getContext(), deviceId, DEVICE_GENERATION, identifier); mReader->pushNextDevice(device); - mFakeEventHub->addDevice(eventHubId, name, classes); + mFakeEventHub->addDevice(eventHubId, name, classes, bus); mReader->loopOnce(); return device; } @@ -5486,6 +5493,106 @@ TEST_F(CursorInputMapperTest, ConfigureDisplayId_IgnoresEventsForMismatchedPoint ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); } +// --- BluetoothCursorInputMapperTest --- + +class BluetoothCursorInputMapperTest : public CursorInputMapperTest { +protected: + void SetUp() override { + InputMapperTest::SetUp(DEVICE_CLASSES | InputDeviceClass::EXTERNAL, BUS_BLUETOOTH); + + mFakePointerController = std::make_shared<FakePointerController>(); + mFakePolicy->setPointerController(mFakePointerController); + } +}; + +TEST_F(BluetoothCursorInputMapperTest, TimestampSmoothening) { + addConfigurationProperty("cursor.mode", "pointer"); + CursorInputMapper& mapper = addMapperAndConfigure<CursorInputMapper>(); + + nsecs_t kernelEventTime = ARBITRARY_TIME; + nsecs_t expectedEventTime = ARBITRARY_TIME; + process(mapper, kernelEventTime, READ_TIME, EV_REL, REL_X, 1); + process(mapper, kernelEventTime, READ_TIME, EV_SYN, SYN_REPORT, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), + WithEventTime(expectedEventTime)))); + + // Process several events that come in quick succession, according to their timestamps. + for (int i = 0; i < 3; i++) { + constexpr static nsecs_t delta = ms2ns(1); + static_assert(delta < MIN_BLUETOOTH_TIMESTAMP_DELTA); + kernelEventTime += delta; + expectedEventTime += MIN_BLUETOOTH_TIMESTAMP_DELTA; + + process(mapper, kernelEventTime, READ_TIME, EV_REL, REL_X, 1); + process(mapper, kernelEventTime, READ_TIME, EV_SYN, SYN_REPORT, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), + WithEventTime(expectedEventTime)))); + } +} + +TEST_F(BluetoothCursorInputMapperTest, TimestampSmootheningIsCapped) { + addConfigurationProperty("cursor.mode", "pointer"); + CursorInputMapper& mapper = addMapperAndConfigure<CursorInputMapper>(); + + nsecs_t expectedEventTime = ARBITRARY_TIME; + process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 1); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), + WithEventTime(expectedEventTime)))); + + // Process several events with the same timestamp from the kernel. + // Ensure that we do not generate events too far into the future. + constexpr static int32_t numEvents = + MAX_BLUETOOTH_SMOOTHING_DELTA / MIN_BLUETOOTH_TIMESTAMP_DELTA; + for (int i = 0; i < numEvents; i++) { + expectedEventTime += MIN_BLUETOOTH_TIMESTAMP_DELTA; + + process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 1); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), + WithEventTime(expectedEventTime)))); + } + + // By processing more events with the same timestamp, we should not generate events with a + // timestamp that is more than the specified max time delta from the timestamp at its injection. + const nsecs_t cappedEventTime = ARBITRARY_TIME + MAX_BLUETOOTH_SMOOTHING_DELTA; + for (int i = 0; i < 3; i++) { + process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 1); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), + WithEventTime(cappedEventTime)))); + } +} + +TEST_F(BluetoothCursorInputMapperTest, TimestampSmootheningNotUsed) { + addConfigurationProperty("cursor.mode", "pointer"); + CursorInputMapper& mapper = addMapperAndConfigure<CursorInputMapper>(); + + nsecs_t kernelEventTime = ARBITRARY_TIME; + nsecs_t expectedEventTime = ARBITRARY_TIME; + process(mapper, kernelEventTime, READ_TIME, EV_REL, REL_X, 1); + process(mapper, kernelEventTime, READ_TIME, EV_SYN, SYN_REPORT, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), + WithEventTime(expectedEventTime)))); + + // If the next event has a timestamp that is sufficiently spaced out so that Bluetooth timestamp + // smoothening is not needed, its timestamp is not affected. + kernelEventTime += MAX_BLUETOOTH_SMOOTHING_DELTA + ms2ns(1); + expectedEventTime = kernelEventTime; + + process(mapper, kernelEventTime, READ_TIME, EV_REL, REL_X, 1); + process(mapper, kernelEventTime, READ_TIME, EV_SYN, SYN_REPORT, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), + WithEventTime(expectedEventTime)))); +} + // --- TouchInputMapperTest --- class TouchInputMapperTest : public InputMapperTest { @@ -7479,7 +7586,8 @@ protected: void processKey(MultiTouchInputMapper& mapper, int32_t code, int32_t value); void processHidUsage(MultiTouchInputMapper& mapper, int32_t usageCode, int32_t value); void processMTSync(MultiTouchInputMapper& mapper); - void processSync(MultiTouchInputMapper& mapper); + void processSync(MultiTouchInputMapper& mapper, nsecs_t eventTime = ARBITRARY_TIME, + nsecs_t readTime = READ_TIME); }; void MultiTouchInputMapperTest::prepareAxes(int axes) { @@ -7592,8 +7700,9 @@ void MultiTouchInputMapperTest::processMTSync(MultiTouchInputMapper& mapper) { process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_MT_REPORT, 0); } -void MultiTouchInputMapperTest::processSync(MultiTouchInputMapper& mapper) { - process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); +void MultiTouchInputMapperTest::processSync(MultiTouchInputMapper& mapper, nsecs_t eventTime, + nsecs_t readTime) { + process(mapper, eventTime, readTime, EV_SYN, SYN_REPORT, 0); } TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithoutTrackingIds) { @@ -10228,6 +10337,56 @@ TEST_F(MultiTouchInputMapperTest, WhenCapturedAndNotCaptured_GetSources) { ASSERT_EQ(AINPUT_SOURCE_TOUCHPAD, mapper.getSources()); } +// --- BluetoothMultiTouchInputMapperTest --- + +class BluetoothMultiTouchInputMapperTest : public MultiTouchInputMapperTest { +protected: + void SetUp() override { + InputMapperTest::SetUp(DEVICE_CLASSES | InputDeviceClass::EXTERNAL, BUS_BLUETOOTH); + } +}; + +TEST_F(BluetoothMultiTouchInputMapperTest, TimestampSmoothening) { + addConfigurationProperty("touch.deviceType", "touchScreen"); + prepareDisplay(DISPLAY_ORIENTATION_0); + prepareAxes(POSITION | ID | SLOT | PRESSURE); + MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>(); + + nsecs_t kernelEventTime = ARBITRARY_TIME; + nsecs_t expectedEventTime = ARBITRARY_TIME; + // Touch down. + processId(mapper, FIRST_TRACKING_ID); + processPosition(mapper, 100, 200); + processPressure(mapper, RAW_PRESSURE_MAX); + processSync(mapper, ARBITRARY_TIME); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithEventTime(ARBITRARY_TIME)))); + + // Process several events that come in quick succession, according to their timestamps. + for (int i = 0; i < 3; i++) { + constexpr static nsecs_t delta = ms2ns(1); + static_assert(delta < MIN_BLUETOOTH_TIMESTAMP_DELTA); + kernelEventTime += delta; + expectedEventTime += MIN_BLUETOOTH_TIMESTAMP_DELTA; + + processPosition(mapper, 101 + i, 201 + i); + processSync(mapper, kernelEventTime); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), + WithEventTime(expectedEventTime)))); + } + + // Release the touch. + processId(mapper, INVALID_TRACKING_ID); + processPressure(mapper, RAW_PRESSURE_MIN); + processSync(mapper, ARBITRARY_TIME + ms2ns(50)); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), + WithEventTime(ARBITRARY_TIME + ms2ns(50))))); +} + +// --- MultiTouchPointerModeTest --- + class MultiTouchPointerModeTest : public MultiTouchInputMapperTest { protected: float mPointerMovementScale; diff --git a/services/inputflinger/tests/TestInputListenerMatchers.h b/services/inputflinger/tests/TestInputListenerMatchers.h index 5107af7e27..9a47e3e4e7 100644 --- a/services/inputflinger/tests/TestInputListenerMatchers.h +++ b/services/inputflinger/tests/TestInputListenerMatchers.h @@ -91,4 +91,9 @@ MATCHER_P(WithButtonState, buttons, "InputEvent with specified button state") { return arg.buttonState == buttons; } +MATCHER_P(WithEventTime, eventTime, "InputEvent with specified eventTime") { + *result_listener << "expected event time " << eventTime << ", but got " << arg.eventTime; + return arg.eventTime == eventTime; +} + } // namespace android |