mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-02 21:33:26 +00:00
Listen to call decline to stop ringing when declined from other device (#4505)
* Listen to call decline to stop ringing when declined from other device * use proper swift naming convention for Id/ID * fix Force unwrapping * fix lint * An approach without the custom publisher. * review: correct naming convention * review: revert some invisible char/tab changes * add ref to the room proxy in the closure --------- Co-authored-by: Doug <douglase@element.io>
This commit is contained in:
@@ -9067,6 +9067,76 @@ class JoinedRoomProxyMock: JoinedRoomProxyProtocol, @unchecked Sendable {
|
|||||||
return declineCallNotificationIDReturnValue
|
return declineCallNotificationIDReturnValue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
//MARK: - subscribeToCallDeclineEvents
|
||||||
|
|
||||||
|
var subscribeToCallDeclineEventsRtcNotificationEventIDListenerUnderlyingCallsCount = 0
|
||||||
|
var subscribeToCallDeclineEventsRtcNotificationEventIDListenerCallsCount: Int {
|
||||||
|
get {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
return subscribeToCallDeclineEventsRtcNotificationEventIDListenerUnderlyingCallsCount
|
||||||
|
} else {
|
||||||
|
var returnValue: Int? = nil
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
returnValue = subscribeToCallDeclineEventsRtcNotificationEventIDListenerUnderlyingCallsCount
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnValue!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
subscribeToCallDeclineEventsRtcNotificationEventIDListenerUnderlyingCallsCount = newValue
|
||||||
|
} else {
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
subscribeToCallDeclineEventsRtcNotificationEventIDListenerUnderlyingCallsCount = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var subscribeToCallDeclineEventsRtcNotificationEventIDListenerCalled: Bool {
|
||||||
|
return subscribeToCallDeclineEventsRtcNotificationEventIDListenerCallsCount > 0
|
||||||
|
}
|
||||||
|
var subscribeToCallDeclineEventsRtcNotificationEventIDListenerReceivedArguments: (rtcNotificationEventID: String, listener: CallDeclineListener)?
|
||||||
|
var subscribeToCallDeclineEventsRtcNotificationEventIDListenerReceivedInvocations: [(rtcNotificationEventID: String, listener: CallDeclineListener)] = []
|
||||||
|
|
||||||
|
var subscribeToCallDeclineEventsRtcNotificationEventIDListenerUnderlyingReturnValue: Result<TaskHandle, RoomProxyError>!
|
||||||
|
var subscribeToCallDeclineEventsRtcNotificationEventIDListenerReturnValue: Result<TaskHandle, RoomProxyError>! {
|
||||||
|
get {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
return subscribeToCallDeclineEventsRtcNotificationEventIDListenerUnderlyingReturnValue
|
||||||
|
} else {
|
||||||
|
var returnValue: Result<TaskHandle, RoomProxyError>? = nil
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
returnValue = subscribeToCallDeclineEventsRtcNotificationEventIDListenerUnderlyingReturnValue
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnValue!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
subscribeToCallDeclineEventsRtcNotificationEventIDListenerUnderlyingReturnValue = newValue
|
||||||
|
} else {
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
subscribeToCallDeclineEventsRtcNotificationEventIDListenerUnderlyingReturnValue = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var subscribeToCallDeclineEventsRtcNotificationEventIDListenerClosure: ((String, CallDeclineListener) -> Result<TaskHandle, RoomProxyError>)?
|
||||||
|
|
||||||
|
func subscribeToCallDeclineEvents(rtcNotificationEventID: String, listener: CallDeclineListener) -> Result<TaskHandle, RoomProxyError> {
|
||||||
|
subscribeToCallDeclineEventsRtcNotificationEventIDListenerCallsCount += 1
|
||||||
|
subscribeToCallDeclineEventsRtcNotificationEventIDListenerReceivedArguments = (rtcNotificationEventID: rtcNotificationEventID, listener: listener)
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.subscribeToCallDeclineEventsRtcNotificationEventIDListenerReceivedInvocations.append((rtcNotificationEventID: rtcNotificationEventID, listener: listener))
|
||||||
|
}
|
||||||
|
if let subscribeToCallDeclineEventsRtcNotificationEventIDListenerClosure = subscribeToCallDeclineEventsRtcNotificationEventIDListenerClosure {
|
||||||
|
return subscribeToCallDeclineEventsRtcNotificationEventIDListenerClosure(rtcNotificationEventID, listener)
|
||||||
|
} else {
|
||||||
|
return subscribeToCallDeclineEventsRtcNotificationEventIDListenerReturnValue
|
||||||
|
}
|
||||||
|
}
|
||||||
//MARK: - matrixToPermalink
|
//MARK: - matrixToPermalink
|
||||||
|
|
||||||
var matrixToPermalinkUnderlyingCallsCount = 0
|
var matrixToPermalinkUnderlyingCallsCount = 0
|
||||||
|
|||||||
@@ -98,6 +98,10 @@ extension SDKListener: RoomInfoListener where T == RoomInfo {
|
|||||||
func call(roomInfo: RoomInfo) { onUpdateClosure(roomInfo) }
|
func call(roomInfo: RoomInfo) { onUpdateClosure(roomInfo) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension SDKListener: CallDeclineListener where T == String {
|
||||||
|
func call(declinerUserId: String) { onUpdateClosure(declinerUserId) }
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: TimelineProxy
|
// MARK: TimelineProxy
|
||||||
|
|
||||||
extension SDKListener: PaginationStatusListener where T == RoomPaginationStatus {
|
extension SDKListener: PaginationStatusListener where T == RoomPaginationStatus {
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import AVFoundation
|
|||||||
import CallKit
|
import CallKit
|
||||||
import Combine
|
import Combine
|
||||||
import Foundation
|
import Foundation
|
||||||
|
import MatrixRustSDK
|
||||||
import PushKit
|
import PushKit
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
@@ -41,14 +42,14 @@ class ElementCallService: NSObject, ElementCallServiceProtocol, PKPushRegistryDe
|
|||||||
// There's a race condition where a call starts when the app has been killed and the
|
// 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.
|
// 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).
|
// So observe when the client proxy is set to fix this (the method guards for the call).
|
||||||
Task { await observeIncomingCallRoomInfo() }
|
Task { await observeIncomingCall() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private var incomingCallRoomInfoCancellable: AnyCancellable?
|
private var incomingCallRoomInfoCancellable: AnyCancellable?
|
||||||
private var incomingCallID: CallID? {
|
private var incomingCallID: CallID? {
|
||||||
didSet {
|
didSet {
|
||||||
Task { await observeIncomingCallRoomInfo() }
|
Task { await observeIncomingCall() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,6 +69,8 @@ class ElementCallService: NSObject, ElementCallServiceProtocol, PKPushRegistryDe
|
|||||||
actionsSubject.eraseToAnyPublisher()
|
actionsSubject.eraseToAnyPublisher()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var declineListenerHandle: TaskHandle?
|
||||||
|
|
||||||
override init() {
|
override init() {
|
||||||
pushRegistry = PKPushRegistry(queue: nil)
|
pushRegistry = PKPushRegistry(queue: nil)
|
||||||
|
|
||||||
@@ -305,7 +308,7 @@ class ElementCallService: NSObject, ElementCallServiceProtocol, PKPushRegistryDe
|
|||||||
_ = await roomProxy.declineCall(notificationID: rtcNotificationID)
|
_ = await roomProxy.declineCall(notificationID: rtcNotificationID)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func observeIncomingCallRoomInfo() async {
|
private func observeIncomingCall() async {
|
||||||
incomingCallRoomInfoCancellable = nil
|
incomingCallRoomInfoCancellable = nil
|
||||||
|
|
||||||
guard let incomingCallID else {
|
guard let incomingCallID else {
|
||||||
@@ -341,17 +344,44 @@ class ElementCallService: NSObject, ElementCallServiceProtocol, PKPushRegistryDe
|
|||||||
|
|
||||||
if !hasOngoingCall {
|
if !hasOngoingCall {
|
||||||
MXLog.info("Call cancelled by remote")
|
MXLog.info("Call cancelled by remote")
|
||||||
|
reportEndedCall(incomingCallID: incomingCallID, reason: .remoteEnded)
|
||||||
incomingCallRoomInfoCancellable = nil
|
|
||||||
endUnansweredCallTask?.cancel()
|
|
||||||
callProvider.reportCall(with: incomingCallID.callKitID, endedAt: nil, reason: .remoteEnded)
|
|
||||||
} else if participants.contains(roomProxy.ownUserID) {
|
} else if participants.contains(roomProxy.ownUserID) {
|
||||||
MXLog.info("Call answered elsewhere")
|
MXLog.info("Call answered elsewhere")
|
||||||
|
reportEndedCall(incomingCallID: incomingCallID, reason: .answeredElsewhere)
|
||||||
incomingCallRoomInfoCancellable = nil
|
|
||||||
endUnansweredCallTask?.cancel()
|
|
||||||
callProvider.reportCall(with: incomingCallID.callKitID, endedAt: nil, reason: .answeredElsewhere)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
guard let rtcNotificationID = incomingCallID.rtcNotificationID else {
|
||||||
|
MXLog.warning("Decline: No RTC notification ID found for the incoming call.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
MXLog.info("Observe decline events for notification \(rtcNotificationID)")
|
||||||
|
|
||||||
|
let listener: CallDeclineListener = SDKListener { [weak self] senderID in
|
||||||
|
guard let self else { return }
|
||||||
|
|
||||||
|
MXLog.debug("Call declined event received from \(senderID)")
|
||||||
|
|
||||||
|
if senderID == roomProxy.ownUserID {
|
||||||
|
// Stop ringing!
|
||||||
|
MXLog.debug("Call declined elsewhere")
|
||||||
|
reportEndedCall(incomingCallID: incomingCallID, reason: .declinedElsewhere)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
guard case let .success(handle) = roomProxy.subscribeToCallDeclineEvents(rtcNotificationEventID: rtcNotificationID, listener: listener) else {
|
||||||
|
MXLog.error("Unable to listen for decline events.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
declineListenerHandle = handle
|
||||||
|
}
|
||||||
|
|
||||||
|
private func reportEndedCall(incomingCallID: CallID, reason: CXCallEndedReason) {
|
||||||
|
declineListenerHandle?.cancel()
|
||||||
|
declineListenerHandle = nil
|
||||||
|
endUnansweredCallTask?.cancel()
|
||||||
|
callProvider.reportCall(with: incomingCallID.callKitID, endedAt: nil, reason: reason)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -649,6 +649,17 @@ class JoinedRoomProxy: JoinedRoomProxyProtocol {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Subscribe to call decline events from that rtc notification event.
|
||||||
|
func subscribeToCallDeclineEvents(rtcNotificationEventID: String, listener: CallDeclineListener) -> Result<TaskHandle, RoomProxyError> {
|
||||||
|
do {
|
||||||
|
let handle = try room.subscribeToCallDeclineEvents(rtcNotificationEventId: rtcNotificationEventID, listener: listener)
|
||||||
|
return .success(handle)
|
||||||
|
} catch {
|
||||||
|
MXLog.error("Failed observing rtc decline with error: \(error)")
|
||||||
|
return .failure(.sdkError(error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Permalinks
|
// MARK: - Permalinks
|
||||||
|
|
||||||
func matrixToPermalink() async -> Result<URL, RoomProxyError> {
|
func matrixToPermalink() async -> Result<URL, RoomProxyError> {
|
||||||
|
|||||||
@@ -64,6 +64,13 @@ enum KnockRequestsState {
|
|||||||
case loaded([KnockRequestProxyProtocol])
|
case loaded([KnockRequestProxyProtocol])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct RTCDeclinedEvent {
|
||||||
|
/// The sender of the decline event
|
||||||
|
let sender: String
|
||||||
|
/// The rtc.notification event that is beeing declined
|
||||||
|
let notificationEventID: String
|
||||||
|
}
|
||||||
|
|
||||||
// sourcery: AutoMockable
|
// sourcery: AutoMockable
|
||||||
protocol JoinedRoomProxyProtocol: RoomProxyProtocol {
|
protocol JoinedRoomProxyProtocol: RoomProxyProtocol {
|
||||||
var infoPublisher: CurrentValuePublisher<RoomInfoProxyProtocol, Never> { get }
|
var infoPublisher: CurrentValuePublisher<RoomInfoProxyProtocol, Never> { get }
|
||||||
@@ -170,6 +177,7 @@ protocol JoinedRoomProxyProtocol: RoomProxyProtocol {
|
|||||||
|
|
||||||
func elementCallWidgetDriver(deviceID: String) -> ElementCallWidgetDriverProtocol
|
func elementCallWidgetDriver(deviceID: String) -> ElementCallWidgetDriverProtocol
|
||||||
func declineCall(notificationID: String) async -> Result<Void, RoomProxyError>
|
func declineCall(notificationID: String) async -> Result<Void, RoomProxyError>
|
||||||
|
func subscribeToCallDeclineEvents(rtcNotificationEventID: String, listener: CallDeclineListener) -> Result<TaskHandle, RoomProxyError>
|
||||||
|
|
||||||
// MARK: - Permalinks
|
// MARK: - Permalinks
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user