@@ -10,22 +10,7 @@ project's convention. Code is grouped by **feature**, not framework layer.
1010
1111## Package Map
1212
13- ```
14- eu.darken.amply
15- ├── charging/core policies, capability gate, OEM adapters, WSS + Shizuku access
16- │ ├── access/shizuku Shizuku detection, user-service client, AIDL boundary
17- │ └── adapter AdapterRegistry + adapters (Pixel, Samsung, Xiaomi, OnePlus/ColorOS, LineageOS live)
18- ├── fullcharge/core temporary session, boot recovery, reconnect gesture, decision engines
19- ├── diagnostics/core + ui "Help add support" contribution wizard: read-only multi-mode setting discovery + on-device privacy review
20- ├── main
21- │ ├── core app-level wiring
22- │ └── ui MainActivity, onboarding, dashboard, settings, setup, tile, widget
23- └── common
24- ├── AppDataStore single process-safe Preferences DataStore owner
25- ├── theming brand / Material You / mode / contrast prefs
26- ├── settings reusable hierarchical settings rows + sections
27- └── debug/logging Logging fan-out + backends (Logcat, File)
28- ```
13+ See "Package Layout" in ` .claude/CLAUDE.md ` .
2914
3015Feature-specific preference facades live with their owning feature but share the one ` AppDataStore ` instance. They
3116declare their settings with the ` createValue() ` DSL (` common/datastore ` ) rather than touching ` store.data ` — with a
@@ -60,95 +45,11 @@ sticky broadcast keeps its last powered value, so hardware state is never treate
6045to the last request. Normal (` 1 ` ) stays unknown without Shizuku (inactive adaptive vs. unrestricted are
6146indistinguishable).
6247
63- ## Samsung Adapters
64-
65- Two live adapters, gated by ` ro.build.version.oneui ` ranges plus ` protect_battery ` presence plus system user
66- (all in world-readable ` global ` namespace; only writes need WSS):
67-
68- - ** Modern (One UI 8.x)** : ` protect_battery ` 0=off / 1=Maximum / 3=Standard(pause at 100%, resume 95%), plus
69- ` battery_protection_threshold ` 80|85|90|95 (absent = 80, only valid ticks decode; malformed → Unknown).
70- Policies: FixedLimit(80/85/90/95), PauseAtFull, Unrestricted. Session override = ** PauseAtFull** (reaches 100%
71- while keeping Samsung's own safety net). Threshold is written before mode.
72- - ** Legacy (One UI 4.x/5.x)** : ` protect_battery ` 0/1 toggle, fixed 85% cap. Policies: FixedLimit(85),
73- Unrestricted. Session override = Unrestricted.
74-
75- Writes apply ** synchronously** (` VerificationStrategy.SYNC_READBACK ` ): ` apply() ` requires read-back equality, no
76- pending-settle window, boot recovery converges on settings readback, and no reapply-inversion trick is needed.
77- The reconnect gesture is Pixel-only (` reconnectGestureSupported ` ). One UI 6/7 and 9+ fall through to the
78- diagnostics-only lab adapter. An external ` protect_battery=0 ` makes One UI forget the user's prior mode (it falls back
79- to the OEM default on re-enable), so Amply restores the exact prior policy itself rather than trusting Samsung's
80- bookkeeping. Verified devices + coverage: see the qualification ledger in ` privileged-access.md ` .
81-
82- ## Xiaomi Adapter
83-
84- One live adapter (` xiaomi-hyperos2-v1 ` ), gated to the HyperOS ROM version (the setting is a ROM
85- feature, not a per-model one): manufacturer Xiaomi (covers Redmi/POCO — they report Xiaomi as
86- manufacturer) + ` ro.mi.os.version.code == 2 ` (HyperOS 2.x) + system user. Use ` ro.mi.os.version.code ` ,
87- NOT the frozen legacy ` ro.miui.ui.version.code ` . Single key
88- ` secure/security_pc_secure_protect_mode_key ` : ` 0 ` =charge fully, ` 1 ` =Intelligent (heuristic 80% hold →
89- ` ChargePolicy.Adaptive ` ), absent=Intelligent (factory state). No hard-cap mode exists. SYNC_READBACK
90- with read-back equality; session override = Unrestricted; protective default = Adaptive. HyperOS 1,
91- pre-HyperOS MIUI, and a future HyperOS 3 fall to ` XiaomiLabAdapter ` (diagnostics + contribution). Two
92- documented assumptions: the feature is treated as present on any HyperOS 2 device (a device lacking it
93- reads the key absent → a harmless false claim of control), and daemon-level enforcement of external
94- writes is pending long-term observation (see the qualification ledger in ` privileged-access.md ` ).
95-
96- ## OnePlus / ColorOS Adapter
97-
98- One live adapter (` oplus-coloros15-v1 ` ) for the ColorOS/OxygenOS (Oplus) family — OnePlus, Oppo, Realme —
99- gated to ` ro.build.version.oplusrom == 15 ` (Oplus-exclusive property, so it doubles as the family signal) +
100- system user. Two mutually-exclusive ** ` system ` ** keys under Battery health: ` regular_charge_protection_switch_state `
101- = "Charging limit" (fixed 80% cap → ` FixedLimit(80) ` ) and ` smart_charge_protection_switch_state ` = "Smart charging"
102- (adaptive → ` Adaptive ` ); neither on = Unrestricted; both on = Unknown/unrecognized. The OEM enforces exclusion and
103- keeps a ` _status ` mirror (Amply writes only ` _switch_state ` ). SYNC_READBACK with read-back equality; session
104- override = Unrestricted; protective default = FixedLimit(80). ** Writes require Shizuku** — the keys are ` system `
105- namespace, which WRITE_SECURE_SETTINGS cannot write (reads are unprivileged); the adapter sets
106- ` preferShizukuForWrites ` . Unqualified Oplus versions fall to ` OnePlusLabAdapter ` . Enforcement is directly
107- observable (device holds at 80%). See the qualification ledger in ` privileged-access.md ` .
108-
109- ## LineageOS Adapter
110-
111- One live adapter (` lineageos-chargingcontrol-v1 ` ) plus a ` LineageLabAdapter ` , for LineageOS's native Charging
112- Control. ** Manufacturer-agnostic** — the ROM changes charging control regardless of the OEM hardware — so both are
113- registered ** first** in ` AdapterRegistry ` , ahead of every OEM adapter; a LineageOS build on Samsung/Xiaomi/OnePlus/
114- Pixel hardware is handled by these, never the OEM lab adapters (stock devices have ` lineageOsVersion == null ` and
115- skip both). Gate: ` ro.lineage.build.version ` present + ` Build.DEVICE ` in a ** physically-qualified codename
116- allowlist** (` QUALIFIED_CODENAMES ` ) + ` lineagesettings ` provider present + system user. Unqualified LineageOS builds
117- fall to ` LineageLabAdapter ` .
118-
119- The three keys live in the private ` content://lineagesettings/system ` provider (` SettingNamespace.LINEAGE_SYSTEM ` ),
120- NOT any AOSP ` settings ` namespace: ` charging_control_enabled ` (0/1), ` charging_control_mode ` (3=LIMIT), and
121- ` charging_control_charging_limit ` (the discrete ticks 70/75/80/85/90/95). A hard cap is ` enabled=1 ` +` mode=3 ` +` limit=N ` ;
122- ` enabled=0 ` is Unrestricted. Writes are ordered limit→mode→enabled (the observable "on" flip last). ** Reads are
123- unprivileged** (` LineageSettingsClient ` via ContentResolver, shared by both backends); ** writes require Shizuku**
124- (` content insert ` ; the shell UID holds ` lineageos.permission.WRITE_SETTINGS ` , which ` WRITE_SECURE_SETTINGS ` cannot
125- cover — ` preferShizukuForWrites ` , and the WSS auto-grant is skipped). ` SYNC_READBACK ` with read-back equality;
126- session override = Unrestricted; protective default = FixedLimit(80); reconnect gesture unsupported.
127-
128- LineageOS's own ` ChargingControlController ` observes these keys and re-drives the `vendor.lineage.health.
129- IChargingControl` HAL, so an external write is honored. But the HAL is ** device-dependent** (the setting can flip
130- while charging never actually stops — the ` mIsLimitSet:false ` bug), which is why the gate is a qualified-codename
131- allowlist and control ships disabled until a codename is physically proven. ` read() ` returns ` Verified ` ** only** for
132- states v1 can restore exactly (a supported fixed limit, or Unrestricted); AUTO/CUSTOM schedules, off-tick limits, and
133- an absent/malformed ` enabled ` decode to ` Unknown(unrecognizedValue=true) ` so a temporary session refuses rather than
134- clobbering the user's native choice. Verified devices + coverage: see the qualification ledger in ` privileged-access.md ` .
135-
136- ## Pixel Adapter
137-
138- Writes ** only** two secure settings:
139-
140- - ` secure/adaptive_charging_enabled `
141- - ` secure/charge_optimization_mode `
142-
143- Ordering matters:
144-
145- - Fixed 80%: adaptive ` 0 ` , then mode ` 1 `
146- - Unrestricted: mode ` 0 ` , then adaptive ` 0 `
147- - Adaptive: mode ` 0 ` , then adaptive ` 1 `
148-
149- Google's Settings Intelligence worker applies external secure-setting changes ** asynchronously** (measured
150- charging-HAL delay ≈ 11–12 s on tested Pixels). A same-package same-value write does ** not** fire the settings
151- observer — re-writes briefly invert ` charge_optimization_mode ` before applying the target.
48+ ## OEM Adapters
49+
50+ Per-adapter detail (Samsung, Xiaomi, OnePlus/ColorOS, LineageOS, Pixel — keys, value domains, write ordering,
51+ session overrides) lives in the ** ` oem-adapters ` skill** — read it before changing anything under
52+ ` charging/core/adapter ` .
15253
15354## Temporary Session & Recovery
15455
@@ -190,5 +91,5 @@ Android does not deliver `ACTION_POWER_CONNECTED` / `ACTION_POWER_DISCONNECTED`
19091 replace this runtime gate with an exact-model allowlist or a version-only check.
19192- Shizuku installation is detected by resolving the owner of ` ShizukuProvider.PERMISSION ` , ** not** a fixed package
19293 name — this recognizes renamed forks and hidden-package mode. Don't hardcode a package name.
193- - Pixel/Samsung/Xiaomi/Oplus keys are all live on gated devices (see the adapter sections ). New writable keys must
94+ - Pixel/Samsung/Xiaomi/Oplus keys are all live on gated devices (see the ` oem-adapters ` skill ). New writable keys must
19495 be spike-verified and added to ` SettingWritePolicy ` with an explicit per-key value domain.
0 commit comments