LiveCommunicationKit: report Incoming but Assertion failure in -[PKPushRegistry _terminateAppIfThereAreUnhandledVoIPPushes]

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)
        }
    }
}

You may not understand my question very well. My question is that ending/canceling a message does not match the method of adjusting the system's answer interface, which can cause a crash. I have tried removing the task directly, but it will crash on my end. It seems that Pushkit's method must be matched with the method of adjusting the system's answer interface?

how can me change livekit speaker's status,from in app to change

when i receive end/cancle voip notif,it

Voip pushes should ONLY be used for call notifications and nothing else. If you choose to use them for any other purpose (like call cancelation) then you risk triggering multiple incoming all reports, since EVERY call to "didReceiveIncomingPushWith" MUST also contain a call to "reportNewIncomingConversation".

Note that because the call reporting system allows you to skip reporting a call when a call is already active, it's very easy to create a solution that appear to work under "basic" testing but will fail badly under real world conditions.

I follow the method as discussing in this thread. and get through the problem which was boring me for some days. I think you can try just await the "reportNewIncomingConversation" like:

try await self.mgr.reportNewIncomingConversation(uuid: self.currentCallId, update: update) instead of run it in a Task.

No, this does NOT work. The issue here is that "try await" can only be called inside an async function. In concrete terms, there are two declarations of "didReceiveIncomingPushWith":

  1. Completion Handler
optional func pushRegistry(
    _ registry: PKPushRegistry,
    didReceiveIncomingPushWith payload: PKPushPayload,
    for type: PKPushType,
    completion: @escaping () -> Void
)

  1. Async
optional func pushRegistry(
    _ registry: PKPushRegistry,
    didReceiveIncomingPushWith payload: PKPushPayload,
    for type: PKPushType
) async

...and using "try await" requires using #2. However, the problem with #2 is that, architecturally, an async function like this IMMEDIATELY returns and does not block, which causes the current PushKit check to crash your app. Using #1 avoids that issue, but it also means that you cannot use "try await", which is why I used task.

I do in this way and it woks well now.

I would strongly recommend that you review your code and testing process again. More specifically, I would recommend building a dedicated test app, replicating the crashing case in that test app and then validating your code in the context of that test app. The threading and testing issue involved here are complex enough that it's VERY possible for code to "accidently" work, only to fail later.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

LiveCommunicationKit: report Incoming but Assertion failure in -[PKPushRegistry _terminateAppIfThereAreUnhandledVoIPPushes]
 
 
Q