Skip to content

FullCharge: Arm the reconnect gesture at the limit without waiting for the battery status - #46

Merged
d4rken merged 1 commit into
mainfrom
worktree-gesture-post-write-arming
Aug 3, 2026
Merged

FullCharge: Arm the reconnect gesture at the limit without waiting for the battery status#46
d4rken merged 1 commit into
mainfrom
worktree-gesture-post-write-arming

Conversation

@d4rken

@d4rken d4rken commented Aug 3, 2026

Copy link
Copy Markdown
Member

What changed

The reconnect gesture no longer goes blind for ~10 seconds after your charge limit is written.

Previously, tapping Restore now and then immediately performing the gesture again did nothing — the phone is physically topping back up right after the limit is re-applied, and the gesture waited for charging to visibly stop before it would arm. On a Pixel 9 Pro that wait was ~10.5 seconds, which reads as the gesture simply being broken.

The gesture now arms as soon as the hardware confirms the limit is active and the battery has reached it. Re-arming after a restore is effectively immediate.

This affected every path that writes the limit while the battery is already near it — restoring a session, boot recovery, and the widget's persistent-policy buttons — not just "Restore now".

Unchanged: an accidental unplug/replug while the battery is still climbing below your limit still does nothing.

Technical Context

Root cause. QuickFullChargeGesture armed only when plugged, chargingStatus == 4, batteryStatus != CHARGING and percent in 75..90 held simultaneously. After a limit write the battery status stays CHARGING for the ~10–12 s Pixel HAL transition, so nothing armed; the unplug edge then went to Idle instead of AwaitingReconnect and the replug had no window to consume.

From the reported logcat:

13:49:07.937  restore applied fixed:80
13:49:07.983  plugged=true percent=80 chargingStatus=4 batteryStatus=2  → IDLE
13:49:10.063  unplug (~2.1s after restore)                              → IDLE
13:49:13.091  replug                                                    → IDLE   (no trigger)

Fix. A second arming path for the same LIMIT_HOLD basis: policy state reported, percent in band, and percent >= verifiedLimitPercent. Reaching the verified limit is what replaces the dropped battery-status check — it is what distinguishes sitting at the limit from climbing through the band. Both paths are kept; neither subsumes the other (drift to 79 % under an 80 % limit is held but not settled; the window after a write is settled but not held).

Why not GestureBasis.evidence(). It falls back to lastPersistentPolicy when the hardware reading is inconclusive, which would arm the default basis off a limit Amply merely remembers writing — the class of bug #28 fixed for the opt-in any-level basis. GestureBasis.limitPercent() has no journal fallback by construction; its KDoc now records that it is an arming input, not just notification copy.

The counterweight (review focus). Accepting a CHARGING reading is not free: the hardware state lags a policy change in both directions. Switching away from an 80 % limit at 80 % briefly still reports the old policy state, which would latch a hold that no longer exists — and the steady-plugged branch has never dropped a latch, so it survived until the battery left the 75–90 % band. setPersistentPolicy() resets the gesture and immediately re-evaluates against exactly that stale reading. The steady-plugged branch now retires a limit-hold basis on positive proof: current flowing while no policy state is reported, from a readable broadcast.

Two guards on that rule, both load-bearing:

  • Steady-plugged only. A replug legitimately reads CHARGING before the policy state is re-reported; dropping there would destroy the carried basis the reconnect window exists to preserve.
  • percent >= 0. An unreadable percent marks the whole broadcast read as failed, so its charging status proves nothing either. Caught by the pre-existing an unreadable percent does not retire a limit-hold basis test.

Verification. 795 unit tests pass on both flavors; the gesture suite went 43 → 45 cases, covering the logged regression, the climbing case, out-of-band, unverified limit, adaptive, drift, the stale-policy-state transition, and the replug carry-over.

Device-verified on two devices.

Pixel 9 Pro (caiman, Android 16) — the reported scenario end to end:

