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

Commit 484d55ac authored by Prabir Pradhan's avatar Prabir Pradhan
Browse files

Add integration tests to verify the behavior of a fused external stylus

A fused external stylus reports pressure through a separate input
device. That pressure information is then fused with touches from the
touchscreen.

Bug: 246394583
Test: atest inputflinger_tests
Change-Id: I2c00fca0f5e3d5214ebb2d0af04ed4efe14de9f7
parent 7d04c4bc
Loading
Loading
Loading
Loading
+120 −1
Original line number Diff line number Diff line
@@ -2523,7 +2523,8 @@ TEST_F(InputReaderIntegrationTest, SendsGearDownAndUpToInputListener) {
    ASSERT_EQ(BTN_GEAR_UP, keyArgs.scanCode);
}
// --- TouchProcessTest ---
// --- TouchIntegrationTest ---
class TouchIntegrationTest : public InputReaderIntegrationTest {
protected:
    const std::string UNIQUE_ID = "local:0";
@@ -2940,6 +2941,124 @@ TEST_F(TouchIntegrationTest, StylusButtonsWithinTouchGesture) {
                  WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS), WithButtonState(0))));
}
// --- ExternalStylusIntegrationTest ---
// Verify the behavior of an external stylus. An external stylus can report pressure or button
// data independently of the touchscreen, which is then sent as a MotionEvent as part of an
// ongoing stylus gesture that is being emitted by the touchscreen.
using ExternalStylusIntegrationTest = TouchIntegrationTest;
TEST_F(ExternalStylusIntegrationTest, FusedExternalStylusPressureReported) {
    const Point centerPoint = mDevice->getCenterPoint();
    // Create an external stylus capable of reporting pressure data that
    // should be fused with a touch pointer.
    std::unique_ptr<UinputExternalStylusWithPressure> stylus =
            createUinputDevice<UinputExternalStylusWithPressure>();
    ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertInputDevicesChanged());
    ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyConfigurationChangedWasCalled());
    const auto stylusInfo = findDeviceByName(stylus->getName());
    ASSERT_TRUE(stylusInfo);
    ASSERT_EQ(AINPUT_SOURCE_STYLUS | AINPUT_SOURCE_KEYBOARD, stylusInfo->getSources());
    const auto touchscreenId = mDeviceInfo.getId();
    // Set a pressure value on the stylus. It doesn't generate any events.
    const auto& RAW_PRESSURE_MAX = UinputExternalStylusWithPressure::RAW_PRESSURE_MAX;
    stylus->setPressure(100);
    ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasNotCalled());
    // Start a finger gesture, and ensure it shows up as stylus gesture
    // with the pressure set by the external stylus.
    mDevice->sendSlot(FIRST_SLOT);
    mDevice->sendTrackingId(FIRST_TRACKING_ID);
    mDevice->sendToolType(MT_TOOL_FINGER);
    mDevice->sendDown(centerPoint);
    mDevice->sendSync();
    ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(
            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
                  WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS), WithButtonState(0),
                  WithDeviceId(touchscreenId), WithPressure(100.f / RAW_PRESSURE_MAX))));
    // Change the pressure on the external stylus, and ensure the touchscreen generates a MOVE
    // event with the updated pressure.
    stylus->setPressure(200);
    ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(
            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
                  WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS), WithButtonState(0),
                  WithDeviceId(touchscreenId), WithPressure(200.f / RAW_PRESSURE_MAX))));
    // The external stylus did not generate any events.
    ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasNotCalled());
    ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyKeyWasNotCalled());
}
TEST_F(ExternalStylusIntegrationTest, FusedExternalStylusPressureNotReported) {
    const Point centerPoint = mDevice->getCenterPoint();
    // Create an external stylus capable of reporting pressure data that
    // should be fused with a touch pointer.
    std::unique_ptr<UinputExternalStylusWithPressure> stylus =
            createUinputDevice<UinputExternalStylusWithPressure>();
    ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertInputDevicesChanged());
    ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyConfigurationChangedWasCalled());
    const auto stylusInfo = findDeviceByName(stylus->getName());
    ASSERT_TRUE(stylusInfo);
    ASSERT_EQ(AINPUT_SOURCE_STYLUS | AINPUT_SOURCE_KEYBOARD, stylusInfo->getSources());
    const auto touchscreenId = mDeviceInfo.getId();
    // Set a pressure value of 0 on the stylus. It doesn't generate any events.
    const auto& RAW_PRESSURE_MAX = UinputExternalStylusWithPressure::RAW_PRESSURE_MAX;
    stylus->setPressure(0);
    // Start a finger gesture. The touch device will withhold generating any touches for
    // up to 72 milliseconds while waiting for pressure data from the external stylus.
    mDevice->sendSlot(FIRST_SLOT);
    mDevice->sendTrackingId(FIRST_TRACKING_ID);
    mDevice->sendToolType(MT_TOOL_FINGER);
    mDevice->sendDown(centerPoint);
    auto waitUntil = std::chrono::system_clock::now() +
            std::chrono::milliseconds(ns2ms(EXTERNAL_STYLUS_DATA_TIMEOUT));
    mDevice->sendSync();
    ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasNotCalled(waitUntil));
    // Since the external stylus did not report a pressure value within the timeout,
    // it shows up as a finger pointer.
    ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(
            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
                  WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER), WithDeviceId(touchscreenId),
                  WithPressure(1.f))));
    // Change the pressure on the external stylus. Since the pressure was not present at the start
    // of the gesture, it is ignored for now.
    stylus->setPressure(200);
    ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasNotCalled());
    // Finish the finger gesture.
    mDevice->sendTrackingId(INVALID_TRACKING_ID);
    mDevice->sendSync();
    ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(
            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
                  WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER))));
    // Start a new gesture. Since we have a valid pressure value, it shows up as a stylus.
    mDevice->sendTrackingId(FIRST_TRACKING_ID);
    mDevice->sendToolType(MT_TOOL_FINGER);
    mDevice->sendDown(centerPoint);
    mDevice->sendSync();
    ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(
            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
                  WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS), WithButtonState(0),
                  WithDeviceId(touchscreenId), WithPressure(200.f / RAW_PRESSURE_MAX))));
    // The external stylus did not generate any events.
    ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasNotCalled());
    ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyKeyWasNotCalled());
}
// --- InputDeviceTest ---
class InputDeviceTest : public testing::Test {
protected:
+8 −6
Original line number Diff line number Diff line
@@ -82,9 +82,9 @@ void TestInputListener::assertNotifyMotionWasCalled(
    ASSERT_THAT(outEventArgs, matcher);
}

void TestInputListener::assertNotifyMotionWasNotCalled() {
void TestInputListener::assertNotifyMotionWasNotCalled(std::optional<TimePoint> waitUntil) {
    ASSERT_NO_FATAL_FAILURE(
            assertNotCalled<NotifyMotionArgs>("notifyMotion() should not be called."));
            assertNotCalled<NotifyMotionArgs>("notifyMotion() should not be called.", waitUntil));
}

void TestInputListener::assertNotifySwitchWasCalled(NotifySwitchArgs* outEventArgs) {
@@ -139,14 +139,16 @@ void TestInputListener::assertCalled(NotifyArgsType* outEventArgs, std::string m
}

template <class NotifyArgsType>
void TestInputListener::assertNotCalled(std::string message) {
void TestInputListener::assertNotCalled(std::string message, std::optional<TimePoint> waitUntil) {
    std::unique_lock<std::mutex> lock(mLock);
    base::ScopedLockAssertion assumeLocked(mLock);

    std::vector<NotifyArgsType>& queue = std::get<std::vector<NotifyArgsType>>(mQueues);
    const bool eventReceived =
            mCondition.wait_for(lock, mEventDidNotHappenTimeout,
                                [&queue]() REQUIRES(mLock) { return !queue.empty(); });
    const auto time =
            waitUntil.value_or(std::chrono::system_clock::now() + mEventDidNotHappenTimeout);
    const bool eventReceived = mCondition.wait_until(lock, time, [&queue]() REQUIRES(mLock) {
        return !queue.empty();
    });
    if (eventReceived) {
        FAIL() << "Unexpected event: " << message.c_str();
    }
+4 −2
Original line number Diff line number Diff line
@@ -33,6 +33,8 @@ public:
                      std::chrono::milliseconds eventDidNotHappenTimeout = 0ms);
    virtual ~TestInputListener();

    using TimePoint = std::chrono::time_point<std::chrono::system_clock>;

    void assertNotifyConfigurationChangedWasCalled(
            NotifyConfigurationChangedArgs* outEventArgs = nullptr);

@@ -52,7 +54,7 @@ public:

    void assertNotifyMotionWasCalled(const ::testing::Matcher<NotifyMotionArgs>& matcher);

    void assertNotifyMotionWasNotCalled();
    void assertNotifyMotionWasNotCalled(std::optional<TimePoint> waitUntil = {});

    void assertNotifySwitchWasCalled(NotifySwitchArgs* outEventArgs = nullptr);

@@ -66,7 +68,7 @@ private:
    void assertCalled(NotifyArgsType* outEventArgs, std::string message);

    template <class NotifyArgsType>
    void assertNotCalled(std::string message);
    void assertNotCalled(std::string message, std::optional<TimePoint> timeout = {});

    template <class NotifyArgsType>
    void addToQueue(const NotifyArgsType* args);
+5 −0
Original line number Diff line number Diff line
@@ -55,6 +55,11 @@ MATCHER_P(WithDisplayId, displayId, "InputEvent with specified displayId") {
    return arg.displayId == displayId;
}

MATCHER_P(WithDeviceId, deviceId, "InputEvent with specified deviceId") {
    *result_listener << "expected deviceId " << deviceId << ", but got " << arg.deviceId;
    return arg.deviceId == deviceId;
}

MATCHER_P(WithKeyCode, keyCode, "KeyEvent with specified key code") {
    *result_listener << "expected key code " << keyCode << ", but got " << arg.keyCode;
    return arg.keyCode == keyCode;
+19 −0
Original line number Diff line number Diff line
@@ -138,6 +138,25 @@ UinputSteamController::UinputSteamController()
UinputExternalStylus::UinputExternalStylus()
      : UinputKeyboard(DEVICE_NAME, PRODUCT_ID, {BTN_STYLUS, BTN_STYLUS2, BTN_STYLUS3}) {}

// --- UinputExternalStylusWithPressure ---

UinputExternalStylusWithPressure::UinputExternalStylusWithPressure()
      : UinputKeyboard(DEVICE_NAME, PRODUCT_ID, {BTN_STYLUS, BTN_STYLUS2, BTN_STYLUS3}) {}

void UinputExternalStylusWithPressure::configureDevice(int fd, uinput_user_dev* device) {
    UinputKeyboard::configureDevice(fd, device);

    ioctl(fd, UI_SET_EVBIT, EV_ABS);
    ioctl(fd, UI_SET_ABSBIT, ABS_PRESSURE);
    device->absmin[ABS_PRESSURE] = RAW_PRESSURE_MIN;
    device->absmax[ABS_PRESSURE] = RAW_PRESSURE_MAX;
}

void UinputExternalStylusWithPressure::setPressure(int32_t pressure) {
    injectEvent(EV_ABS, ABS_PRESSURE, pressure);
    injectEvent(EV_SYN, SYN_REPORT, 0);
}

// --- UinputTouchScreen ---

UinputTouchScreen::UinputTouchScreen(const Rect& size)
Loading