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
Charging: Add HyperOS 3 Battery Protection support for qualified Xiaomi devices (#64)
* Charging: Record HyperOS 3 both-direction enforcement evidence in the ledger
The tanzanite contributor (issue #48) ran the requested protocol via
on-device adb shell (shell UID, same path as the Shizuku service):
external write of mode 2 below the cap activated Battery Protection
with the Settings UI following, the device held 80% for ~20 minutes
under active use, and writing 0 mid-hold resumed charging past 80.
This answers the daemon-enforcement-of-external-writes question for
tanzanite mode 2. No current reading (sysfs denied) and no hardware
hold signal in dumpsys, so a future adapter is readback-only.
Support still needs gate design (mode 2 is not HyperOS-3-wide),
the reviewed write-domain widening, absent-key semantics, and a
test-build qualification run.
* Charging: Add HyperOS 3 Battery Protection support for qualified Xiaomi devices
New XiaomiHyperOs3ChargingAdapter (xiaomi-hyperos3-v1) over the same
secure key as HyperOS 2, adding mode 2 = Battery protection as
FixedLimit(80). Gate: manufacturer + ro.mi.os.version.code == 3 + a
qualified-codename allowlist (ships with tanzanite) + system user.
Version-only gating is impossible: mode 2 is model-/build-dependent
within HyperOS 3 (marblein on 3.0.2 lacks it) and cannot be probed at
runtime (key absent in factory state). SYNC_READBACK only; dumpsys
battery exposes no hardware hold signal on HyperOS 3.
Both-direction hardware enforcement of external shell-UID writes was
demonstrated on tanzanite (issue #48): write 2 below the cap holds at
80%, write 0 mid-hold resumes past 80, Settings UI follows, applied
synchronously.
Boundary: SettingWritePolicy widens the key's write domain to
{0,1,2} globally; accepted on HyperOS 2 because no code path emits 2
there and its decode refuses it (AdapterMutationDomainTest pins the
pairing). ContributionAllowlist discloses 2 as a named OEM mode.
Absent-key decode (absent = Intelligent) mirrors HyperOS 2 but is
unverified on HyperOS 3 — flagged in comments, pending the remote
qualification run before this leaves draft.
* Charging: Add the tanzanite HyperOS 3 ledger row and landing gaps
Records the issue-#48 remote qualification (both-direction external
shell-UID write enforcement) as a Verified-devices row and documents
the GrapheneOS-precedent landing: app-level items (absent-key
semantics, sessions, access tiers, R8) stay open as failing-closed
known gaps, to be verified by the contributor on the next beta.
Copy file name to clipboardExpand all lines: .claude/CLAUDE.md
+5-3Lines changed: 5 additions & 3 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -11,8 +11,10 @@ timeout.
11
11
Several control adapters exist — four OEM adapters plus two custom-ROM adapters (LineageOS, GrapheneOS). **Pixel charging optimization** is capability-gated to Pixel 6a and newer phones on
12
12
Android 15+ when Google's charging-optimization controller is present. **Samsung battery protection** (global
13
13
`protect_battery` keys) is gated to verified One UI generations — One UI 8 multi-mode, and the legacy One UI 4/5
14
-
toggle — on the system user. **Xiaomi charging protection** (secure `security_pc_secure_protect_mode_key`,
15
-
binary Adaptive/Unrestricted) is gated to the HyperOS 2.x ROM (`ro.mi.os.version.code == 2`) on Xiaomi devices.
14
+
toggle — on the system user. **Xiaomi charging protection** (secure `security_pc_secure_protect_mode_key`)
15
+
has two adapters: binary Adaptive/Unrestricted gated to the HyperOS 2.x ROM (`ro.mi.os.version.code == 2`), and a
16
+
HyperOS 3 three-mode variant adding a FixedLimit(80) hard cap — gated to HyperOS 3 **plus a qualified-codename
17
+
allowlist** (mode `2` is not HyperOS-3-wide and cannot be probed at runtime).
Copy file name to clipboardExpand all lines: .claude/rules/privileged-access.md
+20-7Lines changed: 20 additions & 7 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -79,13 +79,26 @@ without a qualified device; record results in the qualification ledger (`device-
79
79
80
80
### Xiaomi
81
81
82
-
Xiaomi control requires **all** of: Xiaomi manufacturer (covers Redmi/POCO, which report Xiaomi as
83
-
manufacturer), `ro.mi.os.version.code == 2` (HyperOS 2.x — the ROM feature is version-scoped, not
84
-
model-scoped), and the system user. Use `ro.mi.os.version.code`, NOT the frozen legacy
85
-
`ro.miui.ui.version.code`. Do not widen to HyperOS 3+ without qualifying a device; record results in the qualification ledger (`device-qualification` skill). The single key is per-user `secure`, applied synchronously. Two deliberate
86
-
assumptions: the feature is treated as present on any HyperOS 2 device (a device lacking it reads the key
87
-
absent → a harmless false claim of control), and daemon-level enforcement of external writes is pending
88
-
long-term observation (see Known gaps below).
82
+
Two live adapters over the same per-user `secure` key (applied synchronously). Use `ro.mi.os.version.code`, NOT
Copy file name to clipboardExpand all lines: .claude/skills/device-qualification/SKILL.md
+34-2Lines changed: 34 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -51,6 +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
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 |
55
56
56
57
## Known gaps
@@ -136,7 +137,8 @@ only after adding a row here. Detailed run narratives live in each adapter's lan
136
137
-**Wireless charging and secondary users**: NOT RUN (gated to system user).
137
138
-**Xiaomi** — adaptive hardware enforcement of external writes unconfirmed; treat the adapter as provisional until
0 commit comments