Android solves the problem of touch freeze and anti-shake in USB TP driver

I haven’t written anything for a long time, so let’s continue today.

Article directory

        • **background**
        • **Problem Description**
        • **initial analysis**
        • **solution**
        • **Tested and Verified**
        • **20231020 Final Solution**
        • **in conclusion**
Background

In a relatively mature support project, the customer replaced the USB touch screen. Since the equipment has been assembled into a complete machine, it is not convenient to disassemble it, so I chose to troubleshoot and debug the problem through software. In this scenario, the anti-shake problem of touch events becomes a problem, which is often due to redundant touch events caused by USB TP hardware factors.

Problem Description

Customers reported that when operating the touch screen, multiple touch events would occasionally be triggered instead of the expected single event. This situation may lead to erratic behavior of the user interface, such as accidental touches and visual glitches. The specific manifestation is that when clicking on the USB touch screen, there will sometimes be no “lift” event or multiple consecutive clicks, which makes the visual illusion that the system is stuck. In this article, I will explore how to optimize USB TP in the Android kernel driver and solve the anti-shake problem of this touch event.

Preliminary analysis

After debugging using the getevent command and carefully examining the driver code, I observed that the driver reported a status for each touch point when handling touch events. However, when the touch point leaves the screen, the driver does not seem to report its leaving status immediately.

The following are two event logs captured through getevent -lv for comparison of the same operations:

Normal log:

/dev/input/event5: EV_ABS ABS_MT_TRACKING_ID 0000002f
/dev/input/event5: EV_ABS ABS_MT_POSITION_X 0000477b
/dev/input/event5: EV_ABS ABS_MT_POSITION_Y 00004155
/dev/input/event5: EV_ABS ABS_MT_POSITION_X 0000477c
/dev/input/event5: EV_ABS ABS_MT_POSITION_Y 00004152
/dev/input/event5: EV_KEY BTN_TOUCH DOWN
/dev/input/event5: EV_ABS ABS_X 0000477c
/dev/input/event5: EV_ABS ABS_Y 00004152
/dev/input/event5: EV_SYN SYN_REPORT 00000000
/dev/input/event5: EV_ABS ABS_MT_POSITION_X 00004768
/dev/input/event5: EV_ABS ABS_MT_POSITION_Y 00004192
/dev/input/event5: EV_ABS ABS_MT_TRACKING_ID ffffffff
/dev/input/event5: EV_KEY BTN_TOUCH UP
/dev/input/event5: EV_SYN SYN_REPORT 00000000
//normal

Exception log:

/dev/input/event5: EV_ABS ABS_MT_TRACKING_ID 00000030
/dev/input/event5: EV_ABS ABS_MT_POSITION_X 00003b69
/dev/input/event5: EV_ABS ABS_MT_POSITION_Y 00004038
/dev/input/event5: EV_KEY BTN_TOUCH DOWN
/dev/input/event5: EV_ABS ABS_X 00003b69
/dev/input/event5: EV_ABS ABS_Y 00004038
/dev/input/event5: EV_SYN SYN_REPORT 00000000
/dev/input/event5: EV_ABS ABS_MT_POSITION_X 00003b82
/dev/input/event5: EV_ABS ABS_MT_POSITION_Y 00004023
/dev/input/event5: EV_ABS ABS_MT_POSITION_X 00003b97
/dev/input/event5: EV_ABS ABS_MT_POSITION_Y 00004012
/dev/input/event5: EV_ABS ABS_X 00003b97
/dev/input/event5: EV_ABS ABS_Y 00004012
/dev/input/event5: EV_SYN SYN_REPORT 00000000


//unusual

From these two logs, the following key differences can be extracted:

  1. Difference in tracking ID: In the normal log, the touch screen only reports the tracking ID (0000002f) for one touch point. And in the exception log, the touch screen reported the tracking ID of two touch points (00000030 and ffffffff). This may suggest that in the exception log, the touch screen may have detected two or more touch points.

  2. Response to key events: In the normal log, the touch screen reports the key event (BTN_TOUCH DOWN) immediately after reporting the touch location, and reports the key event (BTN_TOUCH UP) immediately after the touch ends. In the exception log, the touch screen did not report the key event immediately after reporting the touch position, but there was a certain delay.

Simply put, for the same operation, the exception log shows that the touch screen did not detect the release action in time after pressing.

In addition, I also compared and tested XXX, XXX and XXX projects and found that they all have the same phenomenon. After asking other colleagues, I learned that they also encountered similar problems, which were solved through firmware updates provided by the touch screen manufacturer (however, our TP is no longer supported, so it can only be solved through system software).

Solution
  1. Report touch point away status immediately
    I modified the driver code to report the away status of a touch point as soon as it is processed.

  2. Add anti-shake logic
    In order to solve the problem of multiple touch events, I added anti-shake logic to the driver. A timestamp is used to record the time of the last touch event and compare it to the time of the current event. If the time difference between two events is less than a predefined threshold (e.g. 100 milliseconds), the current event is ignored.

#define DEBOUNCE_TIME_MS 100
#define MOVE_THRESHOLD 100
#define SLIDE_TIME_THRESHOLD_MS 200

