Repository navigation
Description
Symptom. After one gesture-pinch or gesture-rotate on a physical Android 14+ phone, the phone ignores every later touch from Argent, even after restarting the app. Only adb reboot recovers it. Seen on a Samsung Galaxy A16 (SM-A165F, Android 16) with argent 0.30.1. Android emulators are not affected because they inject through the virtual touchscreen.
Cause. The android_device controller in simulator-server ends a two-finger gesture with a single ACTION_UP that still carries both pointers. Android requires a two-pointer gesture to end with ACTION_POINTER_UP for the leaving finger (action index bits set, both pointers present), followed by a one-pointer ACTION_UP. The input dispatcher's verifier rejects the malformed event, so the injected pointers stay down, and every subsequent ACTION_DOWN is refused as "pointers already down".
The start of the gesture is already encoded correctly (ACTION_DOWN, then ACTION_POINTER_DOWN(1), then two-pointer ACTION_MOVEs). There is also no way to lift a single finger from the tool-server side: a touch command with type: "Up" or "Move" and no second point becomes a one-pointer ACTION_UP or ACTION_MOVE, which is rejected the same way. ACTION_POINTER_UP is never emitted, so this cannot be worked around in gesture-pinch / gesture-rotate / gesture-custom.
Reproduce.
- Connect an Android 14+ phone over USB (a Samsung on Android 16 is a reliable choice) and note its serial.
- Run one pinch:
gesture-pinch { "udid": "<serial>", "centerX": 0.5, "centerY": 0.5, "startDistance": 0.2, "endDistance": 0.6 } - Check the dispatcher state. Both pointers are still down:
adb shell dumpsys input | grep -A6 TouchStatesByDisplay # DeviceId(-1):[touchingPointers=[Pointer(id=0, FINGER), Pointer(id=1, FINGER)], ... - The agent's own log shows the rejected event:
adb logcat -d | grep -E "Unable to inject|Inconsistent event" # E/studio.screen.sharing: Unable to inject an input event MotionEvent { action=ACTION_UP, ... id[0]=0, ... id[1]=1, ... pointerCount=2 - Any
gesture-tapnow fails withInconsistent event: ... action=DOWN ... reason: Invalid DOWN event - pointers already down.
To see every injected event, not just the rejected ones, enable the dispatcher's inbound log and reboot once before step 2:
adb shell setprop persist.log.tag.InputDispatcherInboundEvent DEBUG && adb reboot
adb logcat -d | grep injectInputEvent
Fix. On a touch command with type: "Up" and a second point, send two events: ACTION_POINTER_UP with action index 1 and both pointers at their last positions, then ACTION_UP with pointer 0 alone, and reset the second-pointer bookkeeping afterwards. It would also be worth handling a Move or Up that drops the second point the same way, so a custom gesture can lift one finger at a time.
Also seen while reproducing. The very first touch command after the screen-sharing agent starts on a phone is silently dropped: the tool returns success but the dispatcher logs no injected events. It looks like events are sent before the agent is ready to inject. Probably worth a separate issue.
Activity
camchis commentedon Oct 28, 2026
Reproduced on a second device, and a few details that may help with the fix.
Device: Solana Seeker (Android 16, SDK 36, Mali), Argent 0.31.0, USB. The report was on a Samsung A16 with 0.30.1, so this is not Samsung-specific.
Repro: one
gesture-pinch(centre 0.5/0.5, 0.1 to 0.5, default duration). The tool returnspinched: true, anddumpsys inputthen shows both pointers still down on the app window and on the gesture monitors:What it does to later input:
gesture-tapreturnstapped: truebut is dropped. The app stayed on the same screen.argent server stop, so it belongs to the dispatcher and not to a live agent process.Why shell input cannot clear it:
adb shell input motionevent CANCELandUPinject asdeviceId=4, while the stuck pointers belong to the agent's virtual device. The dispatcher refuses both:So nothing outside the agent's own injector can recover it, which matches the reporter's finding that only a reboot does.
I did not reproduce the first-touch-dropped bug (#1627 / #1646) in this order: the first tap landed, but a screenshot had already started the screen-sharing agent. Possibly relevant to those issues.
One untested thought for the fix: besides ending a two-finger gesture with
ACTION_POINTER_UPthenACTION_UP, anACTION_CANCELsent from the same virtual device would be a recovery path for a session that is already stuck. I have not tried it, since I cannot inject from that device.feat(ios-device): pinch, rotate and multi-finger paths on a physical …