fix(monitor): Stop re-promoting a stale notification into a new session

This commit is contained in:
darken
2026-07-30 12:47:23 +02:00
parent b616696a41
commit 54d3c9d824
2 changed files with 31 additions and 1 deletions
@@ -184,7 +184,15 @@ class MonitorService : Service() {
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
// Every startForegroundService() re-arms the startForeground() obligation,
// so every onStartCommand has to satisfy it again, no matter how it exits.
if (!promoteToForeground(lastNotification ?: MonitorNotifications.createEarlyNotification(this))) {
//
// Only reuse the cached notification while a monitor session is actually live. It is a fully
// built object whose `when` is frozen at build time, so re-promoting it to open a NEW session
// would re-post the previous session's last content under its original timestamp — which is
// how a stale "unknown device" frame survives a teardown/restart cycle. Rebuilding is not an
// option here: this has to stay cheap enough to satisfy the obligation before DI is ready.
val reusable = lastNotification?.takeIf { monitoringJob?.isActive == true }
if (reusable == null) lastNotification = null
if (!promoteToForeground(reusable ?: MonitorNotifications.createEarlyNotification(this))) {
stopSelf(startId)
return START_NOT_STICKY
}
@@ -125,6 +125,28 @@ class MonitorServiceTest {
shadowOf(service).lastForegroundNotificationId shouldBe MonitorNotifications.NOTIFICATION_ID
}
/**
* A built Notification's `when` is frozen, so re-promoting the previous session's last frame to
* open a new one re-posts stale content under its original timestamp — the way an "unknown
* device" placeholder survives a teardown/restart cycle.
*/
@Test
fun `a start with no live monitor does not reuse the previous notification`() {
val service = createService()
service.readyForMonitoring()
val stale = notification("stale")
service.postPrimaryNotification(stale)
// Monitor session has ended — the cached frame no longer reflects anything current.
service.setField("monitoringJob", null)
service.onStartCommand(MonitorService.intent(context), 0, 1) shouldBe Service.START_STICKY
shadowOf(service).lastForegroundNotification shouldNotBeSameInstanceAs stale
service.getField("lastNotification") shouldNotBeSameInstanceAs stale
}
/**
* The FGS notification can outlive `stopSelf()`. Leaving it up strands whatever content was last
* posted — including the unknown-device placeholder built before the first BLE scan batch landed.