static int last_x = -1, last_y = -1;
static bool was_moved = false;
static ktime_t last_report_time;
static ktime_t touch_start_time;

static void mt_complete_slot(struct mt_device *td, struct input_dev *input) {<!-- -->
    ktime_t now = ktime_get();
    ktime_t diff = ktime_sub(now, last_report_time);

    if ((td->mtclass.quirks & amp; MT_QUIRK_CONTACT_CNT_ACCURATE) & amp; & amp;
        td->num_received >= td->num_expected)
        return;

    if (td->curvalid || (td->mtclass.quirks & amp; MT_QUIRK_ALWAYS_VALID)) {<!-- -->
        int slotnum = mt_compute_slot(td, input);
        
        // Block multi-touch
        if (slotnum != 0) {<!-- -->
            return;
        }

        struct mt_slot *s = & amp;td->curdata;

        if (ktime_to_ms(diff) < DEBOUNCE_TIME_MS) {<!-- -->
            return;
        }

        if (s->touch_state || s->inrange_state) {<!-- -->
int wide = (s->w > s->h);
            int major = max(s->w, s->h) >> 1;
            int minor = min(s->w, s->h) >> 1;
            int x_diff = abs(last_x - s->x);
            int y_diff = abs(last_y - s->y);

            if (x_diff > MOVE_THRESHOLD || y_diff > MOVE_THRESHOLD) {<!-- -->
                ktime_t touch_duration = ktime_sub(now, touch_start_time);
                if (ktime_to_ms(touch_duration) > SLIDE_TIME_THRESHOLD_MS) {<!-- -->
                    was_moved = true;
                }
            }

            last_x = s->x;
            last_y = s->y;

            input_event(input, EV_ABS, ABS_MT_POSITION_X, s->x);
            input_event(input, EV_ABS, ABS_MT_POSITION_Y, s->y);
            input_event(input, EV_ABS, ABS_MT_TOOL_X, s->cx);
            input_event(input, EV_ABS, ABS_MT_TOOL_Y, s->cy);
            input_event(input, EV_ABS, ABS_MT_DISTANCE, !s->touch_state);
            input_event(input, EV_ABS, ABS_MT_ORIENTATION, wide);
            input_event(input, EV_ABS, ABS_MT_PRESSURE, s->p);
            input_event(input, EV_ABS, ABS_MT_TOUCH_MAJOR, major);
            input_event(input, EV_ABS, ABS_MT_TOUCH_MINOR, minor);
            input_report_key(input, BTN_TOUCH, 1);
            input_sync(input);

            if (!was_moved) {<!-- -->
                // If there is no continuous sliding, send the UP event immediately
                input_mt_report_slot_state(input, MT_TOOL_FINGER, false);
                input_report_key(input, BTN_TOUCH, 0);
                input_sync(input);
            }
        } else {<!-- -->
            if (was_moved) {<!-- -->
                // If there was a slide before, send the UP event now
                input_mt_report_slot_state(input, MT_TOOL_FINGER, false);
                input_report_key(input, BTN_TOUCH, 0);
                input_sync(input);
                was_moved = false;
            }
            last_x = -1;
            last_y = -1;
        }
    }
}
     

A constant DEBOUNCE_TIME_MS is defined at the beginning of the file and its value is set to 100 milliseconds. This is the time threshold used to handle touch event debounce.

A new variable last_report_time is defined to store the time of the last touch event report.

In the mt_complete_slot function, first obtain the current time and calculate the time difference from the last report to the present. When a valid touch point is detected, it is first checked whether the defined anti-shake time has elapsed since the last report. If the time difference is less than DEBOUNCE_TIME_MS, the touch event will not be processed and returned directly.

If the touch event is valid, the touch status is reported and the event is synchronized immediately. This ensures that touch events are reported to upper-layer applications in a timely manner.

After all touch points are processed, the leaving status of the touch point will be reported immediately and this event will be synchronized. This ensures that the end status of the touch is reported to the upper application in a timely manner.

Finally update last_report_time to the current time for next time checking.

Testing and Validation

The modified driver was tested on actual hardware. The test results show that the behavior of touch events is now more stable, and there are no more accidental touches and lags But! The long press function is sacrificed, but customers don’t need it. They just need to solve the stuck’ problem.

20231020 Final Solution

I have been investigating this issue for almost a day, just to prevent the effect from being affected in any way.

#define DEBOUNCE_TIME_MS 100
#define MOVE_THRESHOLD 100
#define SLIDE_TIME_THRESHOLD_MS 200

static int last_x = -1, last_y = -1;
static bool was_moved = false;
static ktime_t last_report_time;
static ktime_t touch_start_time;

