fix(monitor): Use AAP message time for Last Seen while connected

This commit is contained in:
darken
2026-04-08 19:21:19 +02:00
committed by Matthias Urhahn
parent 87d366bcb7
commit f2bf39bda1
3 changed files with 44 additions and 1 deletions
@@ -64,7 +64,8 @@ data class PodDevice(
val hasDualMicrophone: Boolean get() = ble is HasDualMicrophone
// Signal / timing
val seenLastAt: Instant? get() = ble?.seenLastAt ?: cached?.lastSeenAt
val seenLastAt: Instant?
get() = listOfNotNull(ble?.seenLastAt, aap?.lastMessageAt, cached?.lastSeenAt).maxOrNull()
val seenFirstAt: Instant? get() = ble?.seenFirstAt
val signalQuality: Float
get() {
@@ -108,6 +108,21 @@ class PodDeviceCacheTest : BaseTest() {
device.seenLastAt shouldBe fiveMinAgo
}
@Test
fun `seenLastAt prefers live AAP message over stale cache`() {
// Reproduces the reported bug: while AAP is connected, iOS throttles BLE advertising.
// After BlePodMonitor evicts the BLE snapshot, the merged device had ble=null and the
// getter fell through to the stale cache, showing "hours ago" even though AAP traffic
// was still flowing. Live AAP messages must win over any cached timestamp.
val freshAapMessage = Instant.parse("2026-03-31T11:59:55Z") // 5s ago, newer than cache
val aap = AapPodState(
connectionState = AapPodState.ConnectionState.READY,
lastMessageAt = freshAapMessage,
)
val device = PodDevice(profileId = "test-profile", ble = null, aap = aap, cached = cachedState)
device.seenLastAt shouldBe freshAapMessage
}
@Test
fun `profileId falls back to cache`() {
val device = PodDevice(profileId = "test-profile", ble = null, aap = null, cached = cachedState)
@@ -143,6 +143,33 @@ class PodDeviceTest : BaseTest() {
device.rssi shouldBe 0
}
@Test
fun `seenLastAt uses AAP lastMessageAt when BLE is null`() {
val aapMessageAt = Instant.parse("2026-04-01T12:00:00Z")
val aap = AapPodState(
connectionState = AapPodState.ConnectionState.READY,
lastMessageAt = aapMessageAt,
)
val device = PodDevice(profileId = null, ble = null, aap = aap)
device.seenLastAt shouldBe aapMessageAt
}
@Test
fun `seenLastAt picks max of BLE and AAP timestamps`() {
val bleSeenAt = Instant.parse("2026-04-01T12:00:00Z")
val aapMessageAt = bleSeenAt.plusSeconds(25) // AAP is more recent
val ble = mockk<DualApplePods>(relaxed = true) {
every { model } returns PodModel.AIRPODS_PRO3
every { seenLastAt } returns bleSeenAt
}
val aap = AapPodState(
connectionState = AapPodState.ConnectionState.READY,
lastMessageAt = aapMessageAt,
)
val device = PodDevice(profileId = null, ble = ble, aap = aap)
device.seenLastAt shouldBe aapMessageAt
}
@Test
fun `charging properties delegate to BLE interfaces`() {
val mock = mockk<DualApplePods>(relaxed = true) {