I completed the CallKit Demo with the same code.
When I changed to LiveCommunicationKit, the code goes perfectly when the app is in foreground, but it crashed in background. If I changed the reportIncoming method from LCK to CallKit, it goes well. What is the reason?
I changed the method from
func pushRegistry(_ registry: PKPushRegistry,
didReceiveIncomingPushWith payload: PKPushPayload,
for type: PKPushType, completion: @escaping () -> Void)
to
func pushRegistry(_ registry: PKPushRegistry,
didReceiveIncomingPushWith payload: PKPushPayload,
for type: PKPushType) async
it crashed before show the print "receive voip noti".
Here is the core code:
var providerDelegate: ProviderDelegate?
func pushRegistry(_ registry: PKPushRegistry,
didReceiveIncomingPushWith payload: PKPushPayload,
for type: PKPushType, completion: @escaping () -> Void) {
if type != .voIP { return }
guard let uuidString = payload.dictionaryPayload["uuid"] as? String,
let uuid = UUID(uuidString: uuidString),
let handle = payload.dictionaryPayload["handle"] as? String,
let hasVideo = payload.dictionaryPayload["hasVideo"] as? Bool,
let callerID = payload.dictionaryPayload["callerID"] as? String else {
return
}
print("receive voip noti: \(type):\(payload.dictionaryPayload)")
if #available(iOS 17.4, *) {
// This code is only goes perfectly when the App is in foreground
var update = Conversation.Update(members: [Handle(type: .generic, value: callerID, displayName: callerID)])
if hasVideo {
update.capabilities = [.video, .playingTones]
} else {
update.capabilities = .playingTones
}
Task { @MainActor in
do {
print("LCKit report start")
try await LCKitManager.shared.reportNewIncomingConversation(uuid: uuid, update: update)
print("LCKit report success")
completion()
} catch {
print("LCKit report failed")
print(error)
completion()
}
}
} else {
// It went perfectly
providerDelegate?.reportIncomingCall(uuid: uuid, callerID: callerID, handle: handle, hasVideo: hasVideo) { _ in
completion()
}
}
@available(iOS 17.4, *)
final class LCKitManager {
static let shared = LCKitManager()
let manager: ConversationManager
init() {
manager = ConversationManager(configuration: type(of: self).configuration)
manager.delegate = self
}
static var configuration: ConversationManager.Configuration {
ConversationManager.Configuration(ringtoneName: "Ringtone.aif",
iconTemplateImageData: #imageLiteral(resourceName: "IconMask").pngData(),
maximumConversationGroups: 1,
maximumConversationsPerConversationGroup: 1,
includesConversationInRecents: true,
supportsVideo: false,
supportedHandleTypes: [.generic])
}
func reportNewIncomingConversation(uuid: UUID, update: Conversation.Update) async throws {
try await manager.reportNewIncomingConversation(uuid: uuid, update: update)
}
}
final class ProviderDelegate: NSObject, ObservableObject {
static let providerConfiguration: CXProviderConfiguration = {
let providerConfiguration: CXProviderConfiguration
if #available(iOS 14.0, *) {
providerConfiguration = CXProviderConfiguration()
} else {
providerConfiguration = CXProviderConfiguration(localizedName: "Name")
}
providerConfiguration.supportsVideo = false
providerConfiguration.maximumCallGroups = 1
providerConfiguration.maximumCallsPerCallGroup = 1
let iconMaskImage = #imageLiteral(resourceName: "IconMask")
providerConfiguration.iconTemplateImageData = iconMaskImage.pngData()
providerConfiguration.ringtoneSound = "Ringtone.aif"
providerConfiguration.includesCallsInRecents = true
providerConfiguration.supportedHandleTypes = [.generic]
return providerConfiguration
}()
private let provider: CXProvider
init( {
provider = CXProvider(configuration: type(of: self).providerConfiguration)
super.init()
provider.setDelegate(self, queue: nil)
}
func reportCall(uuid: UUID, callerID: String, handle: String, hasVideo: Bool, completion: ((Error?) -> Void)? = nil) {
let callerUUID = UUID()
let update = CXCallUpdate()
update.remoteHandle = CXHandle(type: .generic, value: callerID)
update.hasVideo = hasVideo
update.localizedCallerName = callerID
// Report the incoming call to the system
provider.reportNewIncomingCall(with: callerUUID, update: update) { [weak self] error in
completion?(error)
}
}
}
Expanding on what I said here:
The API contract fo PKPushRegistry specifically requires that you report a call before returning. Trying to bounce to the main thread like this means that you did NOT in fact do that.
The issue I was describing here wasn't about the specific thread, it was about separating the execution stream. In concrete term, using "Task" means "do this later" and you cannot do that here. Here is the code modified to remove Task:
@objc func startConversation(targetHandleValue : String,uuid : String, completion: @escaping (Bool, Error?) -> Void ) async {
....
let incomingCallerHandle = Handle(type: .generic, value: targetHandleValue ,displayName: targetHandleValue)
do {
try await conversationManager.reportNewIncomingConversation(uuid: conversationUUID, update: update)
completion(true, nil)
} catch {
print("An error occurred: \(error.localizedDescription)")
completion(false, error)
}
}
Covering a few other details:
In fact, the same processing logic works correctly in the background when using CallKit.
I'd be curious to see what your CallKit code looked like, but the "Task" usage above will crash CallKit in EXACTLY the same way LiveCommunicationKit is crashing.
When I switch to LiveCommunicationKit, It also displays normally when the application in the foreground, this issue only occurs in the background.
Yes, this is expected behavior in both CallKit and LiveCommunicationKit. App are NOT required to report calls if:
-
They are in the foreground.
-
They already have an active call.
Again, that behavior is exactly the same between CallKit and LiveCommunicationKit.
And the console only prints "Received push" without "LCKit Start Answer"
Yes, because that's what your code actually "does". Note that if you modified your code to print like this:
...
print("Received push")
// !!!!!!!
// Whatever I write `@MainActor in` or remove this code, Most of the time it's wrong when the app is in background
Task {
print("LCKit Start Answer")
....
}
print("After Task")
....
What will print in the foreground is:
Received push
After Task
LCKit Start Answer
And what will happen in the background is:
Received push
After Task
<App Crash>
...because you returned from didRecieveIncomingPush BEFORE you called reportNewIncomingConversation.
__
Kevin Elliott
DTS Engineer, CoreOS/Hardware