16:38:13.270  replug                     → TRIGGER   session 1
16:38:15.987  RESTORE_CHARGE_LIMIT
16:38:16.095  batteryStatus=2 chStatus=4 → ARMED     (108ms, was ~10.5s)
16:38:19.353  unplug                     → WAITING_FOR_RECONNECT
16:38:23.093  replug                     → TRIGGER   session 2

The negative case was checked with a simulated level: from a cleared latch, 76 % still charging under an 80 % limit stays IDLE.

Pixel 8 (shiba, Android 17 / API 37) — covers the drop rule, which the 9 Pro run predates. Switching the persistent policy to Adaptive reproduced the whole mechanism on hardware:

18:41:46.763  Applied adaptive (persistent)
18:41:57.350  batteryStatus=4 chStatus=1 → ARMED   stale latch, no current yet: correctly kept
18:42:08.488  batteryStatus=2 chStatus=1 → IDLE    current flows, no policy state: latch dropped

Without the rule that latch would have survived until the battery left the 75–90 % band. The basic gesture still triggers normally there (unplug → replug at Δ3.58 s → TRIGGER), and after a restore the gesture stayed armed for over a minute while batteryStatus was still CHARGING — armed solely via the new path.

Which of the two signals lags varies per transition rather than per device: when the battery status settles first, the settled path gives no advantage, because the verified limit percent is itself derived from the policy state. The fix removes the wait whenever the policy state arrives first, and never arms on anything weaker.

…r the battery status

The gesture armed only when the charging-policy hardware state, a
non-CHARGING battery status and an in-band percent held at once. After
any write of the limit the phone is physically topping back up, so the
battery status stays CHARGING for the ~10-12s Pixel HAL transition and
nothing armed. An unplug in that window opened no reconnect window and
the replug had nothing to consume, so a gesture right after "Restore
now" silently did nothing. Every path that writes the limit while
already in the band was affected - session restore, boot recovery, the
widget's persistent-policy buttons - not just restore.

Arming now also accepts a second path: the policy state is reported and
the battery has already reached a *verified* limit percent. Reaching the
limit replaces the dropped battery status as the signal that separates
sitting at the limit from climbing through the band, so a replug at 76%
under an 80% limit still arms nothing. The percent is sourced from
GestureBasis.limitPercent(), which answers only from a verified
readback; GestureBasis.evidence() is deliberately not used, as its
journal fallback would arm the default basis off a limit Amply merely
remembers writing.

Accepting a CHARGING reading needs a counterweight, because the hardware
state lags a policy change in both directions: leaving an 80% limit at
80% briefly still reports the old policy state, which would latch a hold
that no longer exists and survive - the steady-plugged branch has never
dropped a latch - until the battery left the band. That branch now
retires a limit-hold basis on positive proof the limit is not holding:
current flowing while no policy state is reported, from a readable
broadcast. It is confined to the steady-plugged branch because a replug
legitimately reads CHARGING before the policy state is re-reported, and
dropping there would destroy the carried basis the reconnect window
exists to preserve.

Verified on a Pixel 9 Pro: re-arming after a restore went from ~10.5s to
108ms, and the full replay (gesture, Restore now, gesture again within
the window) starts a second session where it previously did nothing. The
climbing case (76% under an 80% limit) stays idle.
@github-actions github-actions Bot added the enhancement New feature or request label Aug 3, 2026
@d4rken d4rken added bug Something isn't working ROM: Pixel Google Pixel and removed enhancement New feature or request labels Aug 3, 2026
@d4rken
d4rken marked this pull request as ready for review August 3, 2026 16:28
@github-actions github-actions Bot added enhancement New feature or request and removed bug Something isn't working labels Aug 3, 2026
@d4rken d4rken added bug Something isn't working and removed enhancement New feature or request labels Aug 3, 2026
@d4rken
d4rken merged commit f291f69 into main Aug 3, 2026
13 checks passed
@d4rken
d4rken deleted the worktree-gesture-post-write-arming branch August 3, 2026 17:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working ROM: Pixel Google Pixel

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant