Skip to content

Charging: Detect LineageOS via the system feature, not a denied property - #45

Merged
d4rken merged 2 commits into
mainfrom
worktree-lineage-detection-fix
Aug 3, 2026
Merged

Charging: Detect LineageOS via the system feature, not a denied property#45
d4rken merged 2 commits into
mainfrom
worktree-lineage-detection-fix

Conversation

@d4rken

@d4rken d4rken commented Aug 3, 2026

Copy link
Copy Markdown
Member

What changed

Amply could not recognise LineageOS at all, so LineageOS phones were treated as if they were running their manufacturer's stock software. On a LineageOS Pixel that meant being told the 80% limit is "supported on Pixel 6a and newer phones", being sent to Battery Saver instead of the Battery screen that actually holds LineageOS's own Charging control, and never being offered the "help add support" card. Amply now recognises LineageOS and says the right things: it points at LineageOS's own charging control, and describes the device as a LineageOS device rather than a Google one.

For people who want to help add support, the LineageOS path is reworked. The guided step-by-step wizard is no longer offered there — on LineageOS it could never finish, because the settings it watches are not the ones LineageOS uses, so it always ended with nothing to send after several minutes of work. Instead the "send device info" report now includes which charge-control method the ROM uses, which is the useful part. Connecting Shizuku is offered directly on the card so that detail can be included; without it everything still works, the report just says "unknown".

No change for any other device. This does not make LineageOS charge control work — no LineageOS device is enabled by it.

Technical Context

Root cause. Every ro.lineage.* property is SELinux-labelled custom_version_prop, which untrusted_app cannot read, and SystemProperties.get returns an empty string on denial instead of throwing. So SystemPropertyReader's runCatching never fired, nothing was logged, LineageOsDetector.detect() returned null, and both Lineage adapters skipped their probe. getprop over adb had always masked this because it runs as the shell UID. Confirmed by avc: denied { read } … tcontext=u:object_r:custom_version_prop:s0 … app=eu.darken.amply.

Detection now keys on the org.lineageos.android system feature — app-readable with no <queries> entry and no permission, and declared upstream from LineageOS 15.1 through 23.2. lineageOsVersion stays as an OR-input (derivatives that relabel the property) and for diagnostics. Fingerprint sniffing is not an alternative: LineageOS spoofs Build.FINGERPRINT to stock (google/oriole/oriole:16/…/release-keys).

Why CI never caught it. AdapterRegistrySelectionTest injected lineageOsVersion straight into DeviceInfo, so every gate looked correct while the real read returned nothing. DeviceInfoLineageDetectionTest now drives DeviceInfo.current(context) against a real PackageManager through the registry.

The probe is an observation, not a verdict. Reviewing this twice produced two wrong inferences worth recording so they are not reintroduced:

  • Provider selection branches on the configured mode before capability — MODE_AUTO/MANUAL return Deadline before Limit is consulted — so Deadline proves nothing.
  • Toggle also accepts MODE_LIMIT and enforces targetPct itself by cutting charging, so binding it is a capable mechanism, not a rejection. isHALModeSupported additionally swallows RemoteException into false.

Hence LineageLimitMechanism has no negative case (NATIVE_LIMIT/FRAMEWORK_TOGGLE/NOT_OBSERVED/UNKNOWN), and a test enumerates the constants so one cannot be added back. Even NATIVE_LIMIT is not qualification — oriole bound Limit on LineageOS 20 and still charged past the cap.

Safety boundary. The new AIDL op takes no arguments: constant binary, constant argument, read-only, no write-allowlist involvement, nothing caller-supplied to validate. The raw dump is reduced to PROVIDER|mode inside the user service — it contains StartTime/TargetTime (the user's charging schedule, i.e. their sleep window) and battery level, which must not reach a public report. Reviewed by Codex over four passes; no injection path and no route by which raw text reaches the app process or a log.

Review guidance. Worth the closest look: the mechanism inference in LineageHealthProbe.kt against upstream ChargingControlController.getProviderForMode and Toggle.java; the guidedCaptureUseful gating (both entry points plus the destination, which waits on adapterResolved so a saved-state restore cannot flash the wizard on the permissive default); and the BackendStatus branching on the card, since requesting permission is a no-op while the Shizuku server is stopped.

Verified on Pixel 6 (oriole) / LineageOS 23.2 / Android 16. Adapter selection lineageos-lab; report reads is_lineageos=true, lineageos_version=none, lineage_cc_provider=DEADLINE, lineage_cc_mode=1, lineage_cc_limit_mechanism=NOT_OBSERVED; all fields degrade to unknown with Shizuku stopped, where the button correctly reads "Open Shizuku" and launches it. Lineage settings unchanged throughout; no crashes. The device itself remains NO-GO on both LineageOS builds — that verdict rests on the mode=3 write reading back as 1 and the LOS 20 charge-past observation, not on this probe.

Also fixed alongside: the contribution wizard's bottom bar had no navigation-bar inset and sat under the 3-button nav bar (~31px of Back/Next tappable), and the unsupported card interpolated Build.MANUFACTURER, telling a LineageOS Pixel user that "Google devices" are unmapped.

LineageOS was never detected in the app process, so every LineageOS
device was routed to an OEM adapter. All five ro.lineage.* properties
are SELinux-labelled custom_version_prop, which untrusted_app cannot
read, and SystemProperties.get returns an empty string on denial rather
than throwing — so SystemPropertyReader's runCatching never fired,
nothing was logged, and LineageOsDetector silently returned null. getprop
over adb had always masked this by running as the shell UID.

That made QUALIFIED_CODENAMES dead code (a qualified codename could never
activate), hid the contribution card, and pointed "open battery settings"
at Battery Saver instead of Battery. Detection now uses the app-readable
org.lineageos.android system feature via DeviceInfo.isLineageOs; the
version property is kept as a secondary OR-input for derivatives that
relabel it, and for diagnostics.

Also in the LineageOS contribution path:

- The guided settings-diff wizard is withheld (AdapterSupport
  .guidedCaptureUseful). Its capture set excludes the lineagesettings
  provider, so a run there always diffs to empty and cannot be delivered
  — users were walked into a dead end after entering details and
  capturing twice. Both entry points and the destination itself are
  gated; the destination waits for adapterResolved so a saved-state
  restore cannot flash the wizard on the permissive default.
- The direct report carries a dumpsys lineagehealth probe (new
  zero-argument AIDL op; constant binary and argument, read-only, no
  write allowlist involved). It is an observation, never a verdict:
  provider selection branches on the configured mode before capability,
  and Toggle also accepts MODE_LIMIT and enforces the cap itself, so no
  value disqualifies a device and none proves enforcement.
- The raw dump is reduced to PROVIDER|mode inside the user service, so
  the user's charging schedule (StartTime/TargetTime) and battery level
  never cross Binder.
- Withholding the wizard removed these users' only Shizuku grant entry
  point, which the probe needs; the card now offers it, branching on
  BackendStatus so a stopped server opens Shizuku instead of firing a
  request that returns false immediately.

Fixes found alongside: the wizard's bottom bar lacked a navigation-bar
inset and sat under the 3-button nav bar, and the unsupported card
interpolated Build.MANUFACTURER, telling a LineageOS Pixel user that
"Google devices" are unmapped.

Verified on Pixel 6 (oriole) / LineageOS 23.2 / Android 16: adapter
selection is lineageos-lab, the report reads is_lineageos=true,
lineage_cc_provider=DEADLINE, lineage_cc_mode=1,
lineage_cc_limit_mechanism=NOT_OBSERVED, and every field degrades to
unknown with Shizuku stopped. Regression tests drive DeviceInfo.current
against a real PackageManager, the seam the previous unit tests bypassed
by injecting lineageOsVersion directly.
@github-actions github-actions Bot added the enhancement New feature or request label Aug 3, 2026
@d4rken d4rken added bug Something isn't working ROM: LOS LineageOS Shizuku Shizuku / ADB privileged access related device support Request to add charge-control support for a device/OEM api: 36 A16 (Baklava) and removed enhancement New feature or request labels Aug 3, 2026
#44 landed on the same three files with a related change: the
metadata-only report is now gated on ChargingState.hasSupportLead.

The two interact. #44's fallback without a lead is "the card keeps the
wizard", and this branch withholds the wizard on LineageOS — together
those could have left that card with no contribution path at all. They
do not: hasSupportLead includes adapterMatched (the Lineage lab adapter
matches once detection works) and hasLineageSettingsProvider (verified
present on oriole), so the metadata path stays offered while the wizard
is withheld. Documented on the parameter, with a preview for that
combination since neither existing preview rendered it.

Also corrected hasSupportLead itself: it counted
`device.lineageOsVersion != null` as a lead, which is dead on every real
LineageOS build for exactly the reason this branch exists. Now isLineageOs.
@github-actions github-actions Bot added enhancement New feature or request and removed bug Something isn't working labels Aug 3, 2026
@d4rken
d4rken merged commit 9d34285 into main Aug 3, 2026
12 checks passed
@d4rken
d4rken deleted the worktree-lineage-detection-fix branch August 3, 2026 13:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

api: 36 A16 (Baklava) device support Request to add charge-control support for a device/OEM enhancement New feature or request ROM: LOS LineageOS Shizuku Shizuku / ADB privileged access related

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant