iOS 13 PushKit VoIP restrictions breaking SIP VoIP apps

Hi,


We are a fairly large company providing smartphone and desktop applications for VoIP/UC together with PBX systems, on-premise as well as clourd offerings. All our app are talking to the telephony servers over SIP.

Up unitl now, our flow on iOS was:

  1. Receive PushKit VoIP notification
  2. REGISTER towards telephony server
  3. Receive INVITE
  4. reportIncomingCall


With the changes enforced and outlined in https://developer.apple.com/videos/play/wwdc2019/707/, the whole SIP concept will break.

We are now forced to report an incoming call so early that the user might accept the call before the REGISTER has completed and an INVITE has been received which will lead to a pretty bad user experience.

Even worse, we will report an incoming call even if there is no actual call. This might happen as the call has already been canceled on the remote end and we won't get an INVITE. Or if the registration fails due to network issues that prevents us from reaching the telephony server.


How does Apple expect us to deal with these situations?

It's not possible to perform the whole REGISTER/INVITE stuff "in the same run loop as pushRegistry:didReceiveIncomingPushWithPayload:forType:[withCompletionHandler:] without delay."


I'm not sure what our support and sales guys will do when they receive negative customer feedback with the changed implementation. Maybe tell existing and new customers to use Android smartphones instead of iPhones...


There has to be at least some way of delaying the reportIncomingCall so that an application can check against the telephony server if there is a call and report if there is none to avoid punishment for not reporting the call.

Accepted Reply

A lot of different issues have been raised in this thread, so I've loosely summarized them to the following 4 questions issues:



1) Reporting a call immediately does not work with the SIP register-invite flow


On iOS 13, there are cases where you will need to initiate a CallKit call that you previously would have silently ignored. However, in practice, this should not be the common case, particularly for truly “non-existent" calls, since that would mean you had been notifying the user of calls that did not exist.


The more common cases here are calls that have either already ended (because the caller hung up) or can't be completed (because network conditions prevent the call from connecting). While calls will be created for both of these cases, there are few different techniques that you can use to mitigate any disruptions:


While you must report an incoming call immediately, you are still free to decide the call has failed and tell the system later, asynchronously. To do this, call reportCallWithUUID:endedAtDate:reason:. This will tear down the incoming call UI even if the user has not answered the call.


If the user is particularly fast at tapping the accept call button, or if the network conditions are poor or there is otherwise a lot of latency, then you should simply wait until the necessary handshakes are complete before calling fulfill on the CXAnswerCallAction. To the user, it simply appears that the call is taking some time to connect, which is a common experience even with standard phone calls.


Note that the system takes a few seconds for the incoming call UI to animate in, during which the app has the opportunity to complete this handshake, so this will only have a user-visible impact if it takes a significant time for the handshake to complete.


At any time, you can asynchronously update the UI with the reportCallWithUUID:updated: API. That means that if you cannot put the caller ID info in the push payload, you can simply choose to present dummy information (like "Connecting Call..." for the caller name) and update it asynchronously once they get the real information from your server.


2) Sending a push to cancel an incoming call


While your app currently has an active call (ringing or answered), your app is not required to create additional calls for VoIP pushes received during this call. This is intended to be used to support advanced functionality like dynamic call priority, but it could also be used to cancel an incoming call.


Having said that, this is not an approach I would recommend or rely upon. The reality is that as soon as your app receives it's PushKit notification it should be connecting to your server, so unless network conditions are very poor, you should be able to communicate to your client through that connection faster than PushKit. More to the point, if network conditions mean that you can't connect to your server, then trying to handle this with another push isn't a great idea either, since poor connectivity (and timing generally) opens the door to edge cases you'd want to avoid - for example, a client receiving ONLY the cancelation and not the original call notification.


As a side note here, keep in mind that as part of adapting to the new requirements you'll want to make sure the VoIP notification has a short or zero "apns-expiration" to prevent newly available devices from being notified of out-of-date calls. This will also minimize the cancelation issue, since a “newly available device" (for example, a phone that was just powered on) will ONLY receive notifications about calls that are occurring at that particular instant.


3) Block-lists/Do Not Disturb


CallKit respects the system Do Not Disturb setting, so most apps will not have to worry about system-level Do Not Disturb functionality. If your app has it's own blocking/do not disturb system built in, you can also maintain that list server-side and not send pushes to the blocked devices. If you absolutely need to do "local" call blocking, then you can report a call and then end it. The call will be briefly visible to the user, but you can also configure the source of the call to communicate what's going on ("Blocked Call...").


4) Using VoIP pushes to trigger syncs or other non-VoIP use cases


VoIP pushes were always intended to specifically support call notifications and nothing else. The good news here is that using Notification Service Extension is the best substitute for most of the functionality that you previously handled with PushKit:


https://developer.apple.com/documentation/usernotifications/unnotificationserviceextension


A few examples:

  • For general messaging, you can connect to your server in the extension delegate, download any missed messages, and then update the notification content as appropriate.
  • The same approach can be used to tell the user about calls missed while the device was offline and/or pending voicemails.
  • For non-user facing functionality, like data synchronization or other app maintenance, I would recommend checking out the new BackgroundTasks framework:


