3

We have an iOS app that is configured to receive VoIP notifications through the PushKit framework. When a VoIP notification arrives, we immediately report the incoming call to CallKit (as per iOS 13 guidelines) with the following code:

func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType, completion: @escaping () -> Void) {
        let backgroundTaskIdentifier = UIApplication.shared.beginBackgroundTask(expirationHandler: nil)
        self.callsManager.handleIncomingNotification(with: payload.dictionaryPayload, backgroundTaskIdentifier: backgroundTaskIdentifier, completion: completion)
    }

CallsManager is a singleton class that handles the incoming notification as follows:

        let call = addOrUpdateCall(
            service: TwilioService(),
            token: voipNotification.token,
            in: voipNotification.room,
            with: voipNotification.nickname,
            uuid: UUID())

        if call.ring() != .none {
            let update = CXCallUpdate()
            update.remoteHandle = CXHandle(type: .emailAddress, value: voipNotification.email)
            update.hasVideo = voipNotification.hasVideo
            update.supportsDTMF = false
            update.supportsHolding = false
            update.supportsGrouping = false
            update.supportsUngrouping = false
            update.localizedCallerName = voipNotification.nickname

            self.callProvider?.reportNewIncomingCall(with: call.uuid, update: update, completion: { (error) in
               completion()

               if let error = error {
                  self.serialQueue.async {
                      switch error {
                        case CXErrorCodeIncomingCallError.filteredByDoNotDisturb, CXErrorCodeIncomingCallError.filteredByBlockList:
                           _ = call.decline()
                        default:
                           call.fail(error: error)
                      }
                   }
               }
           })
       }

Basically, we are extracting the necessary info from the notification and we're immediately displaying the incoming call screen.

The addOrUpdateCall function just makes sure that in the array of calls that we have in memory, we are not storing duplicate calls.

Normally, this works perfectly: the CallKit incoming call screen is displayed and the phone starts ringing. But sometimes, in a very sporadic way, the CallKit screen is displayed for a second and then disappears behind the app UI.

This only happens when the app is in foreground or the phone is unlocked, it does not happen if the phone is locked.

After many many attempts, we've found a somewhat systematic way of reproducing the issue using two iOS devices:

  1. From one device, call the other one and hang up after a few seconds.
  2. Repeat this three times in a row, leaving a few seconds between each call.
  3. Call from the other device: on the first device, the CallKit UI is displayed for a second and then hidden behind the app, but the device keeps ringing and vibrating.

We don't have any log from iOS and the phone keeps ringing and vibrating while the app UI is in front. The only way to restore it is by entering in the multi-tasking and re-selecting the app.

I've already tried to look for a workaround, where I continuously call reportNewIncomingCall every second, hoping that this would bring the CallKit UI in front. It seems to alleviate the issue, but it still happens every once in a while.

We've tried on multiple iPhone models and it seems that the iPhone XS and the iPhone XS Max are not affected (the CallKit UI appears and hides after a second, but it comes back automatically, even without the workaround above), but we were able to reproduce the issue on an iPhone 7, two iPhone 6S and an iPhone 6S Plus.

Can anybody help me?

MrAsterisco
  • 797
  • 8
  • 21
  • Did you get a fix for this? – SuddenMoustache Jul 10 '20 at 13:19
  • 1
    @Matt I did not. I pushed the workaround a little bit further, using a timer that checks every second if the CallKit UI is in front (aka. the app is not in foreground) and when it detects that it's not (aka. the app is now in foreground), it dismisses the call and calls `reportNewIncomingCall` once again. – MrAsterisco Jul 12 '20 at 08:59
  • 1. Make sure you do all correct in a sequence of CallKit calls (invocations). It may be due to incorrect call end. – Alexander Volkov Oct 01 '20 at 08:04
  • 2. Try to set `supportsHolding = true`. I'm not sure, but probably the previous call is still "in" the system (although you stopped it in the app) and CallKit may find that it's not possible to show another call because one of them cannot be hold. – Alexander Volkov Oct 01 '20 at 08:15
  • 3. Check all in app messaging - if you are using p2p messages to initiate a call, then may be you end the call in the code if incorrect message is received. Or for example, you receive push notification + in-app message to initiate a call, and they are called one-by-one. I noted that if correct sequence of CallKit API calls is wrong, CallKit became "crazy" and only killing the app resets something in CallKit and new calls work again. Correct sequences are like: you started, then you have to end the call, never call `reportNewIncomingCall` twice. – Alexander Volkov Oct 01 '20 at 08:15
  • 4. One more thing. Even you have a singleton class CallsManager, make sure you have a NON-weak reference to it in one of the view top controllers which are never dismissed. I know that static var like `static var shared = CallsManager()` should keep it in memory, but just make a reference for sure. – Alexander Volkov Oct 01 '20 at 08:15
  • 5. Just an observation. I don't see you save `call` in `reportNewIncomingCall` completion handler. How later do you refer to `call`, e.g. to end it? If you store it before `reportNewIncomingCall` call, then that's wrong. CallKit may ignore your call if it's not possible to display it (it may happen I believe). – Alexander Volkov Oct 01 '20 at 08:25
  • 6. Make sure you invoke method that uses `reportNewIncomingCall` "wrapped" into a background task: `UIApplication.shared.beginBackgroundTask(...`. User may close your app before answering or cancelling the call. – Alexander Volkov Oct 01 '20 at 08:30
  • 2
    @AlexanderVolkov thanks for your suggestions. Unfortunately, this question is very old and I don't work on this app anymore. I can just say, for anybody having the same problem, that newer versions of iOS 13 (I think starting from 13.2) fixed the problem. – MrAsterisco Oct 01 '20 at 09:23
  • We had a similar issue. Newer version of iOS 13 fixed the issue. – David Feb 01 '21 at 02:28

0 Answers0