Charging: Distinguish a configured policy from one that is in effect - #73
Merged
Conversation
ChargeObservation.Verified conflated two claims: that a configured value was read back, and that the charger is acting on it. The dashboard granted its confirmed checkmark for any Verified, so an OEM mode that the ROM has not engaged looked identical to an enforced cap. Physically confirmed on a Xiaomi 13T (aristotle, HyperOS 2, 2026-08-16): with Adaptive written via the shell UID and read back as Verified(Adaptive, SHIZUKU), the phone charged 59% to 100% with no hold at any level. HyperOS engages Intelligent charging only inside a learned overnight window, its BaseChargeProtect_Night daemon keeping average/stddev/count preferences of habitual charging; getNightChargingState returned 0 on all 140 evaluations during the run. The write path is not at fault, external writes drive the daemon identically to native UI taps. ChargePolicy.enforcementIsConditional marks Adaptive as a policy whose timing the OEM decides. ChargeObservation.provesPolicyInEffect() combines it with the backend: hardware evidence settles any policy, a readback settles only unconditional ones. It is deliberately about knowledge rather than safety, so Unrestricted qualifies too, it is in effect exactly as verifiably as a cap. Presentation only. The two sites that decide whether a write landed, the repository's `settled` computation and computeRefreshPending's sync arm, keep clearing on any matching readback; adopting the predicate there would spin every Xiaomi adaptive apply for the full settling window, on that adapter's own protective default. Recovery, sessions, the native-change guard and the reconnect gesture are untouched for the same reason. Pixel now under-reports when the HAL reports an engaged adaptive profile, because refreshLocked prefers a settings Verified over the hardware decode and discards state 5. Under-claiming is the safe direction and matches how the rest of the app fails closed; plumbing live hardware state through would flip the card between states through a single night. The HyperOS 2 and 3 adapters no longer share a readiness string, so HyperOS 3 keeps the stronger hardware claim its qualification supports.
d4rken
marked this pull request as ready for review
August 16, 2026 21:44
… fix Closes the ledger's open adapter question: the HyperOS 2 protective default stays Adaptive because the ROM offers nothing unconditional, and the honesty lives in presentation instead. Verified on aristotle with a foss debug build over direct WSS, including that neither policy shows a settling spinner, which is the visible symptom of the predicate leaking into pending logic.
…tional-enforcement
The charge-rules feature added a required callback to DashboardScreen after this branch was cut. No textual conflict, but the merged tree stopped compiling because the new preview passes the callback list explicitly.
…tional-enforcement
The callback arrived on main and was reverted twenty minutes later, so the preview needed the parameter and then needed it gone. Nothing about this branch changed either time.
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
Amply no longer shows a confirmation checkmark for a charge policy it cannot tell is actually working.
Some OEM charging modes only act when the system decides to. Xiaomi's "Intelligent charging" is one: HyperOS learns when you habitually charge overnight and holds at 80% only inside that window. Until now, Amply read the setting back, saw the mode was selected, and presented that as confirmed protection. A Xiaomi 13T with Intelligent charging switched on charged from 59% to 100% untouched while the app showed a green check.
The dashboard now separates "this mode is set" from "this is what the charger is doing". For a mode whose timing the system controls, the card names the mode and says the system chooses when it applies, without the confirmation mark. Fixed limits and unrestricted charging are unaffected, and a mode the charging hardware actively reports as running still shows as confirmed.
The description of adaptive charging now also says it may not hold at other times, which is the part that would have explained a daytime charge to 100% rather than looking like a bug.
Technical Context
ChargeObservation.Verifiedconflated "configured value read back" with "hardware is acting on it", and the hero card's icon keyed off a bareis Verifiedcheck. Evidence: on the 13T,getNightChargingStatereturned0on all 140 evaluations across a full 59→100% charge with the key verified at1. The write path is sound; external shell-UID writes drive the HyperOS daemon identically to native UI taps.enforcementIsConditionalsits onChargePolicy, not onChargingAdapter. Every adapter mapping a mode toAdaptivemaps a learned/OS-scheduled one (Pixel adaptive charging, Xiaomi Intelligent charging, ColorOS Smart charging), andChargeObservationdoes not carry adapter identity, so identical observations would otherwise mean different things.provesPolicyInEffect()is about knowledge, not safety:Unrestrictedreturns true because it is in effect as verifiably as a cap. Naming it around "protection" would have been wrong for exactly that case.applyLocked'ssettledandcomputeRefreshPending's sync-readback arm). Both must keep clearing on any matching readback; adopting the new predicate in either would spin the settling spinner for the full window on every Xiaomi adaptive apply. Recovery, sessions,NativeChangeGuard, and the reconnect gesture are untouched for equivalent reasons. Worth reviewing that the diff contains no such file.refreshLockedprefers a settingsVerifiedand discards the state-5 decode. Fixing it means plumbing live hardware state ontoChargingState, which would flip the card between "active" and "configured" repeatedly through one night. Under-claiming matches how the rest of the app fails closed.