Files
LibreMail/app/src/test/kotlin/org/libremail/push/PushStatusNotificationTest.kt
T
JMR-devandClaude Opus 4.8 8904351a96 fix(push): handle Service.onTimeout to survive the dataSync FGS runtime cap
IdleService runs continuously as a FOREGROUND_SERVICE_TYPE_DATA_SYNC
foreground service (push is on by default). With targetSdk 37, Android 14+'s
dataSync FGS runtime cap (~6h per rolling 24h) calls Service.onTimeout(...)
and then force-stops the service — throwing a system FGS-timeout exception —
if it doesn't stop itself. IdleService overrode onStartCommand/onDestroy/onBind
but not onTimeout, so after ~6 cumulative hours push silently died and the app
hit the exception; on API 35+ the budget is cumulative and a restart can't
recover it until the next 24h window.

Override both onTimeout(startId) (deprecated, API 34) and
onTimeout(startId, fgsType) (API 35+); both route to a clean shutdown that
re-asserts the already-scheduled 15-minute periodic sync, swaps the persistent
notification to a degraded "paused" text and DETACHes it so it survives, then
stopForeground(DETACH) + stopSelf so we never leave a dataSync FGS running past
its cap (the exact condition the platform kills on). This mirrors the existing
low-battery PushMode.POLLING fallback.

The push-status text choice is pulled into a pure PushStatusNotification.statusTextRes
seam and unit-tested on the JVM; the built notification's new timed-out text is
covered by PushStatusNotificationInstrumentedTest.

Closes #302

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 02:09:01 -05:00

52 lines
1.8 KiB
Kotlin

// SPDX-License-Identifier: GPL-3.0-or-later
package org.libremail.push
import org.junit.Assert.assertEquals
import org.junit.Test
import org.libremail.R
import org.libremail.data.sync.PushMode
/**
* JVM coverage of [PushStatusNotification.statusTextRes] — the pure "which status message for which
* push state" decision. [PushStatusNotification.build]'s `NotificationCompat` is a no-op stub in the
* unit-test `android.jar`, so the built `Notification` is asserted on-device in
* `PushStatusNotificationInstrumentedTest`; this verifies the text-selection branch — including the
* #302 dataSync FGS runtime-cap fallback — with no emulator.
*/
class PushStatusNotificationTest {
@Test
fun `idle shows the instant-delivery text`() {
assertEquals(
R.string.notif_push_status_text,
PushStatusNotification.statusTextRes(PushMode.IDLE, timedOut = false),
)
}
@Test
fun `polling shows the low-battery fallback text`() {
assertEquals(
R.string.notif_push_status_text_low_battery,
PushStatusNotification.statusTextRes(PushMode.POLLING, timedOut = false),
)
}
@Test
fun `a dataSync FGS timeout shows the paused fallback text`() {
assertEquals(
R.string.notif_push_status_text_timed_out,
PushStatusNotification.statusTextRes(PushMode.POLLING, timedOut = true),
)
}
@Test
fun `the timeout text wins even while push is nominally idle`() {
// The platform delivers the runtime-cap timeout while the service is still in IDLE mode, so
// timedOut must take precedence over the mode (#302).
assertEquals(
R.string.notif_push_status_text_timed_out,
PushStatusNotification.statusTextRes(PushMode.IDLE, timedOut = true),
)
}
}