How do I decline observed call with CallKit? - ios

I'm completely new to Swift programming and I don't know how to make this work.
Basically I have and iOS app which observes incoming phone calls and declines it automatically if the caller number is equal to a user defined number to decline, without showing user any notification (this app is only for my private use).
Currently I have this code which observes incoming call. It works like a charm but I don't know how to make it decline the call (for now all phone numbers).
import CallKit
class CallTest: NSObject, CXCallObserverDelegate, ObservableObject {
#Published var incomingCallNumber: String?
let callObserver = CXCallObserver()
override init() {
super.init()
callObserver.setDelegate(self, queue: nil)
}
func callObserver(_ callObserver: CXCallObserver, callChanged call: CXCall) {
if (call.isOutgoing == false && call.hasConnected == false) {
print("Incoming call")
}
}
}
I've tried to decline incoming call like this.
import CallKit
class CallTest: NSObject, CXCallObserverDelegate, ObservableObject {
#Published var incomingCallNumber: String?
let callObserver = CXCallObserver()
override init() {
super.init()
callObserver.setDelegate(self, queue: nil)
}
func callObserver(_ callObserver: CXCallObserver, callChanged call: CXCall) {
if (call.isOutgoing == false && call.hasConnected == false) {
print("Incoming call")
let callController = CXCallController()
let endCallAction = CXEndCallAction(call: call.uuid)
let transaction = CXTransaction(action: endCallAction)
callController.request(transaction) { error in
if let error = error {
print("Error declining call: \(error)")
} else {
print("Call declined successfully")
}
}
}
}
}
But I keep getting error:
Error Domain=com.apple.CallKit.error.requesttransaction Code=2 "(null)"
Which means 'unknownCallProvider', but I don't know how to set provider for this.

Related

<error type="cancel" code="501"><feature-not-implemented xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"/>

I am beginner in XMPP. I want to implement chat application with OpenFire server. I am able to make the proper connection with the server, but when I want to send any message to another Jabberid, I am receiving an error
SEND: <iq type="error" to="DOMAINMANE" id="348-5581"><query xmlns="jabber:iq:version"/><error type="cancel" code="501"><feature-not-implemented xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"/></error></iq>
Here is the code,which I tried
extension AppDelegate{
private func setupStream() {
//xmppRoster = XMPPRoster(rosterStorage: xmppRosterStorage)
xmppRoster.activate(xmppStream)
self.xmppStream.addDelegate(self, delegateQueue: DispatchQueue.main)
self.xmppRoster.addDelegate(self, delegateQueue: DispatchQueue.main)
}
func connect() -> Bool {
if !xmppStream.isConnected {
let jabberID = GlobleConstants.senderJID
if !xmppStream.isDisconnected {
return true
}
xmppStream.myJID = XMPPJID(string: jabberID)
do {
try xmppStream.connect(withTimeout: XMPPStreamTimeoutNone)
self.xmppMessageDeliveryRecipts.autoSendMessageDeliveryReceipts = true
self.xmppMessageDeliveryRecipts.autoSendMessageDeliveryRequests = true
self.xmppMessageDeliveryRecipts.activate(self.xmppStream)
print("Connection success")
return true
} catch {
print("Something went wrong!")
return false
}
} else {
return true
}
}
func disconnect() {
goOffline()
xmppStream.disconnect()
}
private func goOnline() {
let presence = XMPPPresence()
let domain = xmppStream.myJID?.domain
if domain == GlobleConstants.XMPPHOSTNAME || domain == "gmail.com" || domain == "gtalk.com" ||
domain == "talk.google.com" {
let priority = DDXMLElement.element(withName: "priority", stringValue: "24") as! DDXMLElement
presence.addChild(priority)
}
xmppStream.send(presence)
}
private func goOffline() {
let presence = XMPPPresence(type: "unavailable")
xmppStream.send(presence)
}
}
extension AppDelegate : XMPPStreamDelegate{
func xmppStreamDidConnect(_ sender: XMPPStream) {
do {
try xmppStream.authenticate(withPassword: GlobleConstants.sPassword)
} catch {
print("Could not authenticate")
}
}
func xmppStreamDidAuthenticate(_ sender: XMPPStream) {
goOnline()
}
func xmppStream(_ sender: XMPPStream, didReceive iq: XMPPIQ) -> Bool {
print("Did receive IQ")
return false
}
func xmppStream(_ sender: XMPPStream, didReceive message: XMPPMessage) {
print("Did send message \(message)")
}
func xmppStream(_ sender: XMPPStream, didReceive presence: XMPPPresence) {
print("IN APPDELEGATE -----> ", presence)
let presenceType = presence.presenceType?.rawValue
let myUsername = sender.myJID?.user
let presenceFromUser = presence.from?.user
print("MY USERNAME ",myUsername)
print("OTHER USER NAME ",presenceFromUser)
print("PRESNET ",presenceType)
**//HERE ALSO NOT GETTING ANOTHER ONLINE USER, SO THAT I CAN ADD INTO ONLINE BUDDIES LIST**
/*if presenceFromUser != myUsername {
print("Did receive presence from \(presenceFromUser)")
if presenceType == "available" {
delegate.buddyWentOnline(name: "\(presenceFromUser)#\(GlobleConstants.XMPPHOSTNAME)")
} else if presenceType == "unavailable" {
delegate.buddyWentOffline(name: "\(presenceFromUser)#\(GlobleConstants.XMPPHOSTNAME)")
}
}*/
}
}
extension AppDelegate : XMPPRosterDelegate{
func xmppRoster(_ sender: XMPPRoster, didReceiveRosterItem item: DDXMLElement) {
print("Did receive Roster item")
}
}
And now inside of the controller, I am trying to fetch online buddies by
func setupUI(){
appDelegate.delegate = self
if appDelegate.connect() {
self.title = appDelegate.xmppStream.myJID?.user
appDelegate.xmppRoster.fetch()
}
self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
}
But getting the same error. Anyone having solution for the same?
You are not getting an error, you are sending one. After your client connects, Openfire queries it for its software version. Your client does not support that request, and sends back an error to Openfire ("feature not implemented"). This is perfectly reasonable, and no cause for concern.
If you want to get rid of the error, you can make your client respond to queries as defined in the protocol at https://xmpp.org/extensions/xep-0092.html

