Symptom
Every other tap on the Dictate key does nothing at all — no recording starts, no feedback, nothing in the log. Pressing again works. It affects the mic state and the send state equally, so a single dictation costs four taps instead of two.
It reads as a dead button rather than a slow one: the press is simply gone.
Reproduction
- Open the keyboard with the Dictate action in the Smartbar.
- Tap the Dictate key repeatedly, at a normal pace.
- Roughly every second tap does nothing.
Observed on a Pixel 8 Pro (Android 16), v5.3.0 built from source. Not sensitive to press duration or position — the dead taps land well inside the key.
What the log shows
Dead taps emit sendDown followed by sendCancel 12–27 ms later; working taps emit sendDown → sendUp. The pattern alternates cleanly:
50.871 sendDown -214 → 50.930 sendUp ✅
52.193 sendDown -214 → 52.209 sendCancel ❌ (16ms)
54.064 sendDown -214 → 54.136 sendUp ✅
56.285 sendDown -214 → 56.303 sendCancel ❌ (18ms)
12–27 ms is far too fast to be a finger lifting, and QuickActionButton never leaves composition (I instrumented the DisposableEffect(action, isEnabled) — it does not fire for -214 around these taps). So the cancellation comes from the gesture itself: waitForUpOrCancellation() returns null.
Instrumenting that return shows the event responsible:
cancel@MAIN type=Release n=1 id=7 pressed=false consumed=true pos=Offset(6.0, 14.8)
The release — the finger genuinely lifting on the key — arrives already consumed.
Root cause
changedToUp() is defined as !isConsumed && previousPressed && !pressed. A consumed release therefore isn't recognised as an "up" at all, so waitForUpOrCancellation() falls through to its cancellation path and a completed tap is discarded.
Both gesture branches in QuickActionButton are affected — the long-press-aware one the Dictate key takes when idle, and the plain else branch it takes in its send state — which is why the mic and send buttons fail identically.
What I could not determine: what consumes the release. I fixed it at the button, but you may prefer to fix it at the consumer, which is why this is an issue rather than a PR.
Fix I'm running
A drop-in replacement for waitForUpOrCancellation() that
- detects the lift with
changedToUpIgnoreConsumed(), so a consumed release still counts as a tap, and
- cancels only when a change is consumed while still pressed — the genuine "another handler took the drag" case.
It also drops the isOutOfBounds cancellation, so a thumb that rolls a few pixels doesn't cost a tap. Used by both branches.
Verified on device: 6 presses → 6 sendDown, 6 sendUp, 0 sendCancel, where the same test previously alternated.
Happy to open a PR if the approach looks right to you — and equally happy to test a fix at the consumer end instead, since that may be the more correct layer.
Symptom
Every other tap on the Dictate key does nothing at all — no recording starts, no feedback, nothing in the log. Pressing again works. It affects the mic state and the send state equally, so a single dictation costs four taps instead of two.
It reads as a dead button rather than a slow one: the press is simply gone.
Reproduction
Observed on a Pixel 8 Pro (Android 16), v5.3.0 built from source. Not sensitive to press duration or position — the dead taps land well inside the key.
What the log shows
Dead taps emit
sendDownfollowed bysendCancel12–27 ms later; working taps emitsendDown→sendUp. The pattern alternates cleanly:12–27 ms is far too fast to be a finger lifting, and
QuickActionButtonnever leaves composition (I instrumented theDisposableEffect(action, isEnabled)— it does not fire for-214around these taps). So the cancellation comes from the gesture itself:waitForUpOrCancellation()returnsnull.Instrumenting that return shows the event responsible:
The release — the finger genuinely lifting on the key — arrives already consumed.
Root cause
changedToUp()is defined as!isConsumed && previousPressed && !pressed. A consumed release therefore isn't recognised as an "up" at all, sowaitForUpOrCancellation()falls through to its cancellation path and a completed tap is discarded.Both gesture branches in
QuickActionButtonare affected — the long-press-aware one the Dictate key takes when idle, and the plainelsebranch it takes in its send state — which is why the mic and send buttons fail identically.What I could not determine: what consumes the release. I fixed it at the button, but you may prefer to fix it at the consumer, which is why this is an issue rather than a PR.
Fix I'm running
A drop-in replacement for
waitForUpOrCancellation()thatchangedToUpIgnoreConsumed(), so a consumed release still counts as a tap, andIt also drops the
isOutOfBoundscancellation, so a thumb that rolls a few pixels doesn't cost a tap. Used by both branches.Verified on device: 6 presses → 6
sendDown, 6sendUp, 0sendCancel, where the same test previously alternated.Happy to open a PR if the approach looks right to you — and equally happy to test a fix at the consumer end instead, since that may be the more correct layer.