https://developer.apple.com/documentation/backgroundtasks


Kevin Elliott

Developer Technical Support

Core OS/Hardware

Replies

How much is it fare to change the whole PBX programming? As some PBXes updates the clients at regular interval. If not responded back, deregisters the client. Re- Registering the client on wakeup with all subscribe and notify cycles, removes the generated incoming call. Please answer how to deal with these type of Situations.

Hi,

No answers but some more information.

We're experiencing all the same issues. Besides the risk of ghost calls we're finding that limiting the PushKit notifications is preventing us from implementing features of some other well known messaging/voip apps; exactly as others have mentioned.

One feature in particular for us kicked this off. How to mute a group chat/channel and not show any notifications. Yes we could have the server do it but it seemed simpler to have the client do it. So we started investigating how other apps did this. It lead to something interesting.

WhatsApp is a great example of a feature-rich messaging app and we always start these conversations with "what does WhatsApp do". When we looked we saw that it is using PushKit notifications in a seemingly unrestricted fashion. What does that mean? It appears to be using PushKit notifications for sending messages. Something that I'd have considered impossible given the restrictions in place. So we thought maybe it's still using Xcode10? We find that highly unlikely given the looming cut-off date. So a bit of digging (not that much when you consider the link and the question I asked)

https://stackoverflow.com/questions/60815763/how-does-the-whatsapp-web-client-still-work-with-the-latest-ios-update-sdk-vers

This specific entitlement seems to be the reason that they are allowed to not report an incoming call:
com.apple.developer.pushkit.unrestricted-voip
I can't find any documentation regarding this entitlement, so I'm pretty sure they have been given a special permission by Apple.

So we started looking for this entitlement. Lookup source-code-walkthrough-of-telegram-ios-part-3-other-foundations-66ace05954a4 (the editor is refusing to let me post the URL)

As any abuse could cause a significant battery drain problem, Apple started to require apps to invoke CallKit after receiving VoIP notifications since the iOS SDK 13. But Telegram-iOS seems to survive from the new rule as it has got a special entitlement from Apple: com.apple.developer.pushkit.unrestricted-voip. The same undocumented entitlement can also be found in SignalApp.

So there does appear to be an entitlement available that provides seemingly unrestricted use of PushKit.

I completely understand why Apple wanted to restrict this feature but they should have vetted the apps and allowed access to trustworthy apps in a fashion similar to Critical Alerts. I'd be very happy to explain why we needed unrestricted voip and be prepared to justify it every so often. I'm slightly annoyed, but not surprised, that some apps seem to receive more access to the platform.

So I suggest you start asking your nearest Apple dealer about com.apple.developer.pushkit.unrestricted-voip.

Regards





I install the APNS profile in my device and find an interesting thing on Skype. Skype seems use VoIP Pushed to send an incoming call push and send a cancel incoming call push.

StartRing
<APSCourier: 0x10f006b50>: Received message for enabled topic 'com.skype.skype.voip' onInterface: NonCellular with payload '{
data = {
callId = "c598173b-df7d-45d3-a55e-2189e925d3c3";
callerId = "live:ea8284b6bd6f5a73";
convoId = "live:ea8284b6bd6f5a73";
cv = "FAQOTTnKxEu9h3uB.2";
displayName = "*";
eventType = 107;
participantId = "a6559d1b-eefb-4f1d-b6a4-d1bd033759a6";
pnhTime = "2020-12-23T03:19:03.1157560Z";
recipientId = "
*";
servicePayload = {
};
videoCall = false;
};
}' with priority 10 for device token: NO

StopRing:
<APSCourier: 0x10f006b50>: Received message for enabled topic 'com.skype.skype.voip' onInterface: NonCellular with payload '{
data = {
callId = "c598173b-df7d-45d3-a55e-2189e925d3c3";
callerMri = "8:live:ea8284b6bd6f5a73";
conversationId = "live:ea8284b6bd6f5a73";
cv = "oqCcnoRdo0KzXnD2.2";
eventType = 110;
participantId = "a6559d1b-eefb-4f1d-b6a4-d1bd033759a6";
pnhTime = "2020-12-23T03:19:47.5174961Z";
reason = NoAnswerCallerCancelled;
recipientId = "live:rcqa001";
};
}' with priority 10 for device token: NO

That's weird. Because it's impossible to report incoming call for the stopring. How can Skype manage to escape the punishment? Do they have the something like "com.apple.developer.pushkit.unrestricted-voip"? Can someone help to answer this?

On iOS 13.0 and later, if you fail to report a call to CallKit, the system will terminate your app. Repeatedly failing to report calls may cause the system to stop delivering any more VoIP push notifications to your app. If you want to initiate a VoIP call without using CallKit, register for push notifications using the User Notifications framework instead of PushKit. For more information, see User Notifications.
Hi,

Is there a way to get com.apple.developer.pushkit.unrestricted-voip entitlement ?

@langaCom

"While you must report an incoming call immediately, you are still free to decide the call has failed and tell the system later, asynchronously. To do this, call reportCallWithUUID:endedAtDate:reason:. This will tear down the incoming call UI even if the user has not answered the call."

But the call screen UI will still be displayed, even if just momentarily. This makes it look scrappy, messy, broken, unprofessional.