You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
FullCharge: Cancel a session only on a real native settings change (#65)
The session service's native-change observer cancelled without
restoring on ANY notification for the adapter's setting URIs.
Notifications are dispatched asynchronously, so the session's own
override write can arrive after the observer registers, and an OEM
provider can notify without a value change — both observed on
HyperOS 3 tanzanite (issue #48), where the session ended mid-charge
and the protective policy was never restored.
NativeChangeGuard now verifies the change where the configuration is
synchronously readable: a readback still decoding to the session's
override policy is noise and keeps the session; a different, foreign,
or unreadable value cancels as before. Adapters without sync readback
(Pixel) keep the blanket cancel. A Shizuku readback timeout counts as
unreadable instead of silently dropping the notification.
Also records the tanzanite beta verification results in the
qualification ledger: absent key decodes as Intelligent (confirmed),
app-context three-mode control works over direct WSS, and the mode-2
cap is fixed at 80%.
Copy file name to clipboardExpand all lines: .claude/skills/device-qualification/SKILL.md
+24-10Lines changed: 24 additions & 10 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -51,7 +51,7 @@ only after adding a row here. Detailed run narratives live in each adapter's lan
51
51
| Xiaomi | Xiaomi 13T `2306EPN60G` HyperOS 2.0 (`ro.mi.os.version.code=2`) |**Partial** — mapping/readback/session verified; the adaptive 80% hold could not be triggered, so daemon-level hardware enforcement is **not yet demonstrated**| Read matrix, both-direction writes, session at 100%, unknown-value refusal, R8 beta | 2026-07-21 |
52
52
| OnePlus (Oplus) | OnePlus Nord CE4 Lite `CPH2621` ColorOS 15 (`ro.build.version.oplusrom=V15.0.0`) | Full — enforcement directly observable (device holds at 80%); external writes stick | Two mutually-exclusive `system` keys (Charging limit / Smart charging), WSS-only write rejected + Shizuku write succeeds for all three policies, WSS-only UX (controls disabled + Shizuku-required banner) | 2026-07-21 |
53
53
| GrapheneOS | Pixel 9 Pro XL `komodo`, GrapheneOS 2026080501 / Android 17 — **REMOTE qualification via issue #49** (tester-run protocol, not maintainer hardware) |**Enforcement observed**: held at 80% with shield, `dumpsys battery` status=4/Charging state=4/policy=2 (limit on) vs 2/1/1 (off); shell-UID writes move the Settings UI live, **latch at plug-session start** — mid-session writes have no hardware effect until unplug→replug, replug reliably applies the current value | Key isolation (`settings list` diff → single `global battery_charge_limit` 0/1), write→UI both directions, mid-session no-op both directions, replug latch both directions, hardware signal both states. **NOT run**: app-context access tiers (WSS write from Amply, `app.grapheneos.*` package visibility), sessions/boot recovery, wireless, factory-absent key state, secondary user | 2026-08-12 |
54
-
| Xiaomi (HyperOS 3) | Redmi Note 14 `24117RN76G` (`tanzanite`), HyperOS 3.0.302 / Android 16 (`ro.mi.os.version.code=3`) — **REMOTE qualification via issue #48** (contributor-run protocol, not maintainer hardware) | **Both-direction enforcement of EXTERNAL shell-UID writes observed** (the same write path as Amply's Shizuku service): `settings put … 2` below the cap → Battery protection active, Settings UI follows immediately, held at 80% for ~20 min under active use (voltage 4228 mV holding vs 4391 mV charging, charge counter 4283 vs 4341 corroborate; sysfs `current_now` permission-denied, so no current reading); `settings put … 0` mid-hold → charging resumes past 80 immediately. **No hardware hold signal**: `dumpsys battery` reports `status: 2` / `Charging state: 0` / `Charging policy: 0` in both states → read-back-only verification | Key mapping (three modes incl. `2` = Battery protection @80), external write → UI both directions, sustained hold, mid-hold release. **NOT run** (GrapheneOS-precedent landing; verify on the next beta via issue #48): app-context access tiers, sessions/boot recovery, factory-absent key state, wireless, R8 | 2026-08-14 |
54
+
| Xiaomi (HyperOS 3) | Redmi Note 14 `24117RN76G` (`tanzanite`), HyperOS 3.0.302 / Android 16 (`ro.mi.os.version.code=3`) — **REMOTE qualification via issue #48** (contributor-run protocol, not maintainer hardware) | **Both-direction enforcement of EXTERNAL shell-UID writes observed** (the same write path as Amply's Shizuku service): `settings put … 2` below the cap → Battery protection active, Settings UI follows immediately, held at 80% for ~20 min under active use (voltage 4228 mV holding vs 4391 mV charging, charge counter 4283 vs 4341 corroborate; sysfs `current_now` permission-denied, so no current reading); `settings put … 0` mid-hold → charging resumes past 80 immediately. **No hardware hold signal**: `dumpsys battery` reports `status: 2` / `Charging state: 0` / `Charging policy: 0` in both states → read-back-only verification | Key mapping (three modes incl. `2` = Battery protection @80, cap fixed — no percent picker), external write → UI both directions, sustained hold, mid-hold release. Beta run 2026-08-14 added: app-context three-mode control (direct WSS), factory-absent key = Intelligent (confirmed). Session restore FAILED in that run (observer noise cancel — app bug, fixed; see Known gaps). **NOT run**: Shizuku tier, boot recovery, wireless, R8, session re-verify post-fix | 2026-08-14 |
55
55
| GrapheneOS (follow-up) | Same device, **0.3.2-beta0 on-device report via issue #49**|**Package detection VERIFIED from app context** (`is_grapheneos=true` with the FLAG_SYSTEM check); **unprivileged key read DENIED** — `has_battery_charge_limit=false` while the very same report showed `battery_charging_status=4` (limit enforcing). Root cause in GrapheneOS source: the key is `@Protected(read = SYSTEM_UI, readWrite = SETTINGS)` (frameworks_base `c30c6393`); SettingsProvider throws SecurityException for all other packages **including WSS holders**, with the shell UID explicitly exempt ("ADB is used for testing", `e87c93a2`) — so the tester's earlier adb runs ARE the Shizuku-path evidence. Factory-absent semantics resolved from source: `BoolSetting(..., default false)` → absent = off | Detection + fail-closed probe verified live; adapter re-gated to Shizuku-only in response | 2026-08-13 |
56
56
57
57
## Known gaps
@@ -190,15 +190,29 @@ only after adding a row here. Detailed run narratives live in each adapter's lan
190
190
contributor was asked whether the settings screen shows any third mode / 80% option.
191
191
-**LANDED 2026-08-14: `xiaomi-hyperos3-v1`, gated to a qualified-codename allowlist (`tanzanite` only) —
192
192
GrapheneOS-precedent landing** (remote enforcement qualification via issue #48, see the Verified devices
193
-
row; app-level items open, all failing closed, to be verified by the contributor on the next beta):
194
-
-**Absent-key decode unverified**: absent decodes as Intelligent/Adaptive, mirroring HyperOS 2's
195
-
factory-state assumption; HyperOS 3 factory semantics are unknown. Worst case if wrong: a session
196
-
restore writes `1` onto a factory state that was not Intelligent — bounded, no battery hazard.
197
-
Next-beta ask: `settings delete secure security_pc_secure_protect_mode_key`, then `settings get` +
198
-
observe which mode the native UI shows.
199
-
-**Sessions / boot recovery / access tiers (WSS-only vs Shizuku) / R8**: NOT RUN on HyperOS 3;
200
-
the mechanism is the shared session engine + the same `secure`-namespace write path qualified on
201
-
HyperOS 2, but on-device confirmation is pending the next beta.
193
+
row). **First on-device verification run (2026-08-14, issue #48; contributor-run, presumably on
194
+
v0.3.3-beta0 — the first release carrying the adapter):**
195
+
-**Absent-key decode CONFIRMED**: `settings delete` → `settings get` returns `null`, and both the native
196
+
battery settings and Amply fall back to Intelligent charging. Absent = Intelligent is the real factory
197
+
semantic; the shipped decode is correct.
198
+
-**App-context writes + three-mode switching CONFIRMED**: switching all three modes from inside Amply is
199
+
mirrored by the system settings immediately and vice versa, with the key value following each change
200
+
(contributor polled `settings get` per switch). Access tier in the run: direct WSS (dashboard showed
201
+
"Read back through direct wss"); the Shizuku tier remains unexercised on this device.
202
+
-**Temporary session FAILED to restore — real app bug, adapter-independent, FIXED post-run**: the
203
+
session's native-change observer received a settings notification carrying no value change (the
204
+
session's own override write delivered late by async dispatch, or a HyperOS spurious notification) and
205
+
cancelled the session without restoring; the device charged to 100% with the protective policy never
206
+
re-written. Root-caused from the run's artifacts — the contributor's 22:22 screenshot shows the
207
+
dashboard already idle at 96% while their 2s key monitor shows no value change after the 21:59:08
208
+
override write. Fixed by `NativeChangeGuard` (readback-verified cancellation, see
209
+
`rules/architecture.md`); **session-lifecycle re-verification is the headline ask for the next beta**.
210
+
Boot recovery and R8 smoke remain NOT RUN.
211
+
-**Cap is fixed at 80%** (no percent picker in the Battery protection screen), and mode `2`'s own
212
+
description says the device will "charge fully only when scheduled" — HyperOS reserves an OEM-side
213
+
scheduled full charge while in Battery protection. No code impact (verification is settings readback;
214
+
sessions override with `0`), but a future "charged to 100% while protected" report may be this OEM
215
+
behavior rather than a bug.
202
216
-**Adaptive (mode `1`) enforcement undemonstrated** — identical provisional status as HyperOS 2 (the
203
217
top-level Xiaomi gap above); only mode `2` has demonstrated hardware enforcement.
204
218
- The gate cannot widen past the codename allowlist: record any new HyperOS 3 device here plus a
0 commit comments