Skip to content

Commit

Permalink
Cleanup how we setup the CallKit provider and have it be used for out…
Browse files Browse the repository at this point in the history
…going calls as well
  • Loading branch information
stefanceriu committed Jun 25, 2024
1 parent fa22b98 commit a498328
Show file tree
Hide file tree
Showing 4 changed files with 85 additions and 60 deletions.
32 changes: 16 additions & 16 deletions ElementX/Sources/Mocks/Generated/GeneratedMocks.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4697,44 +4697,44 @@ class ElementCallServiceMock: ElementCallServiceProtocol {

//MARK: - setupCallSession

var setupCallSessionTitleUnderlyingCallsCount = 0
var setupCallSessionTitleCallsCount: Int {
var setupCallSessionRoomIDRoomDisplayNameUnderlyingCallsCount = 0
var setupCallSessionRoomIDRoomDisplayNameCallsCount: Int {
get {
if Thread.isMainThread {
return setupCallSessionTitleUnderlyingCallsCount
return setupCallSessionRoomIDRoomDisplayNameUnderlyingCallsCount
} else {
var returnValue: Int? = nil
DispatchQueue.main.sync {
returnValue = setupCallSessionTitleUnderlyingCallsCount
returnValue = setupCallSessionRoomIDRoomDisplayNameUnderlyingCallsCount
}

return returnValue!
}
}
set {
if Thread.isMainThread {
setupCallSessionTitleUnderlyingCallsCount = newValue
setupCallSessionRoomIDRoomDisplayNameUnderlyingCallsCount = newValue
} else {
DispatchQueue.main.sync {
setupCallSessionTitleUnderlyingCallsCount = newValue
setupCallSessionRoomIDRoomDisplayNameUnderlyingCallsCount = newValue
}
}
}
}
var setupCallSessionTitleCalled: Bool {
return setupCallSessionTitleCallsCount > 0
var setupCallSessionRoomIDRoomDisplayNameCalled: Bool {
return setupCallSessionRoomIDRoomDisplayNameCallsCount > 0
}
var setupCallSessionTitleReceivedTitle: String?
var setupCallSessionTitleReceivedInvocations: [String] = []
var setupCallSessionTitleClosure: ((String) async -> Void)?
var setupCallSessionRoomIDRoomDisplayNameReceivedArguments: (roomID: String, roomDisplayName: String)?
var setupCallSessionRoomIDRoomDisplayNameReceivedInvocations: [(roomID: String, roomDisplayName: String)] = []
var setupCallSessionRoomIDRoomDisplayNameClosure: ((String, String) async -> Void)?

func setupCallSession(title: String) async {
setupCallSessionTitleCallsCount += 1
setupCallSessionTitleReceivedTitle = title
func setupCallSession(roomID: String, roomDisplayName: String) async {
setupCallSessionRoomIDRoomDisplayNameCallsCount += 1
setupCallSessionRoomIDRoomDisplayNameReceivedArguments = (roomID: roomID, roomDisplayName: roomDisplayName)
DispatchQueue.main.async {
self.setupCallSessionTitleReceivedInvocations.append(title)
self.setupCallSessionRoomIDRoomDisplayNameReceivedInvocations.append((roomID: roomID, roomDisplayName: roomDisplayName))
}
await setupCallSessionTitleClosure?(title)
await setupCallSessionRoomIDRoomDisplayNameClosure?(roomID, roomDisplayName)
}
//MARK: - tearDownCallSession

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ class CallScreenViewModel: CallScreenViewModelType, CallScreenViewModelProtocol
return
}

await elementCallService.setupCallSession(title: roomProxy.roomTitle)
await elementCallService.setupCallSession(roomID: roomProxy.id, roomDisplayName: roomProxy.roomTitle)

let _ = await roomProxy.sendCallNotificationIfNeeeded()
}
Expand Down
109 changes: 67 additions & 42 deletions ElementX/Sources/Services/ElementCall/ElementCallService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ class ElementCallService: NSObject, ElementCallServiceProtocol, PKPushRegistryDe
private var callProvider: CXProvider?
private var ongoingCallID: UUID?

private var incomingCallRoomID: String?
private var ongoingCallRoomID: String?

private var endUnansweredCallTask: Task<Void, Never>?

Expand All @@ -46,15 +46,14 @@ class ElementCallService: NSObject, ElementCallServiceProtocol, PKPushRegistryDe
pushRegistry.desiredPushTypes = [.voIP]
}

func setupCallSession(title: String) async {
guard ongoingCallID == nil else {
return
}

func setupCallSession(roomID: String, roomDisplayName: String) async {
let callID = UUID()
ongoingCallID = callID
ongoingCallRoomID = roomID

let handle = CXHandle(type: .generic, value: title)
callProvider = setupCallProvider(callID: callID, roomID: roomID, roomDisplayName: roomDisplayName, isCallIncoming: false)

let handle = CXHandle(type: .generic, value: roomDisplayName)
let startCallAction = CXStartCallAction(call: callID, handle: handle)
startCallAction.isVideo = true

Expand Down Expand Up @@ -85,6 +84,10 @@ class ElementCallService: NSObject, ElementCallServiceProtocol, PKPushRegistryDe
MXLog.error("Failed transaction with error: \(error)")
}
}

self.ongoingCallID = nil
ongoingCallRoomID = nil
callProvider = nil
}

