Skip to content

Commit b9edb32

Browse files
committed
Listen to call decline to stop ringing when declined from other device
1 parent b480695 commit b9edb32

File tree

4 files changed

+196
-3
lines changed

4 files changed

+196
-3
lines changed

ElementX/Sources/Mocks/Generated/GeneratedMocks.swift

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9067,6 +9067,76 @@ class JoinedRoomProxyMock: JoinedRoomProxyProtocol, @unchecked Sendable {
90679067
return declineCallNotificationIDReturnValue
90689068
}
90699069
}
9070+
//MARK: - callDeclineEventPublisher
9071+
9072+
var callDeclineEventPublisherNotificationIdUnderlyingCallsCount = 0
9073+
var callDeclineEventPublisherNotificationIdCallsCount: Int {
9074+
get {
9075+
if Thread.isMainThread {
9076+
return callDeclineEventPublisherNotificationIdUnderlyingCallsCount
9077+
} else {
9078+
var returnValue: Int? = nil
9079+
DispatchQueue.main.sync {
9080+
returnValue = callDeclineEventPublisherNotificationIdUnderlyingCallsCount
9081+
}
9082+
9083+
return returnValue!
9084+
}
9085+
}
9086+
set {
9087+
if Thread.isMainThread {
9088+
callDeclineEventPublisherNotificationIdUnderlyingCallsCount = newValue
9089+
} else {
9090+
DispatchQueue.main.sync {
9091+
callDeclineEventPublisherNotificationIdUnderlyingCallsCount = newValue
9092+
}
9093+
}
9094+
}
9095+
}
9096+
var callDeclineEventPublisherNotificationIdCalled: Bool {
9097+
return callDeclineEventPublisherNotificationIdCallsCount > 0
9098+
}
9099+
var callDeclineEventPublisherNotificationIdReceivedRtcNotificationEventId: String?
9100+
var callDeclineEventPublisherNotificationIdReceivedInvocations: [String] = []
9101+
9102+
var callDeclineEventPublisherNotificationIdUnderlyingReturnValue: AnyPublisher<RtcDeclinedEvent, Never>!
9103+
var callDeclineEventPublisherNotificationIdReturnValue: AnyPublisher<RtcDeclinedEvent, Never>! {
9104+
get {
9105+
if Thread.isMainThread {
9106+
return callDeclineEventPublisherNotificationIdUnderlyingReturnValue
9107+
} else {
9108+
var returnValue: AnyPublisher<RtcDeclinedEvent, Never>? = nil
9109+
DispatchQueue.main.sync {
9110+
returnValue = callDeclineEventPublisherNotificationIdUnderlyingReturnValue
9111+
}
9112+
9113+
return returnValue!
9114+
}
9115+
}
9116+
set {
9117+
if Thread.isMainThread {
9118+
callDeclineEventPublisherNotificationIdUnderlyingReturnValue = newValue
9119+
} else {
9120+
DispatchQueue.main.sync {
9121+
callDeclineEventPublisherNotificationIdUnderlyingReturnValue = newValue
9122+
}
9123+
}
9124+
}
9125+
}
9126+
var callDeclineEventPublisherNotificationIdClosure: ((String) -> AnyPublisher<RtcDeclinedEvent, Never>)?
9127+
9128+
func callDeclineEventPublisher(notificationId rtcNotificationEventId: String) -> AnyPublisher<RtcDeclinedEvent, Never> {
9129+
callDeclineEventPublisherNotificationIdCallsCount += 1
9130+
callDeclineEventPublisherNotificationIdReceivedRtcNotificationEventId = rtcNotificationEventId
9131+
DispatchQueue.main.async {
9132+
self.callDeclineEventPublisherNotificationIdReceivedInvocations.append(rtcNotificationEventId)
9133+
}
9134+
if let callDeclineEventPublisherNotificationIdClosure = callDeclineEventPublisherNotificationIdClosure {
9135+
return callDeclineEventPublisherNotificationIdClosure(rtcNotificationEventId)
9136+
} else {
9137+
return callDeclineEventPublisherNotificationIdReturnValue
9138+
}
9139+
}
90709140
//MARK: - matrixToPermalink
90719141

90729142
var matrixToPermalinkUnderlyingCallsCount = 0

