Skip to content

Make the idle→LOW_POWER throttle reachable so #477 can be validated - #1035

Open
ryanbr wants to merge 2 commits into
mainfrom
feat/idle-throttle-reachable
Open

Make the idle→LOW_POWER throttle reachable so #477 can be validated#1035
ryanbr wants to merge 2 commits into
mainfrom
feat/idle-throttle-reachable

Conversation

@ryanbr

@ryanbr ryanbr commented Aug 2, 2026

Copy link
Copy Markdown
Owner

The minimal wiring discussed on #1005. Three files, no UI, no new strings.

Why

#477's risky half — drop an idle link to LOW_POWER, the long interval that is the real all-day saving — shipped dormant and stayed that way. AppViewModel.applyPowerSaving passed a hard-coded 0, and 0 disables the gate, so nobody could arm it however they configured the app. Its own validation plan says it "must be validated on a real strap"; that was impossible.

Same catch-22 #1027 broke for the offload: the thing could not be validated because it could not be reached.

What changes

A pref, clamped, read where the 0 used to be. Default 0, so behaviour is byte-identical for anyone who does not deliberately set it. setConnectionPriorityManagement already restores BALANCED when the value returns to 0 — that edge was handled in #533 — so it is reversible without waiting for a reconnect.

No Settings control, deliberately

The throttle has two preconditions that would be invisible in a Settings row:

  1. It needs Fast history sync on as well, because refreshConnectionPriority() early-returns without connection-priority management.
  2. It keys on the strap's battery, so a healthy strap never trips it however low the phone is.

A row that silently does nothing in either case generates "this setting is broken" reports rather than the field data #477 wants. A Test Centre control is a small follow-up if validators would rather not use adb; it would call NoopPrefs.setIdleThrottleBatteryPct (which applies the write clamp) and then applyPowerSaving(), with no BLE-layer change. I first added a passthrough on AppViewModel for that and removed it on re-review — nothing called it, so the relaunch it claimed to avoid was not actually avoided.

Fails closed

Clamped to 0 or 10..30 on read. With no UI the only way to set it is out-of-band on a debug build, which is exactly where a typo'd 95 would arm the throttle at essentially all times rather than at the low-battery edge. Anything out of range reads as OFF, because a link that keeps dropping is the worse failure. Five tests cover it.

What this does NOT do

It does not address #1005, and should not be read as doing so. That is a phone-battery report; this keys on the strap's. The lever a phone-drain user can already reach is the Power saving master, which stretches offload cadence from the phone's battery — a point I missed on that issue and have corrected in #1034.

ryanbr added 2 commits August 1, 2026 21:52
The risky half of #477 shipped dormant and stayed that way: AppViewModel passed
a hard-coded 0 for idleThrottleBatteryPct, and 0 disables the gate, so no user
or tester could arm it however they configured the app. Its own validation plan
needs it running on a real strap, which nobody could do.

Reads a pref instead. Default 0, so behaviour is unchanged for anyone who has
not deliberately set it - and setConnectionPriorityManagement already restores
BALANCED when the value returns to 0, so it is reversible without a reconnect.

No Settings control on purpose. The throttle has two preconditions that would be
invisible in a Settings row: it needs Fast history sync on as well, because
refreshConnectionPriority early-returns without connection-priority management,
and it keys on the STRAP's battery, so a healthy strap never trips it however
low the phone is. Shipping a row that silently does nothing in both those cases
would generate confusion rather than the field reports #477 wants.

The value is clamped to 0 or 10..30 on read, failing CLOSED. With no UI the only
way to set it is out-of-band on a debug build, which is exactly where a typo'd
95 would arm the throttle at essentially all times instead of at the low-battery
edge; a link that keeps dropping is the worse failure.

Does NOT address #1005, and should not be read as doing so: that is a phone
battery report and this keys on the strap's. The lever a phone-drain user can
already reach is the Power saving master, which stretches offload cadence from
the phone's battery.
Re-reading my own change: AppViewModel.setIdleThrottleBatteryPct had no callers,
and the reason I gave for it - 'so validating #477 does not require a relaunch' -
was simply false, because nothing invokes it. Validation still means setting the
pref out-of-band and relaunching, which applyPowerSaving already handles at
launch. Speculative generality in a change whose whole brief was minimal.

The NoopPrefs setter stays: it is where the WRITE clamp lives, so a later control
has a correct path instead of reaching for edit().putInt() and bypassing it. 61
of the 69 keys in that file carry the same getter/setter pair, so it is also the
local convention.

Leaves seven executable lines: the key, a clamped getter, a clamped setter, the
pure clamp, and the one call site that no longer passes a hard-coded 0.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant