Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,7 @@ private fun readyState() = DashboardUiState(
device = pixelDevice(),
adapterName = "Pixel Charge Control".toCaString(),
adapterId = "pixel",
adapterDetail = "Charging changes take about 15 seconds to reach the hardware".toCaString(),
supportedPolicies = pixelPolicies(),
reconnectSupported = true,
controlEnabled = true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,13 @@ data class ChargingState(
val device: DeviceInfo = DeviceInfo.current(),
val adapterName: CaString = R.string.adapter_name_detecting.toCaString(),
val adapterId: String? = null,
/**
* Standing capability note for a control-enabled adapter (write latency, Shizuku requirement,
* replug semantics — the `adapter_detail_*_ready` strings). Null whenever control is unavailable:
* the gate-failure text already reaches the UI as the observation's reason, and populating both
* would print the same sentence twice.
*/
val adapterDetail: CaString? = null,
val supportedPolicies: List<ChargePolicy> = emptyList(),
/**
* The selected adapter's protective default (e.g. FixedLimit(80) on Pixel, Adaptive on Xiaomi), or
Expand Down Expand Up @@ -276,7 +283,11 @@ class ChargingRepository @Inject constructor(
if (adapter == null || !selection.support.controlEnabled) {
val detail = selection.support.detail.toCaString()
val observation = ChargeObservation.Unsupported(detail)
mutableState.value = state.value.copy(observation = observation, message = detail)
// Also drop the standing ready note: this branch means the gate just failed on a fresh
// selection (a capability can vanish between refresh and tap), and the field's contract
// is "null whenever control is unavailable" — keeping a stale "changes apply
// immediately" under the failure reason would contradict it.
mutableState.value = state.value.copy(observation = observation, message = detail, adapterDetail = null)
return ApplyResult(false, observation, context.getString(selection.support.detail))
}
if (policy !in adapter.supportedPolicies) {
Expand Down Expand Up @@ -500,6 +511,13 @@ class ChargingRepository @Inject constructor(
controlEnabled = selection.support.controlEnabled,
contributionWanted = selection.support.contributionWanted,
guidedCaptureUseful = selection.support.guidedCaptureUseful,
// controlEnabled implies detail is the adapter's *_ready string (every probe's when-cascade
// pairs them), so this can never carry a gate-failure reason.
adapterDetail = if (adapter != null && selection.support.controlEnabled) {
selection.support.detail.toCaString()
} else {
null
},
adapterResolved = true,
access = access,
observation = observation,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -483,6 +483,18 @@ private fun StatusCard(
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
// Standing adapter fact (write latency, Shizuku requirement, replug semantics) — only set
// while control is enabled, so it never repeats a gate-failure reason shown above. Hidden
// while a replug is pending: the transient hint below states the same fact as an instruction,
// and printing both would say "reconnect the charger" twice on latched adapters.
state.charging.adapterDetail?.takeIf { !state.charging.isAwaitingReplug() }?.let {
Spacer(Modifier.height(4.dp))
Text(
it.asComposable(),
style = MaterialTheme.typography.labelMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
}
Spacer(Modifier.height(4.dp))
Text(
stringResource(
Expand Down Expand Up @@ -889,6 +901,7 @@ private fun DashboardScreenPreview() = PreviewWrapper {
device = DeviceInfo("Google", "Pixel 8", 36, "preview"),
adapterName = "Pixel Charge Control".toCaString(),
adapterId = "pixel",
adapterDetail = "Charging changes take about 15 seconds to reach the hardware".toCaString(),
supportedPolicies = listOf(
ChargePolicy.FixedLimit(80),
ChargePolicy.Adaptive,
Expand Down Expand Up @@ -1125,6 +1138,7 @@ private fun DashboardScreenAwaitingReplugPreview() = PreviewWrapper {
device = DeviceInfo("Google", "Pixel 9 Pro XL", 37, "preview"),
adapterName = "GrapheneOS charge limit".toCaString(),
adapterId = "grapheneos-chargelimit-v1",
adapterDetail = "Changes take effect the next time the charger is reconnected".toCaString(),
supportedPolicies = listOf(
ChargePolicy.FixedLimit(80),
ChargePolicy.Unrestricted,
Expand Down
12 changes: 6 additions & 6 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -126,22 +126,22 @@
<string name="adapter_detail_no_tablet">Pixel tablets are not supported</string>
<string name="adapter_detail_no_controller">Google\'s charging-optimization controller is not available</string>
<string name="adapter_detail_disabled_build">Adapter disabled in this build</string>
<string name="adapter_detail_pixel_ready">Supported Pixel capability detected; charging hardware may take about 15 seconds to react</string>
<string name="adapter_detail_pixel_ready">Charging changes take about 15 seconds to reach the hardware</string>
<string name="adapter_detail_lab_diagnostics">Detected for diagnostics only; no unverified writes are exposed</string>
<string name="adapter_detail_requires_samsung">Requires a Samsung device on a verified One UI version</string>
<string name="adapter_detail_samsung_no_key">The battery-protection setting is not present on this device</string>
<string name="adapter_detail_secondary_user">Charging protection is device-wide; control is limited to the main user</string>
<string name="adapter_detail_samsung_ready">Samsung battery protection detected; changes apply immediately</string>
<string name="adapter_detail_samsung_ready">Changes apply to the hardware immediately</string>
<string name="adapter_detail_requires_xiaomi">Requires a verified Xiaomi device on HyperOS 2</string>
<string name="adapter_detail_xiaomi_ready">Xiaomi charging protection detected; changes apply immediately</string>
<string name="adapter_detail_xiaomi_ready">Changes apply to the hardware immediately</string>
<string name="adapter_detail_requires_oplus">Requires a OnePlus, Oppo, or Realme device on ColorOS 15</string>
<string name="adapter_detail_oplus_ready">ColorOS charging protection detected; control requires Shizuku</string>
<string name="adapter_detail_oplus_ready">Charging control requires Shizuku on this device</string>
<string name="adapter_detail_requires_grapheneos">Requires GrapheneOS</string>
<string name="adapter_detail_grapheneos_no_key">GrapheneOS detected, but its charge-limit setting is not present on this build</string>
<string name="adapter_detail_grapheneos_ready">GrapheneOS charge limit detected — changes take effect when the charger is reconnected</string>
<string name="adapter_detail_grapheneos_ready">Changes take effect the next time the charger is reconnected</string>
<string name="adapter_detail_requires_lineageos">Requires a qualified LineageOS device</string>
<string name="adapter_detail_lineageos_no_provider">LineageOS charging control is not available on this build</string>
<string name="adapter_detail_lineageos_ready">LineageOS charging control detected; control requires Shizuku</string>
<string name="adapter_detail_lineageos_ready">Charging control requires Shizuku on this device</string>
<string name="adapter_detail_none">No charging adapter is known for this device</string>

<!-- Access backend detail + summary labels -->
Expand Down