Skip to content

Commit 484d2c5

Browse files
committed
FullCharge: State the gesture firing condition in the armed notification
The armed gesture notification passed the latched arming basis (anyLevelBasis) into the copy. At a holding limit the LIMIT_HOLD branch wins the latch, so a device sitting at 80% with "Any charge level" on rendered the identical "Ready at your 80% limit" text as with the option off - understating a gesture that will in fact re-arm and fire at any charge level. The armed and waiting-for-reconnect states now pass the condition the gesture fires under: the any-level option being on AND its policy evidence currently PROTECTIVE. Requiring PROTECTIVE evidence avoids the opposite error of claiming "any charge level" when the option is on but nothing protective is detected. Idle copy keeps describing the enabled mode. The engine is untouched, and anyLevelBasis remains in the DEBUG log line, where the actually-latched basis is the right thing to record. Fixes review finding F4.
1 parent 2ad0a1b commit 484d2c5

3 files changed

Lines changed: 28 additions & 6 deletions

File tree

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

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -448,11 +448,17 @@ class ChargeSessionService : Service() {
448448
SessionNotifications.gesture(
449449
this,
450450
decision = decision,
451-
// Armed copy reflects what actually armed; idle copy explains the enabled mode.
452451
anyLevel = when (decision) {
452+
// The armed copy states the condition the gesture will fire under. The
453+
// latched basis is not that condition: at the limit, LIMIT_HOLD wins the
454+
// latch even while any-level is on and qualifying, and naming the limit
455+
// there would understate a gesture that will in fact fire at any level.
456+
// Requiring PROTECTIVE evidence keeps it from overstating in the opposite
457+
// direction, when the option is on but nothing protective is detected.
453458
QuickFullChargeDecision.ARMED,
454459
QuickFullChargeDecision.WAITING_FOR_RECONNECT,
455-
-> output.anyLevelBasis
460+
-> anyLevel && policyEvidence == PolicyEvidence.PROTECTIVE
461+
// Idle copy explains the enabled mode rather than a live basis.
456462
else -> anyLevel
457463
},
458464
// Verified evidence only, never Amply's write journal: naming a number is a

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

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -140,10 +140,11 @@ object SessionNotifications {
140140
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE,
141141
)
142142
// Three distinct states, not two. WAITING_FOR_RECONNECT is the time-critical countdown and
143-
// stays a bare instruction — the arming basis is history by then. ARMED is what a plugged-in
144-
// user actually sees, for hours, so that is where the arming basis (any level vs. a named
145-
// limit vs. a generic hold) has to be spelled out. IDLE keeps the passive "waiting" copy,
146-
// which explains the enabled mode rather than the current basis.
143+
// stays a bare instruction — how the gesture armed is history by then. ARMED is what a
144+
// plugged-in user actually sees, for hours, so that is where the condition the gesture will
145+
// fire under (any level vs. a named limit vs. a generic hold) has to be spelled out — the
146+
// caller passes that condition, not whichever basis happened to latch first. IDLE keeps the
147+
// passive "waiting" copy, which explains the enabled mode rather than the current basis.
147148
val contentText = when (decision) {
148149
QuickFullChargeDecision.WAITING_FOR_RECONNECT -> context.getString(
149150
R.string.gesture_notification_armed,

app/src/test/java/eu/darken/amply/fullcharge/core/SessionNotificationsGestureTest.kt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import eu.darken.amply.R
77
import io.kotest.matchers.shouldBe
88
import io.kotest.matchers.shouldNotBe
99
import io.kotest.matchers.string.shouldContain
10+
import io.kotest.matchers.string.shouldNotContain
1011
import org.junit.Test
1112
import org.junit.runner.RunWith
1213
import org.robolectric.RobolectricTestRunner
@@ -67,6 +68,20 @@ class SessionNotificationsGestureTest {
6768
text(QuickFullChargeDecision.IDLE, anyLevel = true)
6869
}
6970

71+
@Test
72+
fun `armed at a known limit names no percentage once any level qualifies`() {
73+
// The device sitting at its holding limit is the common state: the caller must pass the
74+
// condition the gesture fires under, so the any-level copy must not name the limit there.
75+
val atLimitAnyLevel = text(QuickFullChargeDecision.ARMED, anyLevel = true, limitPercent = 80)
76+
val atLimitOnly = text(QuickFullChargeDecision.ARMED, limitPercent = 80)
77+
78+
atLimitAnyLevel shouldBe
79+
context.getString(R.string.gesture_notification_armed_any_level)
80+
atLimitOnly shouldBe
81+
context.getString(R.string.gesture_notification_armed_limit, 80)
82+
atLimitAnyLevel!! shouldNotContain "80%"
83+
}
84+
7085
@Test
7186
fun `the reconnect countdown ignores the arming basis`() {
7287
val plain = text(QuickFullChargeDecision.WAITING_FOR_RECONNECT)

0 commit comments

Comments
 (0)