fix: stop call ringing when Ended state is missed due to StateFlow conflation

The state collector in CallController was blocking on
showIncomingCallNotification() which downloads the caller's profile
picture over the network. Because StateFlow is conflated, if the call
ended (peer hangup, reject, timeout) while the collector was suspended,
the Ended→Idle transition would cause the Ended emission to be lost.
Since cleanup/stopRinging only ran in the Ended handler, the ringtone
and vibration would continue indefinitely.

Two fixes:
1. Launch showIncomingCallNotification in a separate coroutine so the
   collector is never blocked by network I/O.
2. Add a safety-net Idle handler that stops ringing, ringback tone,
   and cancels the call notification.

https://claude.ai/code/session_01NfyLNgR4d8yjnxaQJ6FUt5
This commit is contained in:
Claude
2026-04-03 17:31:06 +00:00
parent 5250ef0abd
commit a1f8817fce
@@ -155,7 +155,13 @@ class CallController(
when (state) {
is CallState.IncomingCall -> {
withContext(Dispatchers.IO) { audioManager.startRinging() }
showIncomingCallNotification(state.callerPubKey)
// Launch notification in a separate coroutine so that
// long-running network I/O (profile picture download)
// does not block the state collector. StateFlow is
// conflated — if the collector is suspended when the
// state transitions Ended → Idle, the Ended emission
// is lost and cleanup/stopRinging never runs.
scope.launch { showIncomingCallNotification(state.callerPubKey) }
}
is CallState.Offering -> {
@@ -178,7 +184,14 @@ class CallController(
cleanup()
}
else -> {}
is CallState.Idle -> {
// Safety net: ensure ringing and notifications are
// stopped even if the Ended state was missed due to
// StateFlow conflation.
audioManager.stopRinging()
audioManager.stopRingbackTone()
NotificationUtils.cancelCallNotification(context)
}
}
}
}