Skip to content

Detect gestures on the main thread, in order with their timeouts - #144

Merged
trypsynth merged 1 commit into
trypsynth:masterfrom
zywek123:handler-thread
Oct 10, 2026
Merged

trypsynth merged 1 commit into
trypsynth:masterfrom
zywek123:handler-thread

Conversation

@zywek123

Copy link
Copy Markdown
Contributor

On Android 13 and later, Backtalk recognizes gestures itself. The touch
interaction controller passed each motion event to a thread of its own,
but the gesture matchers and the touch exploration delay counted their
timeouts on the main thread, so the same state changed on both threads
without locking:

  • A timeout that completes or cancels a gesture, such as the hold of a
    double tap and hold, could run on the main thread while the gesture
    thread handled the lift that ended it, and both could report a
    gesture.
  • When the touch exploration delay ended, it handed its request to the
    gesture thread, where cancelling the delay no longer stopped it, so a
    swipe that started just then could still start touch exploration.
  • Following a held touch with the circle menu and two-finger rotation
    kept state that either thread changed.
  • Cancelling the phonetic letter hint ran the feedback pipeline on the
    gesture thread, and turning touch exploration off once a touch ended
    changed the service info there.
  • Each restart of gesture detection started a new thread without
    stopping the old one.

Android passes motion events and touch state changes to the service
through its main thread before the controller hands them on, so the
gesture thread never got them sooner. The controller now calls the
monitor on the main thread as each event comes in, with no executor,
and the monitor no longer hands requests to another thread; the
requestStateChangeInSameThread flag, which only chose that, is gone.
Events and timeouts wait in one queue, in the order they came in, and
cancelling a timeout is exact. An executor that posts to the main
thread would not do: a timeout that ran out while the main thread was
busy would then run before an event that came in earlier.

TouchInteractionMonitorTest runs the monitor with Android's own
controller and keeps the main thread busy during a touch: a swipe stays
a swipe instead of starting touch exploration, a double tap is not
taken for a double tap and hold, touch exploration turned off during a
touch changes when the touch ends, and a finger held still starts touch
exploration after the delay. Registering the monitor with an executor
that posts to the main thread fails two of these tests, and with a
thread of its own, all four.

On an Android 16 emulator, swipes, double taps and touch exploration
work, also after turning the service off and on, and gesture detection
logs come from the main thread.

Limitations: gestures still wait while the main thread is busy, as
before, since Android passes them through it. Following a held touch
still posts each move to the circle menu, all displays still share the
finger-down state, and stopping detection still goes through the
connected displays rather than the registered monitors.

  On Android 13 and later, Backtalk recognizes gestures itself. The touch
  interaction controller passed each motion event to a thread of its own,
  but the gesture matchers and the touch exploration delay counted their
  timeouts on the main thread, so the same state changed on both threads
  without locking:

  - A timeout that completes or cancels a gesture, such as the hold of a
    double tap and hold, could run on the main thread while the gesture
    thread handled the lift that ended it, and both could report a
    gesture.
  - When the touch exploration delay ended, it handed its request to the
    gesture thread, where cancelling the delay no longer stopped it, so a
    swipe that started just then could still start touch exploration.
  - Following a held touch with the circle menu and two-finger rotation
    kept state that either thread changed.
  - Cancelling the phonetic letter hint ran the feedback pipeline on the
    gesture thread, and turning touch exploration off once a touch ended
    changed the service info there.
  - Each restart of gesture detection started a new thread without
    stopping the old one.

  Android passes motion events and touch state changes to the service
  through its main thread before the controller hands them on, so the
  gesture thread never got them sooner. The controller now calls the
  monitor on the main thread as each event comes in, with no executor,
  and the monitor no longer hands requests to another thread; the
  requestStateChangeInSameThread flag, which only chose that, is gone.
  Events and timeouts wait in one queue, in the order they came in, and
  cancelling a timeout is exact. An executor that posts to the main
  thread would not do: a timeout that ran out while the main thread was
  busy would then run before an event that came in earlier.

  TouchInteractionMonitorTest runs the monitor with Android's own
  controller and keeps the main thread busy during a touch: a swipe stays
  a swipe instead of starting touch exploration, a double tap is not
  taken for a double tap and hold, touch exploration turned off during a
  touch changes when the touch ends, and a finger held still starts touch
  exploration after the delay. Registering the monitor with an executor
  that posts to the main thread fails two of these tests, and with a
  thread of its own, all four.

  On an Android 16 emulator, swipes, double taps and touch exploration
  work, also after turning the service off and on, and gesture detection
  logs come from the main thread.

  Limitations: gestures still wait while the main thread is busy, as
  before, since Android passes them through it. Following a held touch
  still posts each move to the circle menu, all displays still share the
  finger-down state, and stopping detection still goes through the
  connected displays rather than the registered monitors.
@trypsynth
trypsynth merged commit c8a43e1 into trypsynth:master Oct 10, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants