Detecting phone calls when the app is asleep - objective-c

What i'm trying to do is an app that runs a code when a call is incoming. I know I cannot get any details of the caller and that is fine.
My issue so far is that it's only working when the app is in foreground.
This is my code:
#import <CoreTelephony/CoreTelephonyDefines.h>
#import <CoreTelephony/CTCallCenter.h>
#import <CoreTelephony/CTCall.h>
CTCallCenter *callCenter = [[CTCallCenter alloc] init];
[callCenter setCallEventHandler:^(CTCall *call) {
ONLog(#"Event handler called");
if ([call.callState isEqualToString: CTCallStateConnected]) {
ONLog(#"Connected");
} else if ([call.callState isEqualToString: CTCallStateDialing]) {
ONLog(#"Dialing");
} else if ([call.callState isEqualToString: CTCallStateDisconnected]) {
ONLog(#"Disconnected");
} else if ([call.callState isEqualToString: CTCallStateIncoming]) {
ONLog(#"Incomming");
}
}];
Can someone help me with making this run as a background task?
EDIT:
I used Richas' code like this:
__block UIBackgroundTaskIdentifier yourBackgroundTaskName;
yourBackgroundTaskName = [application beginBackgroundTaskWithExpirationHandler:^{
// CODE FOR EXPIRATION HANDLER BLOCK
[application endBackgroundTask: yourBackgroundTaskName];
yourBackgroundTaskName = UIBackgroundTaskInvalid;
}];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// DO YOUR STUFF HERE. START OF BACKGROUND TASK.
NSLog(#"Background time Remaining: %f",[[UIApplication sharedApplication] backgroundTimeRemaining]);
self.callCenter = [[CTCallCenter alloc] init];
[self.callCenter setCallEventHandler:^(CTCall *call) {
ONLog(#"Event handler called");
if ([call.callState isEqualToString: CTCallStateConnected]) {
ONLog(#"Connected");
} else if ([call.callState isEqualToString: CTCallStateDialing]) {
ONLog(#"Dialing");
} else if ([call.callState isEqualToString: CTCallStateDisconnected]) {
ONLog(#"Disconnected");
} else if ([call.callState isEqualToString: CTCallStateIncoming]) {
ONLog(#"Incomming");
}
}];
//WHEN YOU ARE DONE WITH YOUR TASK, TELL THAT TO SYSTEM
[application endBackgroundTask: yourBackgroundTaskName];
yourBackgroundTaskName = UIBackgroundTaskInvalid;
});
It only buys me 180 seconds of background running...
It's not working :) The call is not detected

You should check for following two methods for your problem:
- (UIBackgroundTaskIdentifier)beginBackgroundTaskWithName:(NSString *)taskName expirationHandler:(void (^)(void))handler`
- (UIBackgroundTaskIdentifier)beginBackgroundTaskWithExpirationHandler:(void (^)(void))handler
You can implement any of them in applicationDidEnterBackground & that can buy you some time (5-10 minutes). Code might be something like following:
- (void)applicationDidEnterBackground:(UIApplication *)application
{
UIBackgroundTaskIdentifier yourBackgroundTaskName;
yourBackgroundTaskName = [application beginBackgroundTaskWithExpirationHandler:^{
// CODE FOR EXPIRATION HANDLER BLOCK
[application endBackgroundTask: yourBackgroundTaskName];
yourBackgroundTaskName = UIBackgroundTaskInvalid;
}];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// DO YOUR STUFF HERE. START OF BACKGROUND TASK.
//WHEN YOU ARE DONE WITH YOUR TASK, TELL THAT TO SYSTEM
[application endBackgroundTask: yourBackgroundTaskName];
yourBackgroundTaskName = UIBackgroundTaskInvalid;
});
}
Hope, this should at least get you started.

Related

Rest API post call is failing when app in background

I'm using AWS Transfer Utility to upload media to the S3 bucket and it's working fine in the background but after the media upload, I have to call a REST API (POST) call to my server which is failing when the app is in the background. How can I call rest API when the app is in the background?
Here is my code
- (void)didUploadedAllMedias:(BOOL)yesOrNo {
if (yesOrNo) {
UIApplicationState state = [[UIApplication sharedApplication] applicationState];
if (state == UIApplicationStateBackground){
[self notifyUserForMedia:YES];
}
self.postStatus = BG_SERVICE_STATUS_FINISHING_UP;
if (self.delegate && [self.delegate respondsToSelector:#selector(finishingUP)]) {
[self.delegate finishingUP];
[self executeWebServiceCall];
}
}
else {
self.postStatus = BG_SERVICE_STATUS_ERROR;
UIApplicationState state = [[UIApplication sharedApplication] applicationState];
if (state == UIApplicationStateBackground){
[self notifyUserForMedia:YES];
}
}
}
- (void)executeWebServiceCall {
UIApplicationState state = [[UIApplication sharedApplication] applicationState];
if (state == UIApplicationStateBackground){
__weak typeof(self) weakSelf = self;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
__strong typeof(weakSelf) strongSelf = weakSelf;
if(strongSelf){
[strongSelf submitPost];
}
});
}else {
__weak typeof(self) weakSelf = self;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
__strong typeof(weakSelf) strongSelf = weakSelf;
if(strongSelf){
[strongSelf submitPost];
}
});
}
}
On the foreground, it works perfectly fine. In the background, it's like waiting for the app to come in the foreground and executed automatically, and sometimes the request timed out.

objective C FCM how to get remote message when app in background

my code can get remote notification and remote message, the last problem is can not getting remote message when app in background ( just open app then got it)
When i push remote notification and app in background, it's seem notification message show like expect, click to this notification then it bring me to my app. I dont know why remote message does not have some behavior like that( I using postman and "https://fcm.googleapis.com/fcm/send" to send remote message)
. Plz someone help me
Here is my code
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Register for remote notifications
if (floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_7_1) {
// iOS 7.1 or earlier. Disable the deprecation warnings.
UIRemoteNotificationType allNotificationTypes =
(UIRemoteNotificationTypeSound |
UIRemoteNotificationTypeAlert |
UIRemoteNotificationTypeBadge);
[application registerForRemoteNotificationTypes:allNotificationTypes];
} else {
// iOS 8 or later
// [START register_for_notifications]
if (floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_9_x_Max) {
UIUserNotificationType allNotificationTypes =
(UIUserNotificationTypeSound | UIUserNotificationTypeAlert | UIUserNotificationTypeBadge);
UIUserNotificationSettings *settings =
[UIUserNotificationSettings settingsForTypes:allNotificationTypes categories:nil];
[[UIApplication sharedApplication] registerUserNotificationSettings:settings];
} else {
// iOS 10 or later
UNAuthorizationOptions authOptions =
UNAuthorizationOptionAlert
| UNAuthorizationOptionSound
| UNAuthorizationOptionBadge;
[[UNUserNotificationCenter currentNotificationCenter]
requestAuthorizationWithOptions:authOptions
completionHandler:^(BOOL granted, NSError * _Nullable error) {
}
];
// For iOS 10 display notification (sent via APNS)
[[UNUserNotificationCenter currentNotificationCenter] setDelegate:self];
// For iOS 10 data message (sent via FCM)
[[FIRMessaging messaging] setRemoteMessageDelegate:self];
}
[[UIApplication sharedApplication] registerForRemoteNotifications];
// [END register_for_notifications]
}
// [START configure_firebase]
[FIRApp configure];
// [END configure_firebase]
// Add observer for InstanceID token refresh callback.
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(tokenRefreshNotification:)
name:kFIRInstanceIDTokenRefreshNotification object:nil];
return YES;
}
// [START receive_message]
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo {
// If you are receiving a notification message while your app is in the background,
// this callback will not be fired till the user taps on the notification launching the application.
// TODO: Handle data of notification
// Print message ID.
NSLog(#"didReceiveRemoteNotification1 ID: %#", userInfo[#"gcm.message_id"]);
// Print full message.
NSLog(#"didReceiveRemoteNotification1 %#", userInfo);
}
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
// If you are receiving a notification message while your app is in the background,
// this callback will not be fired till the user taps on the notification launching the application.
// TODO: Handle data of notification
// Print message ID.
NSLog(#"didReceiveRemoteNotification2 ID: %#", userInfo[#"gcm.message_id"]);
// Print full message.
NSLog(#"didReceiveRemoteNotification2 %#", userInfo);
}
- (void)userNotificationCenter:(UNUserNotificationCenter *)center
willPresentNotification:(UNNotification *)notification
withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler {
// Print message ID.
NSDictionary *userInfo = notification.request.content.userInfo;
NSLog(#"willPresentNotification: %#", userInfo[#"gcm.message_id"]);
// Print full message.
NSLog(#"willPresentNotification %#", userInfo);
}
- (void)userNotificationCenter:(UNUserNotificationCenter *)center
didReceiveNotificationResponse:(UNNotificationResponse *)response
withCompletionHandler:(void (^)())completionHandler {
NSDictionary *userInfo = response.notification.request.content.userInfo;
NSLog(#"didReceiveNotificationResponse: %#", userInfo);
}
- (void)applicationReceivedRemoteMessage:(FIRMessagingRemoteMessage *)remoteMessage {
NSLog(#"%#", [remoteMessage appData]);
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Alert" message:[NSString stringWithFormat:#"%#", [remoteMessage appData]] delegate:self cancelButtonTitle:#"Cancel" otherButtonTitles:#"Ok", nil];
[alert show];
}
- (void)tokenRefreshNotification:(NSNotification *)notification {
NSString *refreshedToken = [[FIRInstanceID instanceID] token];
NSLog(#"InstanceID token: %#", refreshedToken);
[self connectToFcm];
}
- (void)connectToFcm {
[[FIRMessaging messaging] connectWithCompletion:^(NSError * _Nullable error) {
if (error != nil) {
NSLog(#"Unable to connect to FCM. %#", error);
} else {
NSLog(#"Connected to FCM.");
}
}];
}
- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error {
NSLog(#"Unable to register for remote notifications: %#", error);
}
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
NSLog(#"APNs token retrieved: %#", deviceToken);
[[FIRMessaging messaging] setAPNSToken:deviceToken];
}
- (void)applicationDidBecomeActive:(UIApplication *)application {
[self connectToFcm];
}
- (void)applicationDidEnterBackground:(UIApplication *)application {
[[FIRMessaging messaging] disconnect];
NSLog(#"Disconnected from FCM");
}
For other people who face same problem.
Finally after a lot of searching, i found the solution, very simple, u need to follow the content of firebase app message to make it readable by push notification service. U can see here
and here
this is sample to make it show to device when in background mode :)
{
"condition": "'condition1' in topics && 'condition2' in topics",
"notification": {
"category": "notification_category",
"title_loc_key": "notification_title",
"body_loc_key": "notification_body",
"badge": 1
},
"data": {
"data_type": "notification_data_type",
"data_id": "111111",
"data_detail": "FOO",
"data_detail_body": "BAR"
}
}
Now everything work like expect, then the second job is just modify message content like whatever u want :)

Run methods in background on certain intervel

(void)applicationWillResignActive:(UIApplication *)application {
NSLog(#"to background");
UIApplication *app = [UIApplication sharedApplication];
// Request permission to run in the background. Provide an
// expiration handler in case the task runs long.
NSAssert(bgTask == UIBackgroundTaskInvalid, nil);
bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
// Synchronize the cleanup call on the main thread in case
// the task actually finishes at around the same time.
dispatch_async(dispatch_get_main_queue(), ^{
if (bgTask != UIBackgroundTaskInvalid)
{
[app endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}
});
}];
// Start the long-running task and return immediately.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// Do the work associated with the task.
[self performSelector:#selector(hai) withObject:nil afterDelay:30];
NSLog(#"App staus: applicationDidEnterBackground");
dispatch_async(dispatch_get_main_queue(), ^{
if (bgTask != UIBackgroundTaskInvalid)
{
[app endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}
});
});
}

Activity Indicator Continues after Block

got this code:
[self setQRCodeScannerMode:false];
[self.activityIndicator startAnimating];
TBXMLSuccessBlock successBlock = ^(TBXML *tbxmlDocument) {
if ([[TBXML elementName:tbxmlDocument.rootXMLElement] isEqualToString:#"xxxxxx"]){
[self setQRCodeScannerMode:true];
} else {
[self setQRCodeScannerMode:true];
}
};
The thing is that when I set my scan mode to true inside the method [setQRCodeScannerMode] I'm stopping the activity indicator.
But surprise!!! the activity indicator is still working and messing with my view after some seconds.
What can I do?
You need to stop the activity indicator in your completion block:
[self setQRCodeScannerMode:false];
[self.activityIndicator startAnimating];
TBXMLSuccessBlock successBlock = ^(TBXML *tbxmlDocument) {
if ([[TBXML elementName:tbxmlDocument.rootXMLElement] isEqualToString:#"xxxxxx"]){
[self setQRCodeScannerMode:true];
} else {
[self setQRCodeScannerMode:true];
}
[self.activityIndicator stopAnimating];
};
This assumes the completion block is called on the main thread. If there is no guarantee that the completion block is called on the main thread you can do this:
[self setQRCodeScannerMode:false];
[self.activityIndicator startAnimating];
TBXMLSuccessBlock successBlock = ^(TBXML *tbxmlDocument) {
if ([[TBXML elementName:tbxmlDocument.rootXMLElement] isEqualToString:#"xxxxxx"]){
[self setQRCodeScannerMode:true];
} else {
[self setQRCodeScannerMode:true];
}
if ([NSThread isMainThread]) {
[self.activityIndicator stopAnimating];
} else {
dispatch_async(dispatch_get_main_queue(), ^{
[self.activityIndicator stopAnimating];
});
}
};
You are supposed to call [self.activityIndicator stopAnimating]; after you are finished with it, if thats not working for you, try this!
[self.activityIndicator performSelector:#selector(stopAnimating) withObject:nil afterDelay:0.1];
Hope that Helps!
One thing that you can do is working with the indicator on a different thread i.e. not on the Main Thread. You can use NSOperationQueue and NSInvocationOperation
simply add this code after the block
[self.activityIndicator stopAnimating];

Problem with UIImageView loading via OperationQueue

I load a UIImageView using an NSOperationQueue.
The load fetches an image from the Internet and then adds it to an image view. The problem I have is that the method finishes but it takes about 3 seconds later for the image view to actually either show the image or remove the image view from the superview...
- (void)viewDidLoad { NSLog(#"AirportDetailView: viewDidLoad");
[super viewDidLoad];
[self.activity startAnimating];
self.queue = [NSOperationQueue new];
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:#selector(loadImage) object:NULL];
[self.queue addOperation:operation];
[operation release];
}
-(void)loadImage {
[myAp ImageForAp];
NSLog(#"ImageFor Ap Ended, %#",myAp.ApDiagram);
[self.activity stopAnimating];
if (myAp.ApDiagram==NULL) {
NSLog(#"Finally Gets Here");
[self.Diagram removeFromSuperview];
}
else {
NSLog(#"Finally Gets Here with Diag");
[self.Diagram setBackgroundImage:myAp.ApDiagram forState:UIControlStateNormal];
}
}
The NSLOG shows a delay between the first two log statements of about 3 seconds can't understand why....
Updated with my Latest Code......
-(void)loadImage {
[myAp ImageForAp];
NSLog(#"ImageFor Ap Ended, %#",myAp.ApDiagram);
[self performSelectorOnMainThread:#selector(UpdateUI) withObject:nil waitUntilDone:NO];
}
-(void)UpdateUI {
[self.activity stopAnimating];
if (myAp.ApDiagram==NULL) {
NSLog(#"Finally Gets Here");
[self.Diagram removeFromSuperview];
}
else {
NSLog(#"Finally Gets Here with Diag");
[self.Diagram setBackgroundImage:myAp.ApDiagram forState:UIControlStateNormal];
}
}
Make sure that the loadImage method is being run on the main thread. All UI operations need to happen on the main thread to work as expected. Your code will need to look similar to this.
-(void)loadImage {
[myAp ImageForAp];
[self performSelectorOnMainThread:#selector(UpdateUI) withObject:nil waitUntilDone:NO];
}
-(void)UpdateUI {
[self.activity stopAnimating];
if (myAp.ApDiagram==NULL) {
[self.Diagram removeFromSuperview];
}
else {
[self.Diagram setBackgroundImage:myAp.ApDiagram forState:UIControlStateNormal];
}
}
There is also a possible memory leak inside of viewDidLoad.
self.queue = [NSOperationQueue new]; should be changed to
self.queue = [[[NSOperationQueue alloc] init] autorelease];