// MARK: - PKPushRegistryDelegate
Expand All @@ -99,45 +102,28 @@ class ElementCallService: NSObject, ElementCallServiceProtocol, PKPushRegistryDe

let callID = UUID()
ongoingCallID = callID
ongoingCallRoomID = roomID

incomingCallRoomID = roomID

let configuration = CXProviderConfiguration()
configuration.supportsVideo = true
configuration.includesCallsInRecents = true
// Provide image icon if available
configuration.iconTemplateImageData = nil

// https://stackoverflow.com/a/46077628/730924
configuration.supportedHandleTypes = [.generic]

let update = CXCallUpdate()
update.hasVideo = true
let roomDisplayName = payload.dictionaryPayload[ElementCallServiceNotificationKey.roomDisplayName.rawValue] as? String

update.localizedCallerName = payload.dictionaryPayload[ElementCallServiceNotificationKey.roomDisplayName.rawValue] as? String

// https://stackoverflow.com/a/41230020/730924
update.remoteHandle = .init(type: .generic, value: roomID)

let callProvider = CXProvider(configuration: configuration)
callProvider.setDelegate(self, queue: nil)
callProvider.reportNewIncomingCall(with: callID, update: update) { error in
if let error {
MXLog.error("Failed reporting new incoming call with error: \(error)")
}

callProvider = setupCallProvider(callID: callID, roomID: roomID, roomDisplayName: roomDisplayName, isCallIncoming: true) {
completion()
}

endUnansweredCallTask = Task { [weak self, callProvider, callID] in
endUnansweredCallTask = Task { [weak self] in
try? await Task.sleep(for: .seconds(15))
guard !Task.isCancelled else {

guard let self, !Task.isCancelled else {
return
}

if let ongoingCallID = self?.ongoingCallID, ongoingCallID == callID {
callProvider.reportCall(with: callID, endedAt: .now, reason: .unanswered)
if let ongoingCallID, ongoingCallID == callID {
callProvider?.reportCall(with: callID, endedAt: .now, reason: .unanswered)
}

ongoingCallID = nil
ongoingCallRoomID = nil
callProvider = nil
}
}

Expand All @@ -148,12 +134,12 @@ class ElementCallService: NSObject, ElementCallServiceProtocol, PKPushRegistryDe
}

func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {
if let incomingCallRoomID {
if let ongoingCallRoomID {
Task {
// Dispatch to next run loop so it doesn't conflict with `setupCallSession`
actionsSubject.send(.answerCall(roomID: incomingCallRoomID))
actionsSubject.send(.answerCall(roomID: ongoingCallRoomID))
}
self.incomingCallRoomID = nil
self.ongoingCallRoomID = nil
endUnansweredCallTask?.cancel()
} else {
MXLog.error("Failed answering incoming call, missing room ID")
Expand All @@ -163,13 +149,52 @@ class ElementCallService: NSObject, ElementCallServiceProtocol, PKPushRegistryDe
}

func provider(_ provider: CXProvider, perform action: CXEndCallAction) {
if let incomingCallRoomID {
actionsSubject.send(.declineCall(roomID: incomingCallRoomID))
self.incomingCallRoomID = nil
if let ongoingCallRoomID {
actionsSubject.send(.declineCall(roomID: ongoingCallRoomID))
self.ongoingCallRoomID = nil
} else {
MXLog.error("Failed declining incoming call, missing room ID")
}

action.fulfill()
}

// MARK: - Private

private func setupCallProvider(callID: UUID, roomID: String, roomDisplayName: String?, isCallIncoming: Bool, completion: (() -> Void)? = nil) -> CXProvider {
let configuration = CXProviderConfiguration()
configuration.supportsVideo = true
configuration.includesCallsInRecents = true
// Provide image icon if available
configuration.iconTemplateImageData = nil

// https://stackoverflow.com/a/46077628/730924
configuration.supportedHandleTypes = [.generic]

let callProvider = CXProvider(configuration: configuration)
callProvider.setDelegate(self, queue: nil)

if isCallIncoming {
let update = CXCallUpdate()
update.hasVideo = true

update.localizedCallerName = roomDisplayName

// https://stackoverflow.com/a/41230020/730924
update.remoteHandle = .init(type: .generic, value: roomID)

callProvider.reportNewIncomingCall(with: callID, update: update) { error in
if let error {
MXLog.error("Failed reporting new incoming call with error: \(error)")
}

completion?()
}
} else {
callProvider.reportOutgoingCall(with: callID, connectedAt: .now)
completion?()
}

return callProvider
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ let ElementCallServiceNotificationDiscardDelta = 10.0
protocol ElementCallServiceProtocol {
var actions: AnyPublisher<ElementCallServiceAction, Never> { get }

func setupCallSession(title: String) async
func setupCallSession(roomID: String, roomDisplayName: String) async

func tearDownCallSession()
}

0 comments on commit a498328

Please sign in to comment.