iOS 13 Incoming Call UI goes to Recents - ios

I'm developing VoIP based audio call in my application. I have a strange issue for which I couldn't find a solution.
For iOS 13+ devices, sometimes incoming CallKit UI goes in Background. That means incoming CallKit UI doesn't show upfront, but I'm able to hear the call ringtone audio and vibration. When I double-tap on the Home button, I'm able to see my app with the IncomingCall UI in Recents. When I tap on it, it shows the CallKit UI and then I'm not able to move to other applications via double-tapping the home button.
It's inconsistently happening on iOS 13+ versions. Is there any way to show CallKit UI prominently while receiving incoming calls?
I'm using the method below to show an incoming call.
let update = CXCallUpdate()
update.remoteHandle = CXHandle(type: .generic, value: callObj.name ?? "")
update.hasVideo = false
provider.reportNewIncomingCall(with: newUUID, update: update) { error in
if error == nil {
let call = Call(uuid: newUUID, handle: callObj.name ?? "", roomID: self.callObj?.roomId ?? "")
self.callManager.add(call: call)
}
completion?(error as NSError?)
}

Related

How to show incoming CallKit window for VoIP push call even if the device "Do not Disturb" is enabled?

We have an application that has CallKit feature. One problem I am facing is that if the user sets Device Do not Disturb mode on, then CallKit incoming notification is not shown if the device is locked.
There is a CallKit error that is CXErrorCodeIncomingCallErrorFilteredByDoNotDisturb when the device is in this mode, but I want to still show the notification to the user if a call arrives.
Note: I've found that WhatsApp still shows the incoming CallKit notification even when DND is enabled. Any help/suggestion will be appreciated.
If you want to display a notification when you receive a CXErrorCodeIncomingCallErrorFilteredByDoNotDisturb error, you could do the following:
cxProvider.reportNewIncomingCall(
with: aCallId,
update: vCallUpdate,
completion: { error in
guard let vError = error as? CXErrorCodeIncomingCallError else { return }
if vError.code == .filteredByDoNotDisturb {
let content = UNMutableNotificationContent()
content.title = "Call"
// ...
let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: nil)
UNUserNotificationCenter.current().add(request) { error in
if let vError = error {
print(vError.localizedDescription)
}
}
}
})
When do-not-disturb is active, WhatsApp doesn't display a notification after receiving an audio call, it only displays notifications for video calls. That's because they use CallKit and PushKit only for audio calls. For video calls they use normal push notifications.

How to end a CallKit call from custom UI without having caller UUID?

I working on a video call application in iOS Swift 5, where call can only initiate from the backend web app and the mobile app can only answer to those calls,. Means there is no mobile-mobile communication, communication between web app and mobile app. In my application I'm using PushKit with CallKit for notifying the incoming call in the background or killed state. So in the background or killed state, if I get an incoming call, it will show the calling screen using CallKit and if I pressed Answer button, it will navigate to my own custom video call screen where I have end button for dismissing the call. But when I press the end button, the VoIP call get disconnected. But the CallKit call is not dismissing(Still show the green bar on the top of Homescreen). And I checked for how ending the CallKit call via code, but in most of the solution the CallKit is dismissing with the use of callUUID. But I don't know from where I will get that UUID. In some code I saw the UUID is received from the push payload, but in my case the call is initiated from the web app, so I'm not receiving the caller UUID. Please help me.
This is my code,
func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType, completion: #escaping () -> Void) {
print(payload.dictionaryPayload)
let state = UIApplication.shared.applicationState
if state == .background || state == .inactive {
let update = CXCallUpdate()
update.remoteHandle = CXHandle(type: .generic, value: callerName)
update.hasVideo = true
provider.reportNewIncomingCall(with: UUID(), update: update, completion: { error in })
} else if state == .active {
// no need for calling screen
}
}
And I tired the following code to end the call, but not working.
func endCall(call: UUID) {
let callController = CXCallController()
let endCallAction = CXEndCallAction(call: call)
let transaction = CXTransaction(action: endCallAction)
callController.request(transaction) { error in
if let error = error {
print("EndCallAction transaction request failed: \(error.localizedDescription).")
self.provider.reportCall(with: call, endedAt: Date(), reason: .remoteEnded)
return
}
print("EndCallAction transaction request successful")
}
Here I'm passing call as current UUID, then I'm getting error response as
EndCallAction transaction request failed: The operation couldn’t be
completed.
The uuid value is one that you provide.
You are providing it here:
provider.reportNewIncomingCall(with: UUID(),...
Because you are simply allocating a new UUID and passing it directly in reportNewIncomingCall you don't know the uuid when you need it later.
You need to store this uuid in a property so that you can provide it to your endCall function.

CallKit screen goes behind the app UI while ringing

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:
From one device, call the other one and hang up after a few seconds.
Repeat this three times in a row, leaving a few seconds between each call.
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?

How to handle a video call through voip and call kit

I'm new to Apples callKit and Pushkit. i'm using OpenTok in my application for video and audio call handling. To handle native like calling in my app i'm using VOIP with callkit . Audio native call is working fine, When user interacts with the native UI of callkit it goes to background the application gets to foreground. Has i looked into speaker box of apple documentation about call kit. It has some Intent handlers to handle calls
Please can anyone Help me out of by giving any idea about handling video and audio calls natively
Thanks in advance..
I'm doing the same with OpenTok. As far as I'm aware you can't handle video calls natively from the lock screen, however you can use OpenTok with CallKit for just audio. See this link
CallKit have a property supportsVideo of CXProviderConfiguration and one property hasVideo of CXHandle.
It's working fine for me. Check this below demo link.
https://websitebeaver.com/callkit-swift-tutorial-super-easy
func setupVdeoCall() {
let config = CXProviderConfiguration(localizedName: "My App")
config.iconTemplateImageData = UIImagePNGRepresentation(UIImage(named: "pizza")!)
config.ringtoneSound = "ringtone.caf"
config.includesCallsInRecents = false;
config.supportsVideo = true;
let provider = CXProvider(configuration: config)
provider.setDelegate(self, queue: nil)
let update = CXCallUpdate()
update.remoteHandle = CXHandle(type: .generic, value: "Pete Za")
update.hasVideo = true
provider.reportNewIncomingCall(with: UUID(), update: update, completion: { error in })
}

Add function for cancel in UIAlert of Phone Call function

I have a function that places a phone call when a button is pressed.
private func callNumber(phoneNumber:String) {
if let phoneCallURL = URL(string: "tel://\(phoneNumber)") {
let application:UIApplication = UIApplication.shared
if (application.canOpenURL(phoneCallURL)) {
application.open(phoneCallURL, options: [:], completionHandler: nil)
}
}
}
I want to add some functionality if the users hit cancel when the alert pops up to confirm the call. How would I do that?
According to this question: Prompt when trying to dial a phone number using tel:// scheme on iOS 10.3
This alert is actually a bug in iOS 10.3 and should be removed at some point in the future. It isn't supposed to come up for "tel:" links in native apps.
That said, I don't believe there is a way to detect the alert and how the user interacts with it.

Resources