CXProviderDelegate Methods not called - ios

My goal is to open my CallViewController after the user answers the call. I've read some excerpts here on SO that it can be done using:
- (void)provider:(CXProvider *)provider performAnswerCallAction:(CXAnswerCallAction *)action {
NSLog(#"performAnswerCallAction");
}
- (void)provider:(CXProvider *)provider performStartCallAction:(CXStartCallAction *)action {
NSLog(#"performStartCallAction");
}
The code above is inside AppDelegate.
However, using NSLogs, these methods aren't triggered at all. I can't seem to find any tutorials on implementing using Objective-C that is easy to understand from a beginner's POV. I would appreciate any insights, thanks!

First of all, did you set the delegate of CXProvider?
let provider = CXProvider(configuration: your_call_kit_config)
provider.setDelegate(self, queue: nil) // 'nil' means it will run on main queue
Also, have the AppDelegate.swift file conform to CXProviderDelegate
provider:performAnswerCallAction: gets called when user taps on 'Answer' button on the incoming call screen provided by the system. (Apple documentation HERE).
provider:performStartCallAction: gets called after successful request to CXCallController to perform a CXStartCallAction (Apple documentation HERE).
EDIT: For Objective-C
Ok here is the Objective-C snippet, for example, in the AppDelegate. First, you need to make AppDelegate conform to the CXProviderDelegate, Like so:
#interface AppDelegate () <CXProviderDelegate>
Then, add a property for CXProvider and CXCallController, like so:
#interface AppDelegate () <CXProviderDelegate>
#property (nonatomic, nonnull, strong) CXProvider *provider;
#property (nonatomic, nonnull, strong) CXCallController *callController;
#end
In the AppDelegate's function application:willFinishLaunchingWithOptions:, initialize CXProvider object with the call configuration, like this:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Setup CallKit
CXProviderConfiguration *providerConfiguration = [[CXProviderConfiguration alloc] initWithLocalizedName:#"MyApp"];
providerConfiguration.supportsVideo = YES;
providerConfiguration.includesCallsInRecents = YES;
self.provider = [[CXProvider alloc] initWithConfiguration: providerConfiguration];
// Since `AppDelegate` conforms to `CXProviderDelegate`, set it to the provider object
// Setting 'nil' to `queue` argument means, that the methods will be executed on main thread.
// Optionally, you can assign private serial queue to handle `CXProvider` method responses
[self.provider setDelegate:self queue:nil];
// Initialize `CallController`
self.callController = [[CXCallController alloc] init];
return YES;
}
And on the bottom of AppDelegate.m file, implement CXProviderDelegate methods:
#pragma mark: - CXProvider delegate methods
- (void)providerDidReset:(CXProvider *)provider {
// Drop all calls here (if there are pending)
}
/*!
This method gets called when user presses on 'Accept' button on the incoming call screen provided by the system.
Here you should configure `AVAudioSession` for VoIP calls and handle logic for answering the call.
*/
- (void)provider:(CXProvider *)provider performAnswerCallAction:(CXAnswerCallAction *)action {
// Put your answering logic here
// Note: It is important to fulfill the action inside the scope of it's function, or to fail, depending if error occured during answering
[action fulfill];
}
/*!
This method gets called when CXCallController object finishes the CXStartCallAction request. You need to request
start call action to the CXCallController instance, when starting an outgoing VoIP call. After successful transaction,
the provider will respond with this delegate method. Here you should also configure `AVAudioSession` for VoIP calls.
*/
- (void)provider:(CXProvider *)provider performStartCallAction:(CXStartCallAction *)action {
// Put your outgoing call action here
// Note: It is important to fulfill the action inside the scope of it's function, or to fail, depending if error occured during starting a call
[action fulfill];
}
To start an outgoing VoIP call, you need to request the transaction with the action to the CXCallController instance, like so:
#pragma mark - Call Controller requests
- (void)startOutgoingVoIPCallWithNumber:(NSString *)number {
NSUUID *callUUID = [NSUUID UUID]; // Here you create or assign UUID of call.
CXHandle *callHandle = [[CXHandle alloc] initWithType:CXHandleTypePhoneNumber value:number];
CXStartCallAction *startCallAction = [[CXStartCallAction alloc] initWithCallUUID:callUUID handle:callHandle];
startCallAction.video = YES; // Yes or no is call is video or audio.
CXTransaction *startCallTransaction = [[CXTransaction alloc] initWithAction:startCallAction];
[self.callController requestTransaction:startCallTransaction completion:^(NSError * _Nullable error) {
if (error) {
// Handle start call error here
// Ususally, error occurs if the system cannot handle the new outgoing call, since there are others pending
}
// If there is no error, CXProvider will respond with `provider:performStartCallAction:` method.
}];
}
To display system call screen
#pragma mark - Report New incoming call
/*!
You need to call this function each time you receive a new incoming call, usually right from the VoIP push notification.
*/
- (void)reportNewIncomingCallWithNumber:(NSString *)number {
NSUUID *callUUID = [NSUUID UUID]; // Call UUID, you should have this in some Call object, not generating the new here
CXHandle *callHandle = [[CXHandle alloc] initWithType:CXHandleTypePhoneNumber value:number];
CXCallUpdate *callInfo = [[CXCallUpdate alloc] init];
callInfo.remoteHandle = callHandle;
[self.provider reportNewIncomingCallWithUUID:[NSUUID UUID] update:callInfo completion:^(NSError * _Nullable error) {
if (error) {
// Handle error here
}
// If there is no error, system will display incoming call screen and when user taps on 'Answer',
// `CXProvider` will respond with `provider:performAnswerCallAction:`
}];
}

Just in case anyone's coming across this issue still, the problem for me was that I was unwittingly passing a null value for the CXHandle in callUpdate - and THEN that I was only checking for nil instead of NSNull.
Old, buggy one:
NSString *callerName = <nullable value from remote message>
CXHandle *callHandle = [[CXHandle alloc] initWithType:CXHandleTypeGeneric value:handle];
[provider reportNewIncomingCallWithUUID:uuid update:callUpdate completion:^(NSError * _Nullable error) {
...
}]
New, working one
NSString *callerName = <nullable value from remote message>
CXHandle *callHandle = [[CXHandle alloc] initWithType:CXHandleTypeGeneric value:handle];
if (callerName == (id)[NSNull null] || callerName.length == 0) {
callerName = #"Faceless Man";
}
[provider reportNewIncomingCallWithUUID:uuid update:callUpdate completion:^(NSError * _Nullable error) {
...
}]

Related

CallKit reinitializing causes com.apple.CallKit.error.requesttransaction Code=4 (UnknownCallUUID)

My iOS VoIP app uses CallKit in order to support native call integration feature. At first launch everything is working fine, but if I reinitialize CXProvider and CXCallController (in order to disable/enable feature), after incoming call hangup I receive error "com.apple.CallKit.error.requesttransaction Code=4".
#implementation CallKitHandler
- (void) configureCallKitWith
{
...
self.callKitProvider = [[CXProvider alloc] initWithConfiguration:_cxpConfiguration];
[_callKitProvider setDelegate:self queue:nil];
self.callKitCallController = [CXCallController new];
[_callKitCallController.callObserver setDelegate:self queue:nil];
...
}
- (void) requestEndCallActionWithCall:(Call*) callEnded
{
CXEndCallAction* endCallAction = [[CXEndCallAction alloc] initWithCallUUID:self.callUUId];
CXTransaction* transaction = [[CXTransaction alloc] initWithAction:endCallAction];
OTCLogVerbose (#"requestEndCallActionWithCall '%#' : %#", callEnded.reference, transaction);
[self.callKitCallController requestTransaction:transaction completion:^(NSError* error) {
if (error)
{
OTCLogWarn (#"requestEndCallActionWithCall failed for '%#': %#", _callUUId, [self errorDescriptionOf: error]);
I tried to make my CallKitHandler class as singleton, and it seems to be working, is it the only possible solution? Should you avoid reinitializing CallKit during app's runtime?
The documentation says:
A VoIP app should create only one instance of CXProvider and store it for use globally.
So, yes: you should avoid instantiating the CXProvider every time you want to reconfigure it. Just keep a global reference and reconfigure it if you need to.

INSendMessageIntentHandling sirkit skipping handleSendMessage intent

I added NSLog(..) statements on each method but it was not going to the handle send message delegate. I need to get some message from Siri to do some tasks. Also, is there any way to get the date/time from speech with INSentMessageDomiain
import "IntentHandler.h"
// As an example, this class is set up to handle Message intents.
// You will want to replace this or add other intents as appropriate.
// The intents you wish to handle must be declared in the extension's Info.plist.
// You can test your example integration by saying things to Siri like:
// "Send a message using <myApp>"
// "<myApp> John saying hello"
// "Search for messages in <myApp>"
#interface IntentHandler () <INSendMessageIntentHandling, INSearchForMessagesIntentHandling, INSetMessageAttributeIntentHandling>
{
NSString *resName;
}
#end
#implementation IntentHandler
- (id)handlerForIntent:(INIntent *)intent {
// This is the default implementation. If you want different objects to handle different intents,
// you can override this and return the handler you want for that particular intent.
if ([intent isKindOfClass:[INSendMessageIntent class]]) {
}
return self;
}
#pragma mark - INSendMessageIntentHandling
// Implement resolution methods to provide additional information about your intent (optional).
- (void)resolveRecipientsForSendMessage:(INSendMessageIntent *)intent withCompletion:(void (^)(NSArray<INPersonResolutionResult *> *resolutionResults))completion {
// If no recipients were provided we'll need to prompt for a value.
NSMutableArray<INPersonResolutionResult *> *resolutionResults = [NSMutableArray array];
//NSArray<INPerson *> *recipients = intent.recipients;
[resolutionResults addObject:[INPersonResolutionResult notRequired]];
completion(resolutionResults);
}
- (void)resolveContentForSendMessage:(INSendMessageIntent *)intent withCompletion:(void (^)(INStringResolutionResult *resolutionResult))completion {
NSString *text = intent.content;
if (text.length == 0) {
completion([INStringResolutionResult needsValue]);
}
else {
completion([INStringResolutionResult successWithResolvedString:text]);
}
}
// Handle the completed intent (required).
- (void)handleSendMessage:(INSendMessageIntent *)intent completion:(void (^)(INSendMessageIntentResponse *response))completion {
//Implement your application logic to send a message here.
NSUserActivity *userActivity = [[NSUserActivity alloc] initWithActivityType:NSStringFromClass([INSendMessageIntent class])];
INSendMessageIntentResponse *response = [[INSendMessageIntentResponse alloc]initWithCode:INSendMessageIntentResponseCodeSuccess userActivity:userActivity];
completion(response);
}

CallKit Reject Inbound Call (Objective-C)

I am working on a VOIP application (let's call it SampleApp) using CallKit and am struggling with an issue.
I can answer an inbound SampleApp call in a way that appears to work correctly using CallKit. However, when I reject a call, the call is instantiated anyway, which is incorrect behaviour.
To the extend of my understanding I need to get a boolean somehow depending on whether the call was accepted and rejected and then use this to decide whether the phone controller answers the incoming call.
PhoneViewController.m
- (void) presentIncomingCallAlertForCall:(SampleAppClientCall*)call
{
callIdentifier = [[NSUUID alloc] init];
// Use CallKit if iPhone
if(UI_USER_INTERFACE_IDIOM() != UIUserInterfaceIdiomPad)
{
[_pDelegate reportIncomingCall:callIdentifier
handle:call.remoteAddress
hasVideo:hasVideo
completionHandler: nil];
[self answerIncomingCall];
}
// Use Notification mechanism if iPad
else
{
// etc.
}
}
ProviderDelegate.m
-(void) reportIncomingCall: (NSUUID *) uuid
handle: (NSString *) handle
hasVideo: (BOOL) hasVideo
completionHandler:
(nullable void (^) (NSArray * _Nullable results, NSError * _Nonnull error))completionHandler
{
CXCallUpdate *update = [[CXCallUpdate alloc] init];
update.remoteHandle = [[CXHandle alloc] initWithType:CXHandleTypePhoneNumber
value:handle];
update.hasVideo = hasVideo;
[provider reportNewIncomingCallWithUUID:uuid
update:update
completion: ^(NSError *error)
{
// not sure what to put here?
}];
}
- (void) provider: (CXProvider *)provider
performAnswerCallAction : (CXAnswerCallAction *)action
{
NSDate *now = [[NSDate alloc] init];
[action fulfillWithDateConnected: now];
}
This is my first question, I've read the Stack Overflow guidelines but please tell me if I did anything wrong.
However, when I reject a call, the call is instantiated anyway, which
is incorrect behaviour.
It's unclear what you are trying to do in your code here. You always call [self answerIncomingCall]; (which we do not see the code to, but which I assume reports to your app logic and to your backend that the user has answered the call) right after [_pDelegate reportIncomingCall:...], so it seems like you are assuming that the user has decided to answer it, even before the user has done anything.
Rather, you should be telling your app logic and your backend that the user has answered the call, only in your -provider:performAnswerCallAction: method, and in your -provider:performEndCallAction:, tell your app logic and your backend that the user has declined the call (if it was a ringing incoming call).
Here is the solution based on the advice of #user102008 that resulted in what I wanted to achieve.
I moved the responsibility for answering the incoming call to the provider delegate instead of the phone view controller, which invokes the answer call or end call method depending on whether a call is accepted or rejected respectively in its provider methods.
PhoneViewController.m
- (void) presentIncomingCallAlertForCall:(SampleAppClientCall*)call {
callIdentifier = [[NSUUID alloc] init];
// Use CallKit if iPhone
if(UI_USER_INTERFACE_IDIOM() != UIUserInterfaceIdiomPad) {
[_pDelegate reportIncomingCall:callIdentifier
handle:call.remoteAddress
hasVideo:hasVideo
completionHandler: nil];
}
// Use Notification mechanism if iPad
else {
...
}
}
ProviderDelegate.h
-(void) reportIncomingCall: (NSUUID *) uuid
handle: (NSString *) handle
hasVideo: (BOOL) hasVideo
completionHandler: (nullable void (^) (NSArray * _Nullable results, NSError * _Nonnull error))
completionHandler {
CXCallUpdate *update = [[CXCallUpdate alloc] init];
update.remoteHandle = [[CXHandle alloc] initWithType:CXHandleTypePhoneNumber value:handle];
update.hasVideo = hasVideo;
[provider reportNewIncomingCallWithUUID:uuid
update:update
completion: ^(NSError *error) {
if (error) {
[self reportCallEnded:uuid reason:(NSInteger *)CXCallEndedReasonFailed];
}
}];
}
- (void) provider: (CXProvider *)provider performAnswerCallAction: (CXAnswerCallAction *)action {
[_phoneVC answerIncomingCall];
NSDate *now = [[NSDate alloc] init];
[action fulfillWithDateConnected: now];
}
- (void) provider: (CXProvider *)provider performEndCallAction: (CXEndCallAction *)action {
[_phoneVC rejectIncomingCall];
NSDate *now = [[NSDate alloc] init];
[action fulfillWithDateEnded: now];
}

Instance variable nil after it has been set

I have a class with an object incall. I have a method that sets it and another methods that runs a method available for that object.
Here is my header file:
#interface RCTPlivo : NSObject <PlivoEndpointDelegate, CXProviderDelegate>
#property (nonatomic) PlivoIncoming *incall;
#property (nonatomic) PlivoEndpoint *endpoint;
#end
And here is my implementation file:
#implementation RCTPlivo
- (void)login {
endpoint = [[PlivoEndpoint alloc] init];
[endpoint login:plivoUser AndPassword:plivoPass];
endpoint.delegate = self;
}
- (void)triggerIncomingCall {
...
CXProvider *callkitProvider = [[CXProvider alloc] initWithConfiguration: configuration];
[callkitProvider setDelegate:self queue:nil];
...
[callkitProvider reportNewIncomingCallWithUUID:currentCall update:update completion:^(NSError * _Nullable error) {
if (error) {
NSLog(#"Error: %#", error);
}
}];
}
- (void)onIncomingCall:(PlivoIncoming *)incoming {
// setting
self.incall = incoming
}
- (void)provider:(CXProvider *)provider performAnswerCallAction:(CXAnswerCallAction *)action
{
// Here self.incall is null
[self.incall answer];
}
#end
When i log self.incall in perfromAnswerCall delegate it's null. When I log it in the onIncomingCall delegate the variable is set.
What am I missing here?
Update
Added the code that initializes the delegates and removed ivars.
Your interface should be:
#interface RCTPlivo : NSObject <PlivoEndpointDelegate>
#property (nonatomic, strong) PlivoIncoming *incall;
#end
and your implementation should be:
#implementation RCTPlivo
- (void)onIncomingCall:(PlivoIncoming *)incoming {
self.incall = incoming;
}
- (void)provider:(CXProvider *)provider performAnswerCallAction:(CXAnswerCallAction *)action {
[self.incall answer];
}
#end
One way this can happen is that you somehow have two separate instances of RCTPlivo. Try stopping the debugger in each of those calls and in the debugger do:
(lldb) po self
If everything's ok then the addresses should be the same.
The infall property is not defined as strong. So we can assume that there is no strong reference to the original object outside this method and it was released.
Update
This property was mentioned as delegate so its weak nature can be a designated behaviour and if this is an option the message sender should have a strong property holding the object reference.

Get NSURLConnection response (from a helper class) inside method of a different class

I have a class, "WebAPI", that handles all web API calls, the class uses NSURLConnection through its asynchronous delegate-based calls.
Whenever an object needs to communicate with the web API it will use an instance of WebAPI and call the required method as shown below in the case of signing in I make the folowing call from the AppDelegate:
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
WebAPI *webAPI = [[WebAPI alloc] init];
[webAPI performLoginWithUserName:#"test1#myserver.com" andPassword:#"password"];
}
The problem is that once the performLoginWithUserName:andPassword call is made, the code progresses on and any/all response is received in the delegate methods that are implemented in WebAPI.m.
This is a real issue because I need to be able to get response codes and any data received within the class method from where the call to the WebAPI, originated . I would like to be able to this :
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
WebAPI *webAPI = [[WebAPI alloc] init];
WebAPIResponse * webAPIRespnse = [webAPI performLoginWithUserName:#"test1#myserver.com" andPassword:#"password"];
}
Where WebAPIResponse class is a custom class that will contain the HTTP Status code and any data that is received.
This is achievable if I change WebAPI.m to use NSURLConnection sendSynchronousRequest, but that doesnt enable me to receive all HTTP codes.
What would be the best way to fulfill this requirement?
Thank you for your help.
You could use blocks to handle responses.
For example:
WebApi.h
- (void)performLoginWithUsername:(NSString *)userName
andPassword:(NSString *)password
successBlock:(void(^)(NSData *response))successBlock
failureBlock:(void(^)(NSError *error))failureBlock;
WebApi.m
#interface WebAPI()
#property (nonatomic, copy) void(^authorizationSuccessBlock)(NSData *response);
#property (nonatomic, copy) void(^authorizationFailureBlock)(NSError *error);
#end
#implementation WebAPI
- (void)performLoginWithUsername:(NSString *)userName
andPassword:(NSString *)password
successBlock:(void(^)(NSData *response))successBlock
failureBlock:(void(^)(NSError *error))failureBlock {
self.authorizationSuccessBlock = successBlock;
self.authorizationFailureBlock = failureBlock;
// NSURLConnection call for authorization here
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
if (self.authorizationSuccessBlock != nil) {
self.authorizationSuccessBlock(data);
self.authorizationSuccessBlock = nil;
}
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
if (self.authorizationFailureBlock != nil) {
self.authorizationFailureBlock(error);
self.authorizationFailureBlock = nil;
}
}
AppDelegate.m
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
WebAPI *webAPI = [[WebAPI alloc] init];
[webAPI performLoginWithUserName:#"test1#myserver.com" andPassword:#"password" successBlock:^(NSData *response) {
// Handle result here
} failureBlock:^(NSError *error) {
// Handle error here
}];
}
Change your WebAPI class to provide a delegate interface of its own, or to use completion blocks on the request which are called when the asynchronous connection completes.

Resources