Charging: Warn when the hardware never confirms a fixed limit - #60
Merged
Conversation
An ASYNC_HARDWARE settling window that expired without a hardware confirmation was indistinguishable from success: the spinner vanished and the dashboard read like the change applied. That silence hid the silent-failure class where the configured setting reads back fine while charging is never actually limited (the Settings Intelligence worker not running, a HAL ignoring the key). Adapters now declare when a hardware confirmation is EXPECTED (confirmationExpected): Pixel expects one only for a plugged fixed limit on a live, unmasked channel - state 4 spans the entire plugged fixed-limit session, while adaptive idles at the ambiguous state 1 and unrestricted maps to the same 1, and thermal states mask the signal. A pure detector (computeUnconfirmedTarget) surfaces the standing contradiction as ChargingState.unconfirmedTarget once a request is 30s old (2x the settling window) and the expectation is still unmet; an authoritative readback of a DIFFERENT configured policy - a native or competing change - obsoletes the request instead of warning about it. A 15s stability debounce (debounceUnconfirmed, carried across refreshes in the repository) damps the plug-in transient, where an old fixed-limit request legitimately reads state 1 for the ~12s HAL transition. The settle scheduler enqueues a second one-shot refresh at the threshold so the warning appears even when the failing HAL produces no broadcast of its own. The signal deliberately lives outside PendingRequest (mutating pending at expiry would loop the dashboard's deadline observer). Every apply publication - success, cancellation, metadata failure, write failure, and the unsupported/needs-setup refusals - clears it, so a stale warning can never sit over a new request or a state the detector's own contract excludes. Sync-readback and plug-latched adapters carry no expectation and never warn. The dashboard renders the contradiction as an error-tinted warning line; single sticky battery readout per refresh feeds the decode, the pending computation, and the expectation check.
d4rken
force-pushed
the
feat/hw-unconfirmed-warning
branch
from
August 14, 2026 04:12
1ebb25f to
278fed3
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What changed
When a fixed charge limit is applied but the charging hardware never confirms it, the dashboard now shows an explicit warning ("the charging hardware hasn't confirmed the 80% limit yet — it may not be applying") instead of silently looking successful. Previously the "Applying…" spinner simply disappeared after 15 seconds whether or not the limit actually engaged — hiding exactly the failure class where the setting reads back fine while charging is never limited.
Technical Context
confirmationExpected): Pixel only for a plugged fixed limit on a live, unmasked channel — state 4 spans the entire plugged fixed-limit session, while Adaptive idles at the ambiguous state 1 and Unrestricted maps to the same 1, so their absence proves nothing and they never warn. Sync-readback and plug-latched (GrapheneOS) adapters carry no expectation structurally; GrapheneOS's post-replug case staying silent is deliberate scope.ChargingState, notPendingRequest— mutating pending at expiry would infinite-loop the dashboard's settling-deadline observer. Deserving close review:computeUnconfirmedTarget+debounceUnconfirmedand their test matrices, and the clears in every apply publication path.