ElementX/Sources/Services/ElementCall/ElementCallService.swift

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import AVFoundation
99
import CallKit
1010
import Combine
1111
import Foundation
12+
import MatrixRustSDK
1213
import PushKit
1314
import UIKit
1415

@@ -48,7 +49,10 @@ class ElementCallService: NSObject, ElementCallServiceProtocol, PKPushRegistryDe
4849
private var incomingCallRoomInfoCancellable: AnyCancellable?
4950
private var incomingCallID: CallID? {
5051
didSet {
51-
Task { await observeIncomingCallRoomInfo() }
52+
Task {
53+
await observeIncomingCallRoomInfo()
54+
await observeDeclineEvents()
55+
}
5256
}
5357
}
5458

@@ -68,6 +72,8 @@ class ElementCallService: NSObject, ElementCallServiceProtocol, PKPushRegistryDe
6872
actionsSubject.eraseToAnyPublisher()
6973
}
7074

75+
private var declineListenerCancellable: AnyCancellable?
76+
7177
override init() {
7278
pushRegistry = PKPushRegistry(queue: nil)
7379

@@ -354,4 +360,43 @@ class ElementCallService: NSObject, ElementCallServiceProtocol, PKPushRegistryDe
354360
}
355361
}
356362
}
363+
364+
private func observeDeclineEvents() async {
365+
guard let incomingCallID else {
366+
MXLog.info("Decline: No incoming call to observe for.")
367+
return
368+
}
369+
370+
guard let clientProxy else {
371+
MXLog.warning("Decline: A ClientProxy is needed to fetch the room.")
372+
return
373+
}
374+
375+
guard let rtcNotificationId = incomingCallID.rtcNotificationId else {
376+
MXLog.warning("Decline: No RTC notification ID found for the incoming call.")
377+
return
378+
}
379+
380+
guard case let .joined(roomProxy) = await clientProxy.roomForIdentifier(incomingCallID.roomID) else {
381+
MXLog.warning("Failed to fetch a joined room for the incoming call.")
382+
return
383+
}
384+
let ownUserId = clientProxy.userID
385+
386+
MXLog.info("Observe decline events for notification \(rtcNotificationId)")
387+
388+
declineListenerCancellable = roomProxy
389+
.callDeclineEventPublisher(notificationId: rtcNotificationId)
390+
.sink { ev in
391+
MXLog.debug("Call declined event received from \(ev.sender)")
392+
if ev.sender == ownUserId {
393+
// Stop ringing!
394+
MXLog.debug("Call declined elsewhere")
395+
self.declineListenerCancellable?.cancel()
396+
self.declineListenerCancellable = nil
397+
self.endUnansweredCallTask?.cancel()
398+
self.callProvider.reportCall(with: incomingCallID.callKitID, endedAt: nil, reason: .declinedElsewhere)
399+
}
400+
}
401+
}
357402
}