static void mt_complete_slot(struct mt_device *td, struct input_dev *input) {<!-- -->
    ktime_t now = ktime_get();
    ktime_t diff = ktime_sub(now, last_report_time);

    if ((td->mtclass.quirks & amp; MT_QUIRK_CONTACT_CNT_ACCURATE) & amp; & amp;
        td->num_received >= td->num_expected)
        return;

    if (td->curvalid || (td->mtclass.quirks & amp; MT_QUIRK_ALWAYS_VALID)) {<!-- -->
        int slotnum = mt_compute_slot(td, input);
        
        // Block multi-touch
        if (slotnum != 0) {<!-- -->
            return;
        }

        struct mt_slot *s = & amp;td->curdata;

        if (ktime_to_ms(diff) < DEBOUNCE_TIME_MS) {<!-- -->
            return;
        }

        if (s->touch_state || s->inrange_state) {<!-- -->
int wide = (s->w > s->h);
            int major = max(s->w, s->h) >> 1;
            int minor = min(s->w, s->h) >> 1;
            int x_diff = abs(last_x - s->x);
            int y_diff = abs(last_y - s->y);

            if (x_diff > MOVE_THRESHOLD || y_diff > MOVE_THRESHOLD) {<!-- -->
                ktime_t touch_duration = ktime_sub(now, touch_start_time);
                if (ktime_to_ms(touch_duration) > SLIDE_TIME_THRESHOLD_MS) {<!-- -->
                    was_moved = true;
                }
            }

            last_x = s->x;
            last_y = s->y;

            input_event(input, EV_ABS, ABS_MT_POSITION_X, s->x);
            input_event(input, EV_ABS, ABS_MT_POSITION_Y, s->y);
            input_event(input, EV_ABS, ABS_MT_TOOL_X, s->cx);
            input_event(input, EV_ABS, ABS_MT_TOOL_Y, s->cy);
            input_event(input, EV_ABS, ABS_MT_DISTANCE, !s->touch_state);
            input_event(input, EV_ABS, ABS_MT_ORIENTATION, wide);
            input_event(input, EV_ABS, ABS_MT_PRESSURE, s->p);
            input_event(input, EV_ABS, ABS_MT_TOUCH_MAJOR, major);
            input_event(input, EV_ABS, ABS_MT_TOUCH_MINOR, minor);
            input_report_key(input, BTN_TOUCH, 1);
            input_sync(input);

            if (!was_moved) {<!-- -->
                // If there is no continuous sliding, send the UP event immediately
                input_mt_report_slot_state(input, MT_TOOL_FINGER, false);
                input_report_key(input, BTN_TOUCH, 0);
                input_sync(input);
            }
        } else {<!-- -->
            if (was_moved) {<!-- -->
                // If there was a slide before, send the UP event now
                input_mt_report_slot_state(input, MT_TOOL_FINGER, false);
                input_report_key(input, BTN_TOUCH, 0);
                input_sync(input);
                was_moved = false;
            }
            last_x = -1;
            last_y = -1;
        }
    }
}

Final solution summary:

Problem description:
In order to solve the previous problem of not being able to long press and drag to slide, I hope to implement the following functions in the touch screen driver:

  1. A touch release (UP) event should be sent immediately when the user clicks on the screen without continuing to swipe.
  2. When the user performs a continuous swipe on the screen, a touch release (UP) event should be sent after the swipe ends.
  3. Block multi-touch and only process the first touch point.

Solution:

  1. Anti-shake processing: In order to avoid misjudgments caused by tiny finger vibrations, an anti-shake time threshold DEBOUNCE_TIME_MS is introduced. If the time between two touch events is less than this value, the event will be ignored.

  2. Motion Detection: Use MOVE_THRESHOLD to determine if the touch point has moved. Only when the touch point moves farther than this value will it be considered a valid movement.

  3. Sliding Detection: Use SLIDE_TIME_THRESHOLD_MS to determine if sliding has occurred. The touch point must continue to move for longer than this time for it to be considered a slide.

  4. Handling single touch: To block multi-touch, check the slot number of the touch point. If it is not the first touch point (that is, the slot number is not 0), this touch point will be ignored.

  5. Send UP event:

    • For click non-sustained sliding situations, the UP event is sent immediately after the touch point is detected.
    • For continuous sliding, the UP event is sent after the sliding ends.

Code implementation:
The above logic is implemented in the mt_complete_slot function. Check the number and validity of touch points. Then use the ktime_get function to obtain the current time and compare it with the last touch event time to determine whether anti-shake processing is required.

Then check the distance and duration of the touch point movement to determine whether a slide occurred. If a slide occurs, the was_moved flag is set.

Determine whether to send the UP event based on the was_moved flag and touch status. If no sliding occurs (normal touch click), the UP event will be sent immediately. If a slide occurs, the UP event will be sent after the slide ends.

Through the above modifications, the requirements were successfully realized (no difference between test and normal, no impact on operation), that is, click and slide events are correctly processed, and multi-touch is blocked.

Conclusion

Based on my solution, the customer’s USB TP now clicks normally, meeting their needs. I suspect there is a problem with the USB TP itself, but can’t confirm further since the vendor no longer supports this TP. In the Android kernel driver, in order to ensure accurate reporting of the status of each touch point. I added debounce logic to effectively avoid redundant touch events caused by hardware or other external factors.

I hope this blog was helpful to you, if you have any questions or suggestions, please leave them in the comments section. Thanks!