Skip to content

Commit

Permalink
Fix a couple of race conditions when observing room info updates for …
Browse files Browse the repository at this point in the history
…calls. (#3487)

* Fix a race condition observing room info updates for calls.

* Fix a bug where call observation wasn't set up if the call comes when the app has been killed.
  • Loading branch information
pixlwave authored Nov 5, 2024
1 parent c54e4bf commit 5fc8cac
Showing 1 changed file with 30 additions and 21 deletions.
51 changes: 30 additions & 21 deletions ElementX/Sources/Services/ElementCall/ElementCallService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,19 @@ class ElementCallService: NSObject, ElementCallServiceProtocol, PKPushRegistryDe
return CXProvider(configuration: configuration)
}()

private weak var clientProxy: ClientProxyProtocol?
private weak var clientProxy: ClientProxyProtocol? {
didSet {
// There's a race condition where a call starts when the app has been killed and the
// observation set in `incomingCallID` occurs *before* the user session is restored.
// So observe when the client proxy is set to fix this (the method guards for the call).
Task { await observeIncomingCallRoomInfo() }
}
}

private var cancellables = Set<AnyCancellable>()
private var incomingCallRoomInfoCancellable: AnyCancellable?
private var incomingCallID: CallID? {
didSet {
Task {
await observeIncomingCallRoomStateUpdates()
}
Task { await observeIncomingCallRoomInfo() }
}
}

Expand Down Expand Up @@ -260,7 +265,7 @@ class ElementCallService: NSObject, ElementCallServiceProtocol, PKPushRegistryDe

// MARK: - Private

func tearDownCallSession(sendEndCallAction: Bool = true) {
private func tearDownCallSession(sendEndCallAction: Bool = true) {
if sendEndCallAction, let ongoingCallID {
let transaction = CXTransaction(action: CXEndCallAction(call: ongoingCallID.callKitID))
callController.request(transaction) { error in
Expand All @@ -273,30 +278,35 @@ class ElementCallService: NSObject, ElementCallServiceProtocol, PKPushRegistryDe
ongoingCallID = nil
}

func observeIncomingCallRoomStateUpdates() async {
cancellables.removeAll()
private func observeIncomingCallRoomInfo() async {
incomingCallRoomInfoCancellable = nil

guard let clientProxy, let incomingCallID else {
guard let incomingCallID else {
MXLog.info("No incoming call to observe for.")
return
}

guard let clientProxy else {
MXLog.warning("A ClientProxy is needed to fetch the room.")
return
}

guard case let .joined(roomProxy) = await clientProxy.roomForIdentifier(incomingCallID.roomID) else {
MXLog.warning("Failed to fetch a joined room for the incoming call.")
return
}

roomProxy.subscribeToRoomInfoUpdates()

// There's no incoming event for call cancellations so try to infer
// it from what we have. If the call is running before subscribing then wait
// for it to change to `false` otherwise wait for it to turn `true` before
// changing to `false`
let isCallOngoing = roomProxy.infoPublisher.value.hasRoomCall

roomProxy
incomingCallRoomInfoCancellable = roomProxy
.infoPublisher
.compactMap { ($0.hasRoomCall, $0.activeRoomCallParticipants) }
.removeDuplicates { $0 == $1 }
.dropFirst(isCallOngoing ? 0 : 1)
.drop(while: { hasRoomCall, _ in
// Filter all updates before hasRoomCall becomes `true`. Then we can correctly
// detect its change to `false` to stop ringing when the caller hangs up.
!hasRoomCall
})
.sink { [weak self] hasOngoingCall, activeRoomCallParticipants in
guard let self else { return }

Expand All @@ -305,17 +315,16 @@ class ElementCallService: NSObject, ElementCallServiceProtocol, PKPushRegistryDe
if !hasOngoingCall {
MXLog.info("Call cancelled by remote")

cancellables.removeAll()
incomingCallRoomInfoCancellable = nil
endUnansweredCallTask?.cancel()
callProvider.reportCall(with: incomingCallID.callKitID, endedAt: nil, reason: .remoteEnded)
} else if participants.contains(roomProxy.ownUserID) {
MXLog.info("Call anwered elsewhere")
MXLog.info("Call answered elsewhere")

cancellables.removeAll()
incomingCallRoomInfoCancellable = nil
endUnansweredCallTask?.cancel()
callProvider.reportCall(with: incomingCallID.callKitID, endedAt: nil, reason: .answeredElsewhere)
}
}
.store(in: &cancellables)
}
}

0 comments on commit 5fc8cac

Please sign in to comment.