AVFoundation- .audioDeviceInUseByAnotherClient runs after CXCallObserver call.hasEnded (call disconnection) is called

I'm using AVFoundation for video recording. I also use the CXCallObserverDelegate to listen to when a phone call is disconnected.
I go to the background
I make a phone call
While the phone call is active I bring the app back to the foreground and press the button to modally present the vc that contains AVFoundation
Once the vc is on scene and because I'm currently on a phone call the .audioDeviceInUseByAnotherClient gets called and I stop the capture session
Once the phone call is disconnected then CXCallObserver call.hasEnded is called and I restart the capture session. The .sessionInterruptionEnded also gets called but this isn't causing the issue.
This is where the problem occurs. Once call.hasEnded is called then .audioDeviceInUseByAnotherClient gets called again. Since the code to stop the capture session is in there this results in capture session stopping again
In step 6 why does .audioDeviceInUseByAnotherClient get called again after the call has been disconnected?
func sessionWasInterrupted(notification: NSNotification) {
let reasonIntegerValue = userInfoValue.integerValue,
let reason = AVCaptureSession.InterruptionReason(rawValue: reasonIntegerValue) {
case .audioDeviceInUseByAnotherClient:
stopCaptureSession()
}
}
}
func sessionInterruptionEnded(notification: NSNotification) {
print("-----Capture session interruption ended")
restartCaptureSession()
}
func callObserver(_ callObserver: CXCallObserver, callChanged call: CXCall) {
if call.hasEnded == true {
print("\nCXCallState :Disconnected")
restartCaptureSession()
}
if call.hasConnected == true && call.hasEnded == false {
print("\nCXCallState : Connected")
// *** THIS NEVER GETS CALLED IN THIS SCENARIO ***
}
}
fileprivate func stopCaptureSession() {
if captureSession.isRunning {
DispatchQueue.global(qos: .background).async { [weak self] in
DispatchQueue.main.sync {
self?.captureSession.stopRunning()
}
DispatchQueue.main.async {
self?.previewLayer?.removeFromSuperlayer()
self?.previewLayer = nil
}
}
}
}
func restartCaptureSession() {
if !captureSession.isRunning {
DispatchQueue.global(qos: .background).async { [weak self] in
DispatchQueue.main.sync {
self?.captureSession.startRunning()
}
DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) {
if let safeSelf = self {
if safeSelf.previewLayer == nil {
self?.previewLayer = AVCaptureVideoPreviewLayer(session: self!.captureSession)
self?.previewLayer?.videoGravity = AVLayerVideoGravity.resizeAspectFill
guard let previewLayer = self?.previewLayer else { return }
previewLayer.frame = self!.containerViewForPreviewLayer.bounds
self?.containerViewForPreviewLayer.layer.insertSublayer(previewLayer, at: 0)
}
}
}
}
}
}

