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

Commit e8296bdc authored by Prabir Pradhan's avatar Prabir Pradhan Committed by Automerger Merge Worker
Browse files

Preserve multi-touch slot state when resetting input mappers am: 6c7fd13c

parents e0358af0 6c7fd13c
Loading
Loading
Loading
Loading
+15 −18
Original line number Diff line number Diff line
@@ -48,11 +48,8 @@ void MultiTouchMotionAccumulator::configure(InputDeviceContext& deviceContext, s

    delete[] mSlots;
    mSlots = new Slot[slotCount];
}

void MultiTouchMotionAccumulator::reset(InputDeviceContext& deviceContext) {
    // Unfortunately there is no way to read the initial contents of the slots.
    // So when we reset the accumulator, we must assume they are all zeroes.
    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
@@ -64,24 +61,22 @@ void MultiTouchMotionAccumulator::reset(InputDeviceContext& deviceContext) {
        // This can cause the touch point to "jump", but at least there will be
        // no stuck touches.
        int32_t initialSlot;
        status_t status = deviceContext.getAbsoluteAxisValue(ABS_MT_SLOT, &initialSlot);
        if (status) {
            ALOGD("Could not retrieve current multitouch slot index.  status=%d", status);
            initialSlot = -1;
        }
        clearSlots(initialSlot);
        if (const auto status = deviceContext.getAbsoluteAxisValue(ABS_MT_SLOT, &initialSlot);
            status == OK) {
            mCurrentSlot = initialSlot;
        } else {
        clearSlots(-1);
            ALOGD("Could not retrieve current multi-touch slot index. status=%d", status);
        }
    }
}

void MultiTouchMotionAccumulator::clearSlots(int32_t initialSlot) {
void MultiTouchMotionAccumulator::resetSlots() {
    if (mSlots) {
        for (size_t i = 0; i < mSlotCount; i++) {
            mSlots[i].clear();
        }
    }
    mCurrentSlot = initialSlot;
    mCurrentSlot = -1;
}

void MultiTouchMotionAccumulator::process(const RawEvent* rawEvent) {
@@ -169,7 +164,7 @@ void MultiTouchMotionAccumulator::process(const RawEvent* rawEvent) {

void MultiTouchMotionAccumulator::finishSync() {
    if (!mUsingSlotsProtocol) {
        clearSlots(-1);
        resetSlots();
    }
}

@@ -230,10 +225,12 @@ MultiTouchInputMapper::MultiTouchInputMapper(InputDeviceContext& deviceContext)
MultiTouchInputMapper::~MultiTouchInputMapper() {}

void MultiTouchInputMapper::reset(nsecs_t when) {
    mMultiTouchMotionAccumulator.reset(getDeviceContext());

    mPointerIdBits.clear();

    // 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.
    TouchInputMapper::reset(when);
}

+1 −2
Original line number Diff line number Diff line
@@ -71,7 +71,6 @@ public:
    ~MultiTouchMotionAccumulator();

    void configure(InputDeviceContext& deviceContext, size_t slotCount, bool usingSlotsProtocol);
    void reset(InputDeviceContext& deviceContext);
    void process(const RawEvent* rawEvent);
    void finishSync();
    bool hasStylus() const;
@@ -86,7 +85,7 @@ private:
    bool mUsingSlotsProtocol;
    bool mHaveStylus;

    void clearSlots(int32_t initialSlot);
    void resetSlots();
    void warnIfNotInUse(const RawEvent& event, const Slot& slot);
};

+4 −0
Original line number Diff line number Diff line
@@ -1473,6 +1473,10 @@ void TouchInputMapper::process(const RawEvent* rawEvent) {
}

void TouchInputMapper::sync(nsecs_t when, nsecs_t readTime) {
    if (mDeviceMode == DeviceMode::DISABLED) {
        // Only save the last pending state when the device is disabled.
        mRawStatesPending.clear();
    }
    // Push a new state.
    mRawStatesPending.emplace_back();

+2 −0
Original line number Diff line number Diff line
@@ -327,6 +327,8 @@ protected:
        int32_t rawVScroll;
        int32_t rawHScroll;

        explicit inline RawState() { clear(); }

        void copyFrom(const RawState& other) {
            when = other.when;
            readTime = other.readTime;
+110 −3
Original line number Diff line number Diff line
@@ -6787,6 +6787,33 @@ TEST_F(SingleTouchInputMapperTest, Process_WhenAbsPressureIsPresent_HoversIfItsV
            toDisplayX(150), toDisplayY(250), 0, 0, 0, 0, 0, 0, 0, 0));
}

TEST_F(SingleTouchInputMapperTest, Reset_RecreatesTouchState) {
    addConfigurationProperty("touch.deviceType", "touchScreen");
    prepareDisplay(DISPLAY_ORIENTATION_0);
    prepareButtons();
    prepareAxes(POSITION | PRESSURE);
    SingleTouchInputMapper& mapper = addMapperAndConfigure<SingleTouchInputMapper>();
    NotifyMotionArgs motionArgs;

    // Set the initial state for the touch pointer.
    mFakeEventHub->setAbsoluteAxisValue(EVENTHUB_ID, ABS_X, 100);
    mFakeEventHub->setAbsoluteAxisValue(EVENTHUB_ID, ABS_Y, 200);
    mFakeEventHub->setAbsoluteAxisValue(EVENTHUB_ID, ABS_PRESSURE, RAW_PRESSURE_MAX);
    mFakeEventHub->setScanCodeState(EVENTHUB_ID, BTN_TOUCH, 1);

    // Reset the mapper. When the mapper is reset, we expect it to attempt to recreate the touch
    // state by reading the current axis values.
    mapper.reset(ARBITRARY_TIME);

    // Send a sync to simulate an empty touch frame where nothing changes. The mapper should use
    // the recreated touch state to generate a down event.
    processSync(mapper);
    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
    ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action);

    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
}

TEST_F(SingleTouchInputMapperTest,
       Process_WhenViewportDisplayIdChanged_TouchIsCanceledAndDeviceIsReset) {
    addConfigurationProperty("touch.deviceType", "touchScreen");
@@ -6848,10 +6875,17 @@ TEST_F(SingleTouchInputMapperTest,
    // No events are generated while the viewport is inactive.
    processMove(mapper, 101, 201);
    processSync(mapper);
    processDown(mapper, 102, 202);
    processUp(mapper);
    processSync(mapper);
    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());

    // Start a new gesture while the viewport is still inactive.
    processDown(mapper, 300, 400);
    mFakeEventHub->setAbsoluteAxisValue(EVENTHUB_ID, ABS_X, 300);
    mFakeEventHub->setAbsoluteAxisValue(EVENTHUB_ID, ABS_Y, 400);
    mFakeEventHub->setScanCodeState(EVENTHUB_ID, BTN_TOUCH, 1);
    processSync(mapper);

    // Make the viewport active again. The device should resume processing events.
    viewport->isActive = true;
    mFakePolicy->updateViewport(*viewport);
@@ -6861,8 +6895,7 @@ TEST_F(SingleTouchInputMapperTest,
    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled());
    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());

    // Start a new gesture.
    processDown(mapper, 100, 200);
    // In the next sync, the touch state that was recreated when the device was reset is reported.
    processSync(mapper);
    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
    ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action);
