Skip to content

Charging: Gate GrapheneOS charge control on Shizuku - #62

Merged
d4rken merged 1 commit into
mainfrom
fix/grapheneos-shizuku-gate
Aug 14, 2026
Merged

Charging: Gate GrapheneOS charge control on Shizuku#62
d4rken merged 1 commit into
mainfrom
fix/grapheneos-shizuku-gate

Conversation

@d4rken

@d4rken d4rken commented Aug 14, 2026

Copy link
Copy Markdown
Member

What changed

GrapheneOS charge control now requires Shizuku. The first beta on a real device (#49) showed the app detecting GrapheneOS but refusing control with "charge limit setting is not present" — the setting exists, but GrapheneOS deliberately blocks all third-party access to it, including apps holding WRITE_SECURE_SETTINGS. Only the shell (which Shizuku provides) is allowed through. With Shizuku connected, everything works as designed: switching the limit, the one-time full charge with the unplug/replug step, and exact state readback. Without Shizuku the app now shows the same "Shizuku required" guidance used on OnePlus/ColorOS devices instead of a wrong "setting not present" message. A fresh install that never touched the native toggle is now also handled (previously it would have refused sessions).

Technical Context

  • Root cause with receipts: GrapheneOS marks the key @Protected(read = SYSTEM_UI, readWrite = SETTINGS) (frameworks_base c30c6393); their SettingsProvider throws SecurityException for every other package after the WSS permission check, with the shell UID explicitly exempt ("ADB is used for testing", e87c93a2). The tester's earlier successful adb runs were unknowingly the Shizuku-path qualification.
  • The key-presence gate had to go, not just weaken: @Protected denies the unprivileged probe whether the key exists or not, so presence carries zero information from app context. Presence is now assumed on GrapheneOS (the Xiaomi-precedent assumption; accepted failure mode is a harmless false claim of configured control on a hypothetical build without the feature — the row would still be writable, but nothing enforces and the hardware decode stays honest).
  • Absent-key decode flipped from refuse to Unrestricted because upstream source resolves it: GrapheneOS reads the key via BoolSetting(..., default false), so absent is the factory off state.
  • No read-path code change: readSyncDirectFirst already treats the blocked direct read as non-authoritative and falls through to the Shizuku backend. Deserves close review: the probe/gate change in GrapheneOsChargingAdapter and the shared dashboard_shizuku_required_body copy fix (its "can still show the current state" claim was Oplus-only truth).
  • On-device evidence from the beta is recorded in the qualification ledger: package detection verified from app context, unprivileged access denied as designed (fail closed). The Shizuku path end-to-end on real hardware awaits the next beta report in [Device Support] GrapheneOS (Pixel 9 Pro XL) #49, which CI cannot cover.

The 0.3.2-beta0 on-device report (issue #49) disproved the landing
assumption that global battery_charge_limit is world-readable and
WSS-writable: the probe read absent while the same report showed the
limit enforcing (charging state 4). GrapheneOS declares the key
@Protected(read = SYSTEM_UI, readWrite = SETTINGS) (frameworks_base
c30c6393) and its SettingsProvider throws SecurityException on reads
and writes from every other package - including WRITE_SECURE_SETTINGS
holders; the check is package-based and runs after the permission
check (e87c93a2). The one exemption Amply can use is the shell UID
('ADB is used for testing'), which is exactly how the Shizuku user
service executes settings get/put - the tester's successful adb runs
were this path's evidence all along.

The adapter now sets preferShizukuForWrites (existing Oplus plumbing:
Shizuku-required banner, disabled controls, skipped WSS auto-grant)
and drops the unprobeable key-presence gate - @Protected denies the
probe whether the key exists or not, so presence is assumed on any
GrapheneOS build, the Xiaomi-precedent assumption with the same
accepted harmless-false-claim failure mode. An absent key now decodes
as the factory off state per upstream source (BoolSetting default
false) instead of refusing. Reads need no code change: the direct read
comes back unreadable and readSyncDirectFirst already falls through to
Shizuku. The shared Shizuku-required banner no longer claims state
stays readable without Shizuku, which was only true for Oplus.

On-device beta evidence recorded in the ledger: package detection
verified from app context; unprivileged key access denied as designed
(fail closed).
@d4rken d4rken added device support Request to add charge-control support for a device/OEM Shizuku Shizuku / ADB privileged access related labels Aug 14, 2026
@github-actions github-actions Bot added the enhancement New feature or request label Aug 14, 2026
@d4rken d4rken added bug Something isn't working and removed enhancement New feature or request labels Aug 14, 2026
@d4rken
d4rken merged commit c334202 into main Aug 14, 2026
12 checks passed
@d4rken
d4rken deleted the fix/grapheneos-shizuku-gate branch August 14, 2026 05:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working device support Request to add charge-control support for a device/OEM Shizuku Shizuku / ADB privileged access related

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant