Fix ringing calls not stopping when the other user cancels the call (#4613)

* Fix ringing calls not being automatically canceled

This will keep the sync active while the user is screening an incoming call, allowing receiving a call cancellation event, which will terminate the incoming call ringing early.

* Add extra logs to help debugging ringing call issues.
This commit is contained in:
Jorge Martin Espinosa 2025-04-22 13:30:10 +02:00 committed by GitHub
parent 75cb3156a1
commit 3b35d96e1a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 112 additions and 17 deletions

View file

@ -78,15 +78,21 @@ class SyncOrchestrator @AssistedInject constructor(
internal fun observeStates() = coroutineScope.launch {
Timber.tag(tag).d("start observing the app and network state")
val isAppActiveFlow = combine(
appForegroundStateService.isInForeground,
appForegroundStateService.isInCall,
appForegroundStateService.isSyncingNotificationEvent,
appForegroundStateService.hasRingingCall,
) { isInForeground, isInCall, isSyncingNotificationEvent, hasRingingCall ->
isInForeground || isInCall || isSyncingNotificationEvent || hasRingingCall
}
combine(
// small debounce to avoid spamming startSync when the state is changing quickly in case of error.
syncService.syncState.debounce(100.milliseconds),
networkMonitor.connectivity,
appForegroundStateService.isInForeground,
appForegroundStateService.isInCall,
appForegroundStateService.isSyncingNotificationEvent,
) { syncState, networkState, isInForeground, isInCall, isSyncingNotificationEvent ->
val isAppActive = isInForeground || isInCall || isSyncingNotificationEvent
isAppActiveFlow,
) { syncState, networkState, isAppActive ->
val isNetworkAvailable = networkState == NetworkStatus.Connected
Timber.tag(tag).d("isAppActive=$isAppActive, isNetworkAvailable=$isNetworkAvailable")

View file

@ -236,6 +236,50 @@ class SyncOrchestratorTest {
stopSyncRecorder.assertions().isCalledOnce()
}
@Test
fun `when the app was in background and we have an incoming ringing call, a sync will be started`() = runTest {
val startSyncRecorder = lambdaRecorder<Result<Unit>> { Result.success(Unit) }
val stopSyncRecorder = lambdaRecorder<Result<Unit>> { Result.success(Unit) }
val syncService = FakeSyncService(initialSyncState = SyncState.Idle).apply {
startSyncLambda = startSyncRecorder
stopSyncLambda = stopSyncRecorder
}
val networkMonitor = FakeNetworkMonitor(initialStatus = NetworkStatus.Connected)
val appForegroundStateService = FakeAppForegroundStateService(
initialForegroundValue = false,
initialIsSyncingNotificationEventValue = false,
initialHasRingingCall = false,
)
val syncOrchestrator = createSyncOrchestrator(
syncService = syncService,
networkMonitor = networkMonitor,
appForegroundStateService = appForegroundStateService,
)
// We start observing
syncOrchestrator.observeStates()
// Advance the time to make sure the orchestrator has had time to start processing the inputs
advanceTimeBy(100.milliseconds)
// Start sync was never called
startSyncRecorder.assertions().isNeverCalled()
// Now we receive a ringing call
appForegroundStateService.updateHasRingingCall(true)
// Start sync will be called shortly after
advanceTimeBy(1.milliseconds)
startSyncRecorder.assertions().isCalledOnce()
// If the sync is running and the ringing call notification is now over, the sync stops after a delay
syncService.emitSyncState(SyncState.Running)
appForegroundStateService.updateHasRingingCall(false)
advanceTimeBy(10.seconds)
stopSyncRecorder.assertions().isCalledOnce()
}
@Test
fun `when the app is in foreground, we sync for a notification and a call is ongoing, the sync will only stop when all conditions are false`() = runTest {
val startSyncRecorder = lambdaRecorder<Result<Unit>> { Result.success(Unit) }