@@ -9397,6 +9430,80 @@ TEST_F(MultiTouchInputMapperTest, Process_MultiTouch_WithInvalidTrackingId) {
    ASSERT_EQ(uint32_t(1), motionArgs.pointerCount);
}

TEST_F(MultiTouchInputMapperTest, Reset_PreservesLastTouchState) {
    addConfigurationProperty("touch.deviceType", "touchScreen");
    prepareDisplay(DISPLAY_ORIENTATION_0);
    prepareAxes(POSITION | ID | SLOT | PRESSURE);
    MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();

    NotifyMotionArgs motionArgs;

    // 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(&motionArgs));
    ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action);

    // Second finger down.
    processSlot(mapper, SECOND_SLOT);
    processId(mapper, SECOND_TRACKING_ID);
    processPosition(mapper, 300, 400);
    processPressure(mapper, RAW_PRESSURE_MAX);
    processSync(mapper);
    ASSERT_NO_FATAL_FAILURE(
            mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
    ASSERT_EQ(ACTION_POINTER_1_DOWN, motionArgs.action);

    // Reset the mapper. When the mapper is reset, we expect the current multi-touch state to be
    // preserved. Resetting should not generate any events.
    mapper.reset(ARBITRARY_TIME);
    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());

    // 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.
    processPosition(mapper, 301, 302);
    processSync(mapper);
    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
    ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action);
    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
    ASSERT_EQ(ACTION_POINTER_1_DOWN, motionArgs.action);

    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
}

TEST_F(MultiTouchInputMapperTest, Reset_PreservesLastTouchState_NoPointersDown) {
    addConfigurationProperty("touch.deviceType", "touchScreen");
    prepareDisplay(DISPLAY_ORIENTATION_0);
    prepareAxes(POSITION | ID | SLOT | PRESSURE);
    MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();

    NotifyMotionArgs motionArgs;

    // First finger touches down and releases.
    processId(mapper, FIRST_TRACKING_ID);
    processPosition(mapper, 100, 200);
    processPressure(mapper, RAW_PRESSURE_MAX);
    processSync(mapper);
    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
    ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action);
    processId(mapper, INVALID_TRACKING_ID);
    processSync(mapper);
    ASSERT_NO_FATAL_FAILURE(
            mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
    ASSERT_EQ(AMOTION_EVENT_ACTION_UP, motionArgs.action);

    // Reset the mapper. When the mapper is reset, we expect it to restore the latest
    // raw state where no pointers are down.
    mapper.reset(ARBITRARY_TIME);
    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());

    // Send an empty sync frame. Since there are no pointers, no events are generated.
    processSync(mapper);
    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
}

// --- MultiTouchInputMapperTest_ExternalDevice ---

class MultiTouchInputMapperTest_ExternalDevice : public MultiTouchInputMapperTest {