No Voice Communication when call is streaming using CallKit

I was trying to make a call from extension 100(Linphone App) to 102(My application). The extension 102 has shown the incoming call screen and then clicked accept button and then, extension 100 starts to talk to extension 102 but extension 102 unable to hear anything from 100 and also extension 100 unable to hear from 102 as well.
I think the problem because of app. But i don't know what is wrong with my application.
These functions i declared in appdelegate file in order to handle incoming call
let callKitManager = CallKitCallInit(uuid: UUID(), handle: "")
lazy var providerDelegate: ProviderDelegate = ProviderDelegate(callKitManager: self.callKitManager)
func displayIncomingCall(uuid: UUID, handle: String, completion: ((NSError?) -> Void)?) {
providerDelegate.reportIncomingCall(uuid: uuid, handle: handle, completion: completion)
}
This is ProviderDelegate file
extension ProviderDelegate: CXProviderDelegate {
func providerDidReset(_ provider: CXProvider) {
print("Stop Audio ==STOP-AUDIO==")
for call in callKitManager.calls {
call.end(uuid: UUID())
}
callKitManager.removeAllCalls()
}
func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {
guard let call = callKitManager.callWithUUID(uuid: action.callUUID) else {
action.fail()
return
}
configureAudioSession()
call.answer()
action.fulfill()
}
func provider(_ provider: CXProvider, perform action: CXEndCallAction) {
guard let call = callKitManager.callWithUUID(uuid: action.callUUID) else {
action.fail()
return
}
print("Stop audio ==STOP-AUDIO==")
configureAudioSession()
call.end(uuid: action.callUUID)
action.fulfill()
callKitManager.remove(call: call)
}
func provider(_ provider: CXProvider, didActivate audioSession: AVAudioSession) {
print("Starting audio ==STARTING-AUDIO==")
}
func provider(_ provider: CXProvider, didDeactivate audioSession: AVAudioSession) {
print("Received \(#function)")
}
func configureAudioSession() {
let session = AVAudioSession.sharedInstance()
do {
try? session.setCategory(AVAudioSessionCategoryPlayAndRecord)
try? session.setMode(AVAudioSessionModeVoiceChat)
try? session.setPreferredSampleRate(44100.0)
try? session.setPreferredIOBufferDuration(0.005)
try? session.setActive(true)
}
}
}
class ProviderDelegate: NSObject {
fileprivate let callKitManager: CallKitCallInit
fileprivate let provider: CXProvider
init(callKitManager: CallKitCallInit) {
self.callKitManager = callKitManager
provider = CXProvider(configuration: type(of: self).providerConfiguration)
super.init()
provider.setDelegate(self, queue: nil)
}
static var providerConfiguration: CXProviderConfiguration {
let providerConfiguration = CXProviderConfiguration(localizedName: "vKclub")
providerConfiguration.supportsVideo = false
providerConfiguration.maximumCallsPerCallGroup = 1
providerConfiguration.supportedHandleTypes = [.phoneNumber]
return providerConfiguration
}
func reportIncomingCall(uuid: UUID, handle: String, hasVideo: Bool = false, completion: ((NSError?) -> Void)?) {
print("This is UUID === ", uuid)
configureAudioSession()
let update = CXCallUpdate()
update.remoteHandle = CXHandle(type: .phoneNumber, value: handle)
update.hasVideo = hasVideo
provider.reportNewIncomingCall(with: uuid, update: update) { error in
if error == nil {
// 3.
self.configureAudioSession()
let call = CallKitCallInit(uuid: uuid, handle: handle)
self.callKitManager.add(call: call)
lastCallUUID = uuid
print("UUID === ", uuid)
} else {
}
// 4.
completion?(error as NSError?)
}
}
}

