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

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.

Related

Report new incoming call to CallKit using SinchRTC - iOS 13

I am using Sinch to manage calls in my app, but I can't figure out how to report the incoming call to CallKit, as required by iOS 13. I went through some of their documentation listed here, but that's not working either. Also, the documentation states that you should report it as:
- (void)managedPush:(id<SINManagedPush>)managedPush
didReceiveIncomingPushWithPayload:(NSDictionary *)payload
forType:(NSString *)pushType {
id<SINNotificationResult> notification = [SINManagedPush queryPushNotificationPayload:payload];
if ([notification isValid] && [notification isCall]) {
NSUUID *callId = [[NSUUID alloc] initWithUUIDString:notification.callResult.callId];
CXCallUpdate *callUpdate = [[CXCallUpdate alloc] init];
callUpdate.remoteHandle = [[CXHandle alloc] initWithType:CXHandleTypeGeneric value:notification.callResult.remoteUserId];
[self.provider reportNewIncomingCallWithUUID:callId
update:callUpdate
completion:^(NSError *_Nullable error) {
if (error) {
// Hangup call
}
}];
}
}
When I try to implement this, I get the error that SINManagedPush doesn't contain any method such as queryPushNotificationPayload:payload, even though I'm using the latest version of Sinch, so as a workaround I found that SINPushHelper contains these properties/methods, so I'm using that. I am new in Swift and iOS environment in general, so any help would be very much appreciated!
you can follow our SinchCallKit or SinchVideoCallKit sample apps, they are available inside the Samples folder on our IOS SDK.
https://download.sinch.com/ios/4.2.5/Sinch-iOS-4.2.5-a5beacdb.tar.bz2
Sinch Voice & Video Team

How to get use BGTask in objective-c with iOS 13(Background Fetch)

