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

Commit c42ed4a0 authored by Prabir Pradhan's avatar Prabir Pradhan Committed by Android (Google) Code Review
Browse files

Merge "InputReader: Clear the multi-touch state when the device is reset" into main

parents 8597755c f8d9e440
Loading
Loading
Loading
Loading
+4 −6
Original line number Diff line number Diff line
@@ -35,12 +35,10 @@ MultiTouchInputMapper::MultiTouchInputMapper(InputDeviceContext& deviceContext,
MultiTouchInputMapper::~MultiTouchInputMapper() {}

std::list<NotifyArgs> MultiTouchInputMapper::reset(nsecs_t when) {
    // The evdev multi-touch protocol does not allow userspace applications to query the initial or
    // current state of the pointers at any time. This means if we clear our accumulated state when
    // resetting the input mapper, there's no way to rebuild the full initial state of the pointers.
    // We can only wait for updates to all the pointers and axes. Rather than clearing the state and
    // rebuilding the state from scratch, we work around this kernel API limitation by never
    // fully clearing any state specific to the multi-touch protocol.
    // TODO(b/291626046): Sync the MT state with the kernel using EVIOCGMTSLOTS.
    mMultiTouchMotionAccumulator.reset(getDeviceContext());
    mPointerIdBits.clear();

    return TouchInputMapper::reset(when);
}

+22 −16
Original line number Diff line number Diff line
@@ -30,9 +30,16 @@ void MultiTouchMotionAccumulator::configure(const InputDeviceContext& deviceCont
                                            size_t slotCount, bool usingSlotsProtocol) {
    mUsingSlotsProtocol = usingSlotsProtocol;
    mSlots = std::vector<Slot>(slotCount);
    reset(deviceContext);
}

void MultiTouchMotionAccumulator::reset(const InputDeviceContext& deviceContext) {
    resetSlots();

    if (!mUsingSlotsProtocol) {
        return;
    }

    mCurrentSlot = -1;
    if (mUsingSlotsProtocol) {
    // Query the driver for the current slot index and use it as the initial slot before we
    // start reading events from the device.  It is possible that the current slot index will
    // not be the same as it was when the first event was written into the evdev buffer, which
@@ -48,7 +55,6 @@ void MultiTouchMotionAccumulator::configure(const InputDeviceContext& deviceCont
        ALOGD("Could not retrieve current multi-touch slot index. status=%d", status);
    }
}
}

void MultiTouchMotionAccumulator::resetSlots() {
    for (Slot& slot : mSlots) {
+1 −0
Original line number Diff line number Diff line
@@ -83,6 +83,7 @@ public:
        LOG_ALWAYS_FATAL_IF(index < 0 || index >= mSlots.size(), "Invalid index: %zu", index);
        return mSlots[index];
    }
    void reset(const InputDeviceContext& deviceContext);

private:
    int32_t mCurrentSlot;
+143 −8
Original line number Diff line number Diff line
@@ -232,6 +232,11 @@ public:
        mResetWasCalled = false;
    }
    void assertResetWasNotCalled() {
        std::scoped_lock lock(mLock);
        ASSERT_FALSE(mResetWasCalled) << "Expected reset to not have been called.";
    }
    void assertProcessWasCalled(RawEvent* outLastEvent = nullptr) {
        std::unique_lock<std::mutex> lock(mLock);
        base::ScopedLockAssertion assumeLocked(mLock);
@@ -248,6 +253,11 @@ public:
        mProcessWasCalled = false;
    }
    void assertProcessWasNotCalled() {
        std::scoped_lock lock(mLock);
        ASSERT_FALSE(mProcessWasCalled) << "Expected process to not have been called.";
    }
    void setKeyCodeState(int32_t keyCode, int32_t state) {
        mKeyCodeStates.replaceValueFor(keyCode, state);
    }
@@ -2872,6 +2882,60 @@ TEST_F(InputDeviceTest, GetBluetoothAddress) {
    ASSERT_EQ(DEVICE_BLUETOOTH_ADDRESS, *address);
}
TEST_F(InputDeviceTest, KernelBufferOverflowResetsMappers) {
    mFakePolicy->clearViewports();
    FakeInputMapper& mapper =
            mDevice->addMapper<FakeInputMapper>(EVENTHUB_ID, mFakePolicy->getReaderConfiguration(),
                                                AINPUT_SOURCE_KEYBOARD);
    std::list<NotifyArgs> unused =
            mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
                               /*changes=*/{});
    mapper.assertConfigureWasCalled();
    mapper.assertResetWasNotCalled();
    RawEvent event{.deviceId = EVENTHUB_ID,
                   .readTime = ARBITRARY_TIME,
                   .when = ARBITRARY_TIME,
                   .type = EV_SYN,
                   .code = SYN_REPORT,
                   .value = 0};
    // Events are processed normally.
    unused = mDevice->process(&event, /*count=*/1);
    mapper.assertProcessWasCalled();
    // Simulate a kernel buffer overflow, which generates a SYN_DROPPED event.
    // This should reset the mapper.
    event.type = EV_SYN;
    event.code = SYN_DROPPED;
    event.value = 0;
    unused = mDevice->process(&event, /*count=*/1);
    mapper.assertProcessWasNotCalled();
    mapper.assertResetWasCalled();
    // All events until the next SYN_REPORT should be dropped.
    event.type = EV_KEY;
    event.code = KEY_A;
    event.value = 1;
    unused = mDevice->process(&event, /*count=*/1);
    mapper.assertProcessWasNotCalled();
    // We get the SYN_REPORT event now, which is not forwarded to mappers.
    event.type = EV_SYN;
    event.code = SYN_REPORT;
    event.value = 0;
    unused = mDevice->process(&event, /*count=*/1);
    mapper.assertProcessWasNotCalled();
    // The mapper receives events normally now.
    event.type = EV_KEY;
    event.code = KEY_B;
    event.value = 1;
    unused = mDevice->process(&event, /*count=*/1);
    mapper.assertProcessWasCalled();
}
// --- SwitchInputMapperTest ---
class SwitchInputMapperTest : public InputMapperTest {
@@ -10907,7 +10971,7 @@ TEST_F(MultiTouchInputMapperTest, Process_MultiTouch_WithInvalidTrackingId) {
    ASSERT_EQ(uint32_t(1), motionArgs.getPointerCount());
}
TEST_F(MultiTouchInputMapperTest, Reset_PreservesLastTouchState) {
TEST_F(MultiTouchInputMapperTest, ResetClearsTouchState) {
    addConfigurationProperty("touch.deviceType", "touchScreen");
    prepareDisplay(ui::ROTATION_0);
    prepareAxes(POSITION | ID | SLOT | PRESSURE);
@@ -10930,25 +10994,36 @@ TEST_F(MultiTouchInputMapperTest, Reset_PreservesLastTouchState) {
    ASSERT_NO_FATAL_FAILURE(
            mFakeListener->assertNotifyMotionWasCalled(WithMotionAction(ACTION_POINTER_1_DOWN)));
    // Reset the mapper. When the mapper is reset, we expect the current multi-touch state to be
    // preserved. Resetting should cancel the ongoing gesture.
    // Reset the mapper. When the mapper is reset, the touch state is also cleared.
    resetMapper(mapper, ARBITRARY_TIME);
    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
            WithMotionAction(AMOTION_EVENT_ACTION_CANCEL)));
    // Send a sync to simulate an empty touch frame where nothing changes. The mapper should use
    // the existing touch state to generate a down event.
    // Move the second slot pointer, and ensure there are no events, because the touch state was
    // cleared and no slots should be in use.
    processPosition(mapper, 301, 302);
    processSync(mapper);
    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
    // Release both fingers.
    processId(mapper, INVALID_TRACKING_ID);
    processSlot(mapper, FIRST_SLOT);
    processId(mapper, INVALID_TRACKING_ID);
    processSync(mapper);
    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
    // Start a new gesture, and ensure we get a DOWN event for it.
    processId(mapper, FIRST_TRACKING_ID);
    processPosition(mapper, 200, 300);
    processPressure(mapper, RAW_PRESSURE_MAX);
    processSync(mapper);
    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithPressure(1.f))));
    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
            AllOf(WithMotionAction(ACTION_POINTER_1_DOWN), WithPressure(1.f))));
    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
}
TEST_F(MultiTouchInputMapperTest, Reset_PreservesLastTouchState_NoPointersDown) {
TEST_F(MultiTouchInputMapperTest, ResetClearsTouchStateWithNoPointersDown) {
    addConfigurationProperty("touch.deviceType", "touchScreen");
    prepareDisplay(ui::ROTATION_0);
    prepareAxes(POSITION | ID | SLOT | PRESSURE);
@@ -11076,6 +11151,66 @@ TEST_F(MultiTouchInputMapperTest, Process_WhenConfigDisabled_ShouldNotShowDirect
    ASSERT_FALSE(fakePointerController->isPointerShown());
}
TEST_F(MultiTouchInputMapperTest, SimulateKernelBufferOverflow) {
    addConfigurationProperty("touch.deviceType", "touchScreen");
    prepareDisplay(ui::ROTATION_0);
    prepareAxes(POSITION | ID | SLOT | PRESSURE);
    MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
    // First finger down.
    processId(mapper, FIRST_TRACKING_ID);
    processPosition(mapper, 100, 200);
    processPressure(mapper, RAW_PRESSURE_MAX);
    processSync(mapper);
    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
            WithMotionAction(AMOTION_EVENT_ACTION_DOWN)));
    // Assume the kernel buffer overflows, and we get a SYN_DROPPED event.
    // This will reset the mapper, and thus also reset the touch state.
    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_DROPPED, 0);
    resetMapper(mapper, ARBITRARY_TIME);
    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
            WithMotionAction(AMOTION_EVENT_ACTION_CANCEL)));
    // Since the touch state was reset, it doesn't know which slots are active, so any movements
    // are ignored.
    processPosition(mapper, 101, 201);
    processSync(mapper);
    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
    // Second finger goes down. This is the first active finger, so we get a DOWN event.
    processSlot(mapper, SECOND_SLOT);
    processId(mapper, SECOND_TRACKING_ID);
    processPosition(mapper, 400, 500);
    processPressure(mapper, RAW_PRESSURE_MAX);
    processSync(mapper);
    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
            WithMotionAction(AMOTION_EVENT_ACTION_DOWN)));
    // First slot is still ignored, only the second one is active.
    processSlot(mapper, FIRST_SLOT);
    processPosition(mapper, 102, 202);
    processSlot(mapper, SECOND_SLOT);
    processPosition(mapper, 401, 501);
    processSync(mapper);
    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
            WithMotionAction(AMOTION_EVENT_ACTION_MOVE)));
    // Both slots up, so we get the UP event for the active pointer.
    processSlot(mapper, FIRST_SLOT);
    processId(mapper, INVALID_TRACKING_ID);
    processSlot(mapper, SECOND_SLOT);
    processId(mapper, INVALID_TRACKING_ID);
    processSync(mapper);
    ASSERT_NO_FATAL_FAILURE(
            mFakeListener->assertNotifyMotionWasCalled(WithMotionAction(AMOTION_EVENT_ACTION_UP)));
    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
}
// --- MultiTouchInputMapperTest_ExternalDevice ---
class MultiTouchInputMapperTest_ExternalDevice : public MultiTouchInputMapperTest {