-
Notifications
You must be signed in to change notification settings - Fork 0
Multi Pointer MotionEvent
This page documents the correct Android multi-touch semantics implemented in v3, which fixed the analog returns to center when any other button is pressed bug.
Before v3, TouchDaemonService.injectSinglePointer() always created a MotionEvent with pointerCount=1 and ACTION_DOWN for every pointer transition. This is wrong for multi-touch. When pointer 0 (L_STICK) is already DOWN and pointer 2 (button A) injects ACTION_DOWN with pointerCount=1, Android interprets this as a NEW touch session — the previous session (stick) gets CANCELLED.
| Action | When to use | pointerCount |
|---|---|---|
| ACTION_DOWN | First pointer goes DOWN (starts a new gesture) | 1 |
| ACTION_POINTER_DOWN | Additional pointer goes DOWN while others active | ALL active pointers |
| ACTION_MOVE | Any pointer moves | ALL active pointers |
| ACTION_POINTER_UP | Pointer goes UP while others remain | ALL active pointers |
| ACTION_UP | Last pointer goes UP (ends the gesture) | 1 |
The actionIndex (encoded in the upper bits of the action integer via ACTION_POINTER_INDEX_SHIFT) specifies WHICH pointer in the properties array is being added/removed.
- activePointers: ConcurrentHashMap tracking ALL currently-down pointers (IDs + last known positions)
- gestureDownTime: set when the FIRST pointer goes DOWN, shared by ALL pointers in the same gesture
- Add pointer to activePointers map
- If this is the first pointer, set gestureDownTime
- Call injectMultiPointerEvent with ACTION_DOWN
- Snapshot ALL active pointers (sorted by ID)
- Find actionIndex (position of target pointer in array)
- Determine finalAction:
- If pointerCount == 1: ACTION_DOWN or ACTION_UP
- Else: ACTION_POINTER_DOWN or ACTION_POINTER_UP with actionIndex encoded
- Build properties + coords for ALL active pointers
- Create MotionEvent with full pointer set
- Try Path A, then B, then (shell only if single-pointer DOWN/UP)
Before v3 (single-pointer ACTION_DOWN):
- L_STICK DOWN: ACTION_DOWN, pointerCount=1, pointer=0. Android starts new session.
- Button A DOWN: ACTION_DOWN, pointerCount=1, pointer=2. Android cancels previous session, starts new one. L_STICK touch released.
After v3 (multi-pointer ACTION_POINTER_DOWN):
- L_STICK DOWN: ACTION_DOWN, pointerCount=1, pointer=0. Android starts new session.
- Button A DOWN: ACTION_POINTER_DOWN, pointerCount=2, pointers=[0,2]. Android adds pointer 2 to existing session. L_STICK touch continues.
- Button A UP: ACTION_POINTER_UP, pointerCount=2, actionIndex=1. Android removes pointer 2. L_STICK still active.
Path C (input tap shell command) only supports single-pointer DOWN+UP. If it fires while other pointers are active, it would inject pointer 0 DOWN+UP, hijacking/cancelling the active multi-touch session.
v3 fix: Path C only fires when pointerCount == 1 (no other pointers active).
- Injection-Architecture — 3-path injection overview
- Dual-AIDL-Dispatch-Thread — Why stick and button use separate threads