So i want to build a iOS, i am pretty new to the world of objective-c and one feature i want to implement is the ability to send a API request and do a bit of background processing while the app is not "in focus/in background". I have researched for a couple days about this BGTask API for iOS 13 and have created a projected to see if i can get "background fetch" working. I have not be able to. Im pretty sure i have everything setup correctly but i can not get background fetch functionality to trigger on my iPhone, not even once over the past couple days.
I am using a actual iOS device to test this with iOS 13.4.1
"Permitted background task scheduler identifiers" is setup properly in Info.plist
App is signed
Background processing and Background fetch is checked in Background Modes
I waited the 15 minute interval as per Apples documentation
Here is my code. All this is just a blank iOS project using objective-c. I only edited AppDelegate.m and Info.plist
AppDelegate.m
#import "AppDelegate.h"
#import <BackgroundTasks/BackgroundTasks.h>
static NSString* TaskID = #"com.myapp.task";
#interface AppDelegate ()
#end
#implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
[[BGTaskScheduler sharedScheduler] registerForTaskWithIdentifier:TaskID
usingQueue:nil
launchHandler:^(BGProcessingTask *task) {
[self handleAppRefreshTask:task];
}];
return YES;
}
#pragma mark - UISceneSession lifecycle
- (UISceneConfiguration *)application:(UIApplication *)application configurationForConnectingSceneSession:(UISceneSession *)connectingSceneSession options:(UISceneConnectionOptions *)options {
// Called when a new scene session is being created.
// Use this method to select a configuration to create the new scene with.
return [[UISceneConfiguration alloc] initWithName:#"Default Configuration" sessionRole:connectingSceneSession.role];
}
- (void)application:(UIApplication *)application didDiscardSceneSessions:(NSSet<UISceneSession *> *)sceneSessions {
// Called when the user discards a scene session.
// If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
// Use this method to release any resources that were specific to the discarded scenes, as they will not return.
}
-(void)handleAppRefreshTask:(BGProcessingTask *)task {
//do things with task
NSLog(#"Process started!");
task.expirationHandler = ^{
NSLog(#"WARNING: expired before finish was executed.");
};
NSString *targetUrl = #"https://webhook.site/1b274a6f-016f-4edf-8e31-4ed7058eaeac";
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
[request setHTTPMethod:#"GET"];
[request setURL:[NSURL URLWithString:targetUrl]];
[[[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:
^(NSData * _Nullable data,
NSURLResponse * _Nullable response,
NSError * _Nullable error) {
NSString *myString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(#"Data received: %#", myString);
}] resume];
task.expirationHandler = ^{
NSLog(#"WARNING: expired before finish was executed.");
};
[task setTaskCompletedWithSuccess:YES];
}
- (void)applicationDidEnterBackground:(UIApplication *)application
{
NSLog(#"Entering background");
BGProcessingTaskRequest *request = [[BGProcessingTaskRequest alloc] initWithIdentifier:TaskID];
request.requiresNetworkConnectivity = true;
request.requiresExternalPower = false;
request.earliestBeginDate = [NSDate dateWithTimeIntervalSinceNow:60];
#try {
[[BGTaskScheduler sharedScheduler] submitTaskRequest:request error:nil];
}
#catch(NSException *e){
NSLog(#" Unable to submit request");
}
}
#end
Is background fetch broken in iOS 13? Even clicking on the “Simulate background fetch" in Xcode debug menu does not work. It just closes the app and nothing happens. Can anybody help/give any advice?
A few observations:
The setTaskCompletedWithSuccess should be inside the network request’s completion handler. You don’t want to mark the task as complete until the request has had a chance to run and you’ve processed the result.
You are calling submitTaskRequest, but passing nil for the NSError reference. You have also wrapped that in an exception handler. But this API call doesn’t throw exceptions, but rather just passes back errors. But you have to supply it an error reference. E.g.
NSLog(#"Entering background");
BGProcessingTaskRequest *request = [[BGProcessingTaskRequest alloc] initWithIdentifier:TaskID];
request.requiresNetworkConnectivity = true;
request.requiresExternalPower = false;
request.earliestBeginDate = [NSDate dateWithTimeIntervalSinceNow:60];
NSError *error;
if (![[BGTaskScheduler sharedScheduler] submitTaskRequest:request error:&error]) {
NSLog(#"BGTaskScheduler failed: %#", error);
}
In your code, if it failed, you would never know.
You have placed this code in applicationDidEnterBackground. I.e., are you seeing this “Entering background” message at all? The reason I ask is that if you’ve supplied a scene delegate (common if you just created a new iOS 13 app), this method won’t be called, whereas sceneDidEnterBackground will.
You said that you tried “Simulate background fetch”. But you haven’t created a background fetch request (a BGAppRefreshTask). You created a background task (a BGProcessingTask), which is a different thing. To test background processing requests, refer to Starting and Terminating Tasks During Development.
There’s an interesting question as to how you know that the fetch request was processed. You’re just using NSLog (which presumes that you’re keeping your app attached to the Xcode debugger). I would suggest testing this without the app being attached to Xcode. There are a few options:
If you can watch your server logs for requests, that works.
I personally will often put in UserNotifications (and make sure to go into settings and turn on persistent notifications so I don’t miss them).
Another approach that I’ve done is to log these events in some table in my app’s persistent storage and then have some UI within the app to fetch this data so I can confirm what happened.
I’ll often use Unified Logging so that I can watch os_log statements issued by my device from the macOS Console even when Xcode is not running. This is very useful in logging app/scene methods. See WWDC 2016 Unified Logging and Activity Tracing
Whatever you do, for things like background processing, background app refresh, etc., I will program some mechanism so that I can check to see if the requests/tasks took place, even when not attached to Xcode. Being attached to the debugger can, in some cases, affect the app lifecycle, and I want to make sure I’ve got some way to confirm what was going on without the benefit of the console.
Likely obvious, but make sure you never “force quit” the app, as that will stop background processes from taking place.
For more information, See WWDC 2019 video Advances in App Background Execution.

CXProviderDelegate Methods not called

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) {
...
}]

Sinch: How to dismiss Apple CallKit UI from the callee's lock screen

On an app to app call with using SinchCallKit demo app that is provided in the latest version (3.12), if the caller hangs up an ongoing call by calling [SINCall hangup] before the callee answers, the CallKit UI won't be removed from the callee's lock screen. It stays there forever.
So my question is here that how can we remove the CallKit lock screen UI from the callee's screen automatically. Is this is a server side issue or Apple handles this via push notifications?
It is a bug in the Sinch SDK and it has been fixed since 3.12.1, please update to the latest version and give it a try.
Perform an EndCallAction. You need the ID number you used to create the call object initially.
// Where you handle your call disconnect
CXEndCallAction *endCallAction = [[CXEndCallAction alloc] initWithCallUUID:call.callKitUUID];
CXTransaction *transaction = [[CXTransaction alloc] init];
[transaction addAction:endCallAction];
[self requestTransaction:transaction];
Here's the supporting -requestTransaction method:
- (void)requestTransaction:(CXTransaction *)transaction {
[self.callController requestTransaction:transaction completion:^(NSError * _Nullable error) {
if (error) {
SCILog(#"Error requesting transaction: %#", error.localizedDescription);
} else {
SCILog(#"Requested transaction successfully");
}
}];
}

App crashes in Low network like(EDGE,100 %loss)

My application implementing in-app purchase app crashes when connected to EDGE network, 100% loss,very poor network .There is no crash log. But it says "EXC BAD ACCESS code=1 address=0xc " on following line
_completionHandler(YES, skProducts);
Code for the method
#pragma mark - SKProductsRequestDelegate
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response {
sharedManager=[Mymanager sharedManager];
_productsRequest = nil;
sharedManager.bookidList=[[NSMutableArray alloc]init];
sharedManager.sharedProductPrice=[[NSMutableArray alloc]init];
NSArray * skProducts = response.products;
NSLog(#"sk product %#",skProducts);
// NSMutableArray *a=[[NSMutableArray alloc]init];
for (SKProduct * skProduct in skProducts) {
[sharedManager.sharedProductPrice addObject:skProduct.price];
[sharedManager.bookidList addObject:skProduct.productIdentifier];
}
[self updatePlist];
_completionHandler(YES, skProducts); //EXC BAD ACCESS CODE =1 ADDRESS=0XC
_completionHandler = nil;
}
I am following the in-app purchase tutorial on Ray Wenderlich's site (for iOS6.0). Minimum target of my application is iOS5. Any pointers how to fix this crash?
EDIT
New changes ,i made the NSArray to strong ARC property adn it still crashing check below images
tutorial
method to retrieve the product information from iTunes Connect:
- (void)requestProductsWithCompletionHandler:(RequestProductsCompletionHandler)completionHandler {
// 1
_completionHandler = [completionHandler copy];
// 2
_productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:_productIdentifiers];
_productsRequest.delegate = self;
[_productsRequest start];
}
This first squirrels a copy of the completion handler block inside the instance variable so it can notify the caller when the product request asynchronously completes.
It then creates a new instance of SKProductsRequest, which is the Apple-written class that contains the code to pull the info from iTunes Connect. It’s very easy to use – you just give it a delegate (that conforms to the SKProductsRequestDelegate protocol) and then call start to get things running.
We set the IAPHelper class itself as the delegate, which means that it will receive a callback when the products list completes (productsRequest:didReceiveResponse) or fails (request:didFailWithErorr).
Speaking of delegate callbacks, add those next! Add the following code before the #end:
It's quite likely that _completionHandler is nil when you are calling it in the method above. Attempting to execute a block variable when the variable is nil does give a bad access error.
You can confirm this by wrapping the execution of the completion handler block in an if statement and only executing it if the variable isn't nil:
if(_completionHandler)
{
_completionHandler(YES, skProducts);
}
Fixing the problem is another matter, you would need to trace the calls and see when the completion block is either not passed or removed.

Resources