Skip to content

Commit 8c03a07

Browse files
authored
fix(call): handle incoming call hangup when signaling is unavailable (WT-1080) (#1106)
* fix(call): [WT-1080] placeholder — implement incoming call hangup when signaling unavailable * fix(call): send decline when incoming call registration fails (WT-1080) Path 1 (_onCallPushEventIncoming): iOS CXProvider may reject incoming call registration before the user sees it (DND, blocklist, unentitled). Since signaling is disconnected at push-receive time we cannot decline immediately. The server replays the incoming event on next handshake reconnect, which re-enters Path 2 where the SIP line is now known. Replaced the silent drop with a descriptive log and explanation of the recovery path. Path 2 (__onCallSignalingEventIncoming): when reportNewIncomingCall returns an unexpected error (callRejectedBySystem on Android, unknown/internal on iOS) send a DeclineRequest immediately. The call was never shown to the user so performEndCall will not fire. _signalingModule.execute returns null when disconnected — the ?. operator handles that safely.
1 parent 389b8a2 commit 8c03a07

1 file changed

Lines changed: 43 additions & 4 deletions

File tree

lib/features/call/bloc/call_bloc.dart

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -774,8 +774,25 @@ class CallBloc extends Bloc<CallEvent, CallState> with WidgetsBindingObserver im
774774
Future<void> _onCallPushEventIncoming(_CallPushEventIncoming event, Emitter<CallState> emit) async {
775775
final eventError = event.error;
776776
if (eventError != null) {
777-
_logger.warning('_onCallPushEventIncoming event.error: $eventError');
778-
// TODO: implement correct incoming call hangup (take into account that _signalingClient is disconnected)
777+
// iOS only: CXProvider rejected the incoming call registration before it was
778+
// ever presented to the user (e.g. DND / Focus active, Call Directory blocklist,
779+
// missing VoIP entitlement, or unexpected CXProvider failure).
780+
//
781+
// Consequences:
782+
// - performEndCall will NOT fire (CallKit never registered the call).
783+
// - _signalingModule is very likely disconnected: VoIP push wakes the app
784+
// before the WebSocket is established, so the CXProvider completion fires
785+
// before signaling reconnects.
786+
//
787+
// Recovery path: when signaling reconnects the server replays the incoming
788+
// call event via the handshake. HandshakeProcessor generates a
789+
// HandleIncomingCallAction for the unknown callId, which re-enters
790+
// __onCallSignalingEventIncoming. At that point we have the SIP line and
791+
// can send a DeclineRequest immediately (see that method below).
792+
_logger.warning(
793+
'_onCallPushEventIncoming: OS rejected call registration '
794+
'(callId: ${event.callId}, error: $eventError) — server will be notified on next handshake',
795+
);
779796
return;
780797
}
781798

@@ -950,8 +967,30 @@ class CallBloc extends Bloc<CallEvent, CallState> with WidgetsBindingObserver im
950967
final callAlreadyTerminated = error == CallkeepIncomingCallError.callIdAlreadyTerminated;
951968

952969
if (error != null && !callAlreadyExists && !callAlreadyAnswered && !callAlreadyTerminated) {
953-
_logger.warning('__onCallSignalingEventIncoming reportNewIncomingCall error: $error');
954-
// TODO: implement correct incoming call hangup (take into account that _signalingClient could be disconnected)
970+
// reportNewIncomingCall rejected the call with an unexpected error:
971+
// - Android: callRejectedBySystem — Telecom already has a call in RINGING state
972+
// (AOSP behaviour, Android 11+), or the 5 s Telecom confirmation timeout elapsed.
973+
// - iOS: unknown / unentitled / internal — rare CXProvider failure on the
974+
// signaling-path reportNewIncomingCall (not the VoIP-push path).
975+
//
976+
// The call was never presented to the user, so performEndCall will NOT fire.
977+
// Notify the server immediately so the remote party is not left ringing.
978+
// _signalingModule.execute returns null when disconnected — the ?. handles that safely.
979+
_logger.warning(
980+
'__onCallSignalingEventIncoming: reportNewIncomingCall error=$error '
981+
'(callId: ${event.callId}, line: ${event.line}) — sending decline',
982+
);
983+
await _signalingModule
984+
.execute(
985+
DeclineRequest(
986+
transaction: WebtritSignalingClient.generateTransactionId(),
987+
line: event.line,
988+
callId: event.callId,
989+
),
990+
)
991+
?.catchError((e, s) {
992+
callErrorReporter.handle(e, s, '__onCallSignalingEventIncoming declineRequest error');
993+
});
955994
return;
956995
}
957996

0 commit comments

Comments
 (0)