Skip to content

Commit ff3925a

Browse files
committed
Merge remote-tracking branch 'origin/main' into feat/stats-chart-dual-axes
# Conflicts: # app/src/main/java/eu/darken/amply/stats/ui/StatsCurrentSessionCard.kt
2 parents 994db33 + be4f0fe commit ff3925a

38 files changed

Lines changed: 3210 additions & 384 deletions

app/src/debug/java/eu/darken/amply/screenshots/ScreenshotContent.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ private fun DashboardShot(state: DashboardUiState) = PreviewWrapper {
8888
onOpenBatteryDetail = {},
8989
onOpenStats = {},
9090
onOpenLiveSession = {},
91+
onRetryCapture = {},
9192
onPinWidget = {},
9293
onAddTile = {},
9394
onDismissQuickAccess = {},
@@ -103,6 +104,7 @@ private fun DashboardShot(state: DashboardUiState) = PreviewWrapper {
103104
onOpenSupportIssue = {},
104105
onEmailSupport = {},
105106
onHelp = {},
107+
onDismissInterruption = {},
106108
)
107109
}
108110

app/src/main/java/eu/darken/amply/battery/ui/BatteryDetailScreen.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ private fun ChargingSection(readout: BatteryReadout) {
8181
)
8282
DetailRow(
8383
stringResource(R.string.battery_detail_status),
84-
stringResource(batteryStatusLabel(readout.status)),
84+
stringResource(batteryStatusLabel(readout.status, readout.plugged)),
8585
)
8686
DetailRow(
8787
stringResource(R.string.battery_detail_power_source),

app/src/main/java/eu/darken/amply/battery/ui/BatteryLabels.kt

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,20 @@ import eu.darken.amply.R
99
* constants fall back to a generic label rather than crashing or hiding the row.
1010
*/
1111

12+
/**
13+
* A bare "Not charging" on a connected device (held at a charge limit — Amply's core scenario)
14+
* reads as if the charger weren't detected, so that combination gets an explicit "plugged in"
15+
* wording. Other statuses pass through: "Charging"/"Full" already imply a connection.
16+
*/
1217
@StringRes
13-
internal fun batteryStatusLabel(status: Int?): Int = when (status) {
18+
internal fun batteryStatusLabel(status: Int?, plugged: Int?): Int = when (status) {
1419
BatteryManager.BATTERY_STATUS_CHARGING -> R.string.battery_status_charging
1520
BatteryManager.BATTERY_STATUS_DISCHARGING -> R.string.battery_status_discharging
16-
BatteryManager.BATTERY_STATUS_NOT_CHARGING -> R.string.battery_status_not_charging
21+
BatteryManager.BATTERY_STATUS_NOT_CHARGING -> if ((plugged ?: 0) != 0) {
22+
R.string.battery_status_plugged_not_charging
23+
} else {
24+
R.string.battery_status_not_charging
25+
}
1726
BatteryManager.BATTERY_STATUS_FULL -> R.string.battery_status_full
1827
else -> R.string.battery_value_unknown
1928
}

app/src/main/java/eu/darken/amply/fullcharge/core/BootRecoveryFlow.kt

Lines changed: 53 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -54,10 +54,30 @@ class BootRecoveryFlow(private val hooks: Hooks) {
5454
SUPERSEDED,
5555
}
5656

57-
suspend fun run(): Outcome {
57+
/**
58+
* Outcome plus the facts an interruption assessor needs to decide whether the flow actually did
59+
* restore work: [restoreAttempted] (the flow called [Hooks.restoreSession]), [rewrites] (re-write
60+
* attempts issued, including a failed final one), and [retryRemaining] (a pending target is still
61+
* persisted on exit, so a later start will retry).
62+
*/
63+
data class Result(
64+
val outcome: Outcome,
65+
val restoreAttempted: Boolean,
66+
val rewrites: Int,
67+
val retryRemaining: Boolean,
68+
)
69+
70+
suspend fun run(): Result {
71+
var restoreAttempted = false
72+
var rewrites = 0
5873
val sessionTarget = hooks.currentSessionTarget()
5974
val pendingTarget = hooks.pendingTarget()
60-
val target = pendingTarget ?: sessionTarget ?: return Outcome.NOTHING_TO_DO
75+
val target = pendingTarget ?: sessionTarget ?: return Result(
76+
outcome = Outcome.NOTHING_TO_DO,
77+
restoreAttempted = false,
78+
rewrites = 0,
79+
retryRemaining = false,
80+
)
6181
var staleIntended: ChargePolicy? = null
6282
if (pendingTarget != null) {
6383
// The pending target is always the newest intent: setPersistentPolicy persists it
@@ -76,17 +96,22 @@ class BootRecoveryFlow(private val hooks: Hooks) {
7696
} else {
7797
hooks.setPendingTarget(target)
7898
log(TAG) { "Recovery: restoring ${target.stableId}" }
99+
restoreAttempted = true
79100
if (!hooks.restoreSession()) {
80101
log(TAG, Logging.Priority.ERROR) { "Restore failed; session remains persisted" }
81102
hooks.notifyFailure(writeFailed = true)
82103
hooks.clearPendingTarget()
83-
return Outcome.RESTORE_FAILED
104+
return Result(
105+
outcome = Outcome.RESTORE_FAILED,
106+
restoreAttempted = true,
107+
rewrites = rewrites,
108+
retryRemaining = false,
109+
)
84110
}
85111
}
86112

87113
val startedAt = hooks.elapsedRealtime()
88114
var lastWriteAt = startedAt
89-
var rewrites = 0
90115
while (true) {
91116
hooks.tick()
92117
val intended = hooks.intendedTarget()
@@ -95,7 +120,12 @@ class BootRecoveryFlow(private val hooks: Hooks) {
95120
// were converging; never write the boot target over a newer choice.
96121
log(TAG) { "Boot recovery superseded by ${intended.stableId}" }
97122
hooks.clearPendingTarget()
98-
return Outcome.SUPERSEDED
123+
return Result(
124+
outcome = Outcome.SUPERSEDED,
125+
restoreAttempted = restoreAttempted,
126+
rewrites = rewrites,
127+
retryRemaining = false,
128+
)
99129
}
100130
val now = hooks.elapsedRealtime()
101131
val snapshot = hooks.batterySnapshot()
@@ -121,22 +151,37 @@ class BootRecoveryFlow(private val hooks: Hooks) {
121151
// write) should be retried by the next service start.
122152
log(TAG, Logging.Priority.ERROR) { "Boot recovery re-write failed" }
123153
hooks.notifyFailure(writeFailed = true)
124-
return Outcome.GAVE_UP
154+
return Result(
155+
outcome = Outcome.GAVE_UP,
156+
restoreAttempted = restoreAttempted,
157+
rewrites = rewrites,
158+
retryRemaining = true,
159+
)
125160
}
126161
lastWriteAt = hooks.elapsedRealtime()
127162
}
128163
RecoveryDecision.DONE_OK -> {
129164
log(TAG) { "Boot recovery finished for ${target.stableId}" }
130165
hooks.clearPendingTarget()
131-
return Outcome.CONVERGED
166+
return Result(
167+
outcome = Outcome.CONVERGED,
168+
restoreAttempted = restoreAttempted,
169+
rewrites = rewrites,
170+
retryRemaining = false,
171+
)
132172
}
133173
RecoveryDecision.GIVE_UP -> {
134174
log(TAG, Logging.Priority.ERROR) {
135175
"Boot recovery: hardware did not converge to ${target.stableId}"
136176
}
137177
hooks.notifyFailure(writeFailed = false)
138178
hooks.clearPendingTarget()
139-
return Outcome.GAVE_UP
179+
return Result(
180+
outcome = Outcome.GAVE_UP,
181+
restoreAttempted = restoreAttempted,
182+
rewrites = rewrites,
183+
retryRemaining = false,
184+
)
140185
}
141186
}
142187
}

app/src/main/java/eu/darken/amply/fullcharge/core/ChargeSessionManager.kt

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import eu.darken.amply.charging.core.ChargingPreferences
88
import eu.darken.amply.fullcharge.core.FullChargeStore
99
import kotlinx.coroutines.sync.Mutex
1010
import kotlinx.coroutines.sync.withLock
11+
import java.util.UUID
1112
import javax.inject.Inject
1213
import javax.inject.Singleton
1314

@@ -16,6 +17,8 @@ class ChargeSessionManager @Inject constructor(
1617
private val repository: ChargingRepository,
1718
private val preferences: ChargingPreferences,
1819
private val sessionStore: FullChargeStore,
20+
private val processIdentity: ProcessIdentity,
21+
private val bootCountProvider: BootCountProvider,
1922
) {
2023
private val mutex = Mutex()
2124

@@ -74,8 +77,20 @@ class ChargeSessionManager @Inject constructor(
7477
}
7578
val restorePolicy = (decision as SessionStartDecision.Start).restorePolicy
7679

77-
// Persist recovery state before removing the limit.
78-
sessionStore.startSession(restorePolicy, nowMillis)
80+
// Persist recovery state before removing the limit. Stamp this process's identity so a later
81+
// pickup can tell whether the session survived a process death (interruption detection), and a
82+
// stable work id that survives ownership adoption so a later restore can resolve the warning.
83+
sessionStore.startSession(
84+
restorePolicy = restorePolicy,
85+
startedAtMillis = nowMillis,
86+
workId = UUID.randomUUID().toString(),
87+
provenance = WorkProvenance(
88+
token = processIdentity.token,
89+
pid = processIdentity.pid,
90+
bootCount = bootCountProvider.current(),
91+
createdAtMillis = nowMillis,
92+
),
93+
)
7994
val result = repository.applyTemporary(overridePolicy)
8095
if (result.success) {
8196
result

0 commit comments

Comments
 (0)