ElementX/Sources/Services/Room/JoinedRoomProxy.swift

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ class JoinedRoomProxy: JoinedRoomProxyProtocol {
7070
var knockRequestsStatePublisher: CurrentValuePublisher<KnockRequestsState, Never> {
7171
knockRequestsStateSubject.asCurrentValuePublisher()
7272
}
73-
73+
7474
init(roomListService: RoomListServiceProtocol,
7575
room: RoomProtocol,
7676
appSettings: AppSettings) async throws {
@@ -649,6 +649,22 @@ class JoinedRoomProxy: JoinedRoomProxyProtocol {
649649
}
650650
}
651651

652+
/// Subscribe to call decline events from that rtc notification event.
653+
func callDeclineEventPublisher(notificationId rtcNotificationEventId: String) -> AnyPublisher<RtcDeclinedEvent, Never> {
654+
let publisher = DeclineCallbackPublisher(room: self, eventId: rtcNotificationEventId)
655+
return publisher.eraseToAnyPublisher()
656+
}
657+
658+
func subscribeToCallDeclineEvents(rtcNotificationEventId: String, listener: RoomCallDeclineListener) -> Result<TaskHandle, RoomProxyError> {
659+
do {
660+
let handle = try room.subscribeToCallDeclineEvents(rtcNotificationEventId: rtcNotificationEventId, listener: listener)
661+
return .success(handle)
662+
} catch {
663+
MXLog.error("Failed observing rtc decline with error: \(error)")
664+
return .failure(.sdkError(error))
665+
}
666+
}
667+
652668
// MARK: - Permalinks
653669

654670
func matrixToPermalink() async -> Result<URL, RoomProxyError> {
@@ -815,3 +831,57 @@ private final class RoomKnockRequestsListener: KnockRequestsListener {
815831
onUpdateClosure(joinRequests)
816832
}
817833
}
834+
835+
final class RoomCallDeclineListener: CallDeclineListener {
836+
private let onUpdateClosure: (RtcDeclinedEvent) -> Void
837+
private let notificationId: String
838+
839+
init(notificationId: String, onUpdateClosure: @escaping (RtcDeclinedEvent) -> Void) {
840+
self.notificationId = notificationId
841+
self.onUpdateClosure = onUpdateClosure
842+
}
843+
844+
func call(declinerUserId: String) {
845+
onUpdateClosure(.init(sender: declinerUserId, notificationEventId: notificationId))
846+
}
847+
}
848+
849+
// Helper to transform callback to publisher while correctly retaining the task handle and listener.
850+
struct DeclineCallbackPublisher: Publisher {
851+
typealias Output = RtcDeclinedEvent
852+
typealias Failure = Never
853+
854+
let room: JoinedRoomProxy
855+
let eventId: String
856+
857+
func receive<S>(subscriber: S) where S: Subscriber, Never == S.Failure, RtcDeclinedEvent == S.Input {
858+
let subscription = Inner(subscriber: subscriber, room: room, eventId: eventId)
859+
subscriber.receive(subscription: subscription)
860+
}
861+
862+
private final class Inner<S: Subscriber>: Subscription
863+
where S.Input == RtcDeclinedEvent, S.Failure == Never {
864+
private var subscriber: S?
865+
private var handle: TaskHandle?
866+
private var listener: RoomCallDeclineListener?
867+
868+
init(subscriber: S, room: JoinedRoomProxy, eventId: String) {
869+
self.subscriber = subscriber
870+
listener = RoomCallDeclineListener(notificationId: eventId) { [weak self] ev in
871+
_ = self?.subscriber?.receive(ev)
872+
}
873+
handle = try? room.subscribeToCallDeclineEvents(rtcNotificationEventId: eventId, listener: listener!).get()
874+
}
875+
876+
func request(_ demand: Subscribers.Demand) {
877+
// nop
878+
}
879+
880+
func cancel() {
881+
handle?.cancel()
882+
handle = nil
883+
listener = nil
884+
subscriber = nil
885+
}
886+
}
887+
}

ElementX/Sources/Services/Room/RoomProxyProtocol.swift

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,13 @@ enum KnockRequestsState {
6464
case loaded([KnockRequestProxyProtocol])
6565
}
6666

67+
struct RtcDeclinedEvent {
68+
/// The sender of the decline event
69+
let sender: String
70+
/// The rtc.notification event that is beeing declined
71+
let notificationEventId: String
72+
}
73+
6774
// sourcery: AutoMockable
6875
protocol JoinedRoomProxyProtocol: RoomProxyProtocol {
6976
var infoPublisher: CurrentValuePublisher<RoomInfoProxyProtocol, Never> { get }
@@ -75,7 +82,7 @@ protocol JoinedRoomProxyProtocol: RoomProxyProtocol {
7582
var identityStatusChangesPublisher: CurrentValuePublisher<[IdentityStatusChange], Never> { get }
7683

7784
var knockRequestsStatePublisher: CurrentValuePublisher<KnockRequestsState, Never> { get }
78-
85+
7986
var timeline: TimelineProxyProtocol { get }
8087

8188
var predecessorRoom: PredecessorRoom? { get }
@@ -170,6 +177,7 @@ protocol JoinedRoomProxyProtocol: RoomProxyProtocol {
170177

171178
func elementCallWidgetDriver(deviceID: String) -> ElementCallWidgetDriverProtocol
172179
func declineCall(notificationID: String) async -> Result<Void, RoomProxyError>
180+
func callDeclineEventPublisher(notificationId rtcNotificationEventId: String) -> AnyPublisher<RtcDeclinedEvent, Never>
173181

174182
// MARK: - Permalinks
175183

0 commit comments

Comments
 (0)