CallKit Interferes With WebRTC Video

When I use CallKit to answer a WebRTC call, the video chat works most of the time. Once in a while the camera on my local iPhone isn't accessed correctly because of CallKit. When I remove CallKit, the video chat always works. Also if I set a delay for 1.5 second after I answer a CallKit call and then start the video chat, it seems to work well all of the time. What's the reason for this?
Callkit is intended for Audio Call only, so callkit will activate AudioSession only.
Video will be activated only after you navigate to your application.
Try answering the call on phone lock screen, then you will get understood.
Test Facebook or any other popular app.
Try implementing your (re)connection logic in providerDidReset(_ provider: CXProvider). You may also want to provide checks in this method to ensure you aren't resetting a successful connection.
I am using WebRTC + CallKit in my App.
I started a call and when I press Lock / Power button then the CallKit call is getting disconnected and the My Voip call audio route changes to Receiver and remains.
Why locking the iPhone terminating the call.
Here is my Code.
var callUUID: UUID?
extension AppDelegate {
func initiateCallKitCall() {
let config = CXProviderConfiguration(localizedName: "Huddl.ai")
config.includesCallsInRecents = false;
config.supportsVideo = true;
config.maximumCallsPerCallGroup = 1
provider = CXProvider(configuration: config)
guard let provider = provider else { return }
provider.setDelegate(self, queue: nil)
callController = CXCallController()
guard let callController = callController else { return }
callUUID = UUID()
let transaction = CXTransaction(action: CXStartCallAction(call: callUUID!, handle: CXHandle(type: .generic, value: "Huddl.ai")))
callController.request(transaction, completion: { error in
print("Error is : \(String(describing: error))")
})
}
func endCallKitCall(userEnded: Bool) {
self.userEnded = userEnded
guard provider != nil else { return }
guard let callController = callController else { return }
if let uuid = callUUID {
let endCallAction = CXEndCallAction(call: uuid)
callController.request(
CXTransaction(action: endCallAction),
completion: { error in
if let error = error {
print("Error: \(error)")
} else {
print("Success")
}
})
}
}
func isCallGoing() -> Bool {
let callController = CXCallController()
if callController.callObserver.calls.count != 0 {
return true
}
return false
}
}
extension AppDelegate: CXProviderDelegate {
func providerDidReset(_ provider: CXProvider) {
print("-Provider-providerDidReset")
}
func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {
print("-Provider-perform action: CXAnswerCallAction")
action.fulfill()
}
func provider(_ provider: CXProvider, perform action: CXEndCallAction) {
action.fulfill()
print("-Provider: End Call")
}
func provider(_ provider: CXProvider, perform action: CXStartCallAction) {
action.fulfill()
DispatchQueue.main.asyncAfter(wallDeadline: DispatchWallTime.now() + 3) {
provider.reportOutgoingCall(with: action.callUUID, startedConnectingAt: Date())
DispatchQueue.main.asyncAfter(wallDeadline: DispatchWallTime.now() + 1.5) {
provider.reportOutgoingCall(with: action.callUUID, connectedAt: Date())
}
}
}
func provider(_ provider: CXProvider, perform action: CXSetHeldCallAction) {
action.fulfill()
}
func provider(_ provider: CXProvider, didActivate audioSession: AVAudioSession) {
RTCAudioSession.sharedInstance().audioSessionDidActivate(audioSession)
RTCAudioSession.sharedInstance().isAudioEnabled = true
}
func provider(_ provider: CXProvider, didDeactivate audioSession: AVAudioSession) {
RTCAudioSession.sharedInstance().audioSessionDidDeactivate(audioSession)
RTCAudioSession.sharedInstance().isAudioEnabled = false
}
}

CXCallObserver is not working properly and App getting crash when running the app more than one (when includes contacts image data)

I am facing two major problem first one is :
1. I am trying to detect incoming call, outgoing call , dialing call for this i am using this code :
import UIKit
import CoreTelephony
import CallKit
class ViewController: UIViewController,CXCallObserverDelegate {
let callObserver = CXCallObserver()
var seconds = 0
var timer = Timer()
override func viewDidLoad() {
super.viewDidLoad()
callObserver.setDelegate(self, queue: nil)
}
override func viewWillAppear(_ animated: Bool) {
print("viewWillAppear \(seconds)")
}
fileprivate func runTimer(){
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(self.updateTimer), userInfo: nil, repeats: true)
}
func updateTimer() {
seconds += 1
print("Seconds \(seconds)")
}
#IBAction func callButton(_ sender: UIButton) {
if let url = URL(string: "tel://\(12345879)"){
UIApplication.shared.open(url, options: [:], completionHandler: nil)
}
}
func callObserver(_ callObserver: CXCallObserver, callChanged call: CXCall) {
if call.hasEnded == true {
print("Disconnected")
seconds = 0
self.timer.invalidate()
}
if call.isOutgoing == true && call.hasConnected == false {
print("Dialing call")
self.runTimer()
}
if call.isOutgoing == false && call.hasConnected == false && call.hasEnded == false {
print("Incoming")
}
if call.hasConnected == true && call.hasEnded == false {
print("Connected")
}
}
}
It working fine when i am dialing a number it shows "Dialling" but when i cut the call then it shows "Disconnected" then again "Dialing" State.
Another problem is when i am fetching all contacts information from the device it works fine when i am not fetching imageData but when i am fetching contacts image it works fine for the very first time . Then if i run it again app become slow . then next it crash shows found nil while unwrapping a value.
i wrote my contact data fetching function in AppDelegate . it is calling when the app start . this is the code :
func fetchContactList(){
let loginInformation = LoginInformation()
var contactModelData: [ContactsModel] = []
var profileImage : UIImage?
let store = CNContactStore()
store.requestAccess(for: .contacts, completionHandler: {
granted, error in
guard granted else {
let alert = UIAlertController(title: "Can't access contact", message: "Please go to Settings -> MyApp to enable contact permission", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
self.window?.rootViewController?.present(alert, animated: true, completion: nil)
return
}
let keysToFetch = [CNContactFormatter.descriptorForRequiredKeys(for: .fullName),CNContactPhoneNumbersKey, CNContactEmailAddressesKey, CNContactPostalAddressesKey, CNContactImageDataKey, CNContactImageDataAvailableKey,CNContactThumbnailImageDataKey,CNContactThumbnailImageDataKey] as [Any]
let request = CNContactFetchRequest(keysToFetch: keysToFetch as! [CNKeyDescriptor])
var cnContacts = [CNContact]()
do {
try store.enumerateContacts(with: request){
(contact, cursor) -> Void in
cnContacts.append(contact)
}
} catch let error {
NSLog("Fetch contact error: \(error)")
}
for contact in cnContacts {
let fullName = CNContactFormatter.string(from: contact, style: .fullName) ?? "No Name"
var phoneNumberUnclean : String?
var labelofContact : String?
var phoneNumberClean: String?
for phoneNumber in contact.phoneNumbers {
if let number = phoneNumber.value as? CNPhoneNumber,
let label = phoneNumber.label {
let localizedLabel = CNLabeledValue<CNPhoneNumber>.localizedString(forLabel: label)
print("fullname \(fullName), localized \(localizedLabel), number \(number.stringValue)")
phoneNumberUnclean = number.stringValue
labelofContact = localizedLabel
}
}
if let imageData = contact.imageData {
profileImage = UIImage(data: imageData)
print("image \(String(describing: UIImage(data: imageData)))")
} else {
profileImage = UIImage(named: "user")
}
self.contactModelData.append(ContactsModel(contactName: fullName, contactNumber:phoneNumberUnclean!, contactLabel: labelofContact!, contactImage: profileImage!, contactNumberClean: phoneNumberUnclean!))
}
self.loginInformation.saveContactData(allContactData: self.contactModelData)
})
}
I have solved this two problems using this :
for number one when i disconnect a call then if unfortunately it goes to "Dialling" option again i checked the "seconds" variable's value if it greater than 0 in "Dialing" then invalidate the thread.
for number two problem :
I used Dispatch.async.main background thread and take the thumbnail image

Resources