12

The documentation for CTCallCenter:setCallEventHandler: states that:

However, call events can also take place while your application is suspended. While it is suspended, your application does not receive call events. When your application resumes the active state, it receives a single call event for each call that changed state

The part relevant to this question is

When your application resumes the active state, it receives a single call event for each call that changed state

Implying the app will receive a call event for a call that took place in the past while the app was suspended. And this is possible according to the answer to this question: How does the Navita TEM app get call log information?

My question is: if my app is suspended and a call takes place, then when my app resumes the active state how can it retrieve the call event for the call that took place?

I have tried many, many code experiments but have been unable to retrieve any call information when my app resumes the active state.

This is the most simplest thing I have tried: 1) Create a new project using the Xcode single view application template. 2) Add the code shown below to didFinishLaunchingWithOptions 3) Launch the app 4) Task away from the app 5) Make a call from another device, answer the call, hang up the call from either device 6) Bring the app back to the foreground thus resuming the active state.

The code to register for call events is:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
        self.callCenter = [[CTCallCenter alloc] init];
        [self.callCenter setCallEventHandler:^(CTCall *call)
         {
             NSLog(@"Event handler called");
             if ([call.callState isEqualToString: CTCallStateConnected])
             {
                 NSLog(@"Connected");
             }
             else if ([call.callState isEqualToString: CTCallStateDialing])
             {
                 NSLog(@"Dialing");
             }
             else if ([call.callState isEqualToString: CTCallStateDisconnected])
             {
                 NSLog(@"Disconnected");

             } else if ([call.callState isEqualToString: CTCallStateIncoming])
             {
                 NSLog(@"Incomming");
             }
         }];  

    return YES;
}

With this code I am able to get call events if the app is in the foreground when the call occurs. But if I task away from the app before making the call then I am unable to get a call event when my app next resumes the active state - as it states it should in the Apple documentation.

Other things I have tried:

1) The documentation states that the block object is dispatched on the default priority global dispatch queue, so I have tried placing the registration of setCallEventHandler within dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{})

2) Calling setCallEventHandler: in appBecameActive instead of didFinishLaunchingWithOptions

3) Adding background abilities to the app - via beginBackgroundTaskWithExpirationHandler and/or location updates using startUpdatingLocation or startMonitoringForSignificantLocationChanges.

4) Various combinations of the above.

The bounty will be awarded once I get code running on my device which is able to get call events that took place while the app was suspended.

This is on iOS 7.

Community
  • 1
  • 1
Gruntcakes
  • 37,738
  • 44
  • 184
  • 378
  • Add a log as the first line of didFinishLaunchingWithOptions and run one of your failing tests again, does it log? – Wain Jan 17 '14 at 21:25
  • Yes. I don't have a problem with the basic setup of use of the handler - I can get the updates if the app is in the foreground and as I mentioned in 2) I can also get them in the background until the expiration handler expires. Thus it is being registered within didFinishLaunchingWithOptions and invoked. – Gruntcakes Jan 17 '14 at 22:15
  • I was actually wondering of the callback was being replaced somehow and a new session was started so the list of events was lost... – Wain Jan 17 '14 at 22:19
  • Ah I see. didFinishLaunchingWithOptions is only getting called once over the course of moving to the background/foreground. – Gruntcakes Jan 17 '14 at 22:22
  • 1
    Something else I've just noticed - if the app is in the foreground when I make a call from another device, then the app gets the incoming and connected events before moving to the background. After the call terminates the app becomes active again but it didn't ever receive the disconnected event. Shouldn't it have received that? – Gruntcakes Jan 17 '14 at 22:25
  • It certainly should have. I have previously only tracked calls while also tracking location and from a singleton controller (not app delegate). I haven't seen a similar situation to yours. – Wain Jan 17 '14 at 22:39
  • I just saw something about the event handler being dispatched on the the default priority global dispatch queue, so am trying some experiments registering it using dispatch_asyn. But no luck so far. – Gruntcakes Jan 17 '14 at 22:44
  • I've found if I do it in combination with monitoring for background location changes then I can get events in the background after the expiration hander's time has expired. However the location updates have to be done using startUpdatingLocation and not startMonitoringSignificantLocationChanges. However if I disable location on the phone then I don't get the the call notifications in the background or later when the app moves to the foreground, so still miss them. So still wondering how to get missed ones and how Navita is implemented to get them even when location is disabled. – Gruntcakes Jan 17 '14 at 23:09

2 Answers2

15

I've found a solution but I have no idea why it's working. Only thing I can think of is a bug in GCD and/or CoreTelephony.

Basically, I allocate two instances of CTCallCenter like this

void (^block)(CTCall*) = ^(CTCall* call) { NSLog(@"%@", call.callState); };

-(BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions
{
    callCenter1 = [[CTCallCenter alloc] init];
    callCenter1.callEventHandler = block;

    callCenter2 = [[CTCallCenter alloc] init];
    callCenter2.callEventHandler = block;

    return YES;
}

Similar Code in Swift:

func block (call:CTCall!) {
        println(call.callState)
    }

func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
        //Declare callcenter in the class like 'var callcenter = CTCallCenter()'
        callcenter.callEventHandler = block

        return true
    }

To test this I made a call, answered it and then hanged up it while app was in background. When I launched it I received 3 call events: incoming, connected, disconnected.

siegy22
  • 4,295
  • 3
  • 25
  • 43
creker
  • 9,400
  • 1
  • 30
  • 47
  • 2
    Thanks. As you provided a solution that works I'll give you the bounty, however I would like to know if/what the "proper" solution is. As Apple state this behavior in their documentation there must presumably be another method of doing this, we hope, if the above is due to a bug then its fragile to rely on it. – Gruntcakes Jan 20 '14 at 20:38
  • Well, the documentation clearly states that it's not complete or final. At least when I look at it with Xcode 5. So it's no good to rely on the documentation either. – creker Jan 20 '14 at 20:42
  • Hi Guys, This aint working for me. After launching the app and going to the home screen. I am calling myself, receive and hangup the call but still i dont see anything being logged. Am I missing something here? – Obj-Swift Mar 19 '14 at 19:18
  • @akash, this is a hack that worked for me but I have no idea why. You may have a different device with different iOS version which is why it's not working for you. There is related question http://stackoverflow.com/questions/21193088/how-does-the-navita-tem-app-get-call-log-information Also it's much better and don't use any hacks, it uses audio background mode just to keep running in the background. Apple documentation clearly states that such an app will be rejected. Developers of Navita TEM got lucky. – creker May 01 '14 at 13:42
  • worked for me but found 1 issue. Sometimes I get connected event after getting disconnected event. – DareDevil May 30 '14 at 10:59
  • In my situation block never called... i put call logic in applicationDidFinishWithLaunching.. – Ashish Ramani Jun 23 '14 at 13:22
  • 1
    This works like a charm for app is in foreground Mode But Not working when app is in "BackGround Mode". Any help please .. – Vishal Sharma May 26 '15 at 12:34
  • What seems to work for me (somewhat in background mode as well) is allocating the 2 CTCallCenter as mentioned in the answer but using one for the event handler only and one for the currentCalls property. It seems this way the currentCalls property is consistent and sometimes the event handler can be used as well (not always). In accordance with http://stackoverflow.com/questions/26404449/ctcallcenter-doesnt-update-the-currentcalls-property-after-i-set-calleventhandl . – Hooloovoo Jun 25 '15 at 19:10
3

In my case, I was working on an enterprise app which doesn't need to be approved by Apple's app market - so if you develop an enterprise app this solution is for you.

Also, the chosen answer didn't work while the app is the background.

The solution is simple, basically you just need to add 2 capabilities (VOIP & Background fetch) in the Capabilities tab:

  • Your project target -> Capabilities -> Background Modes -> mark Voice over IP & Background fetch

enter image description here

Now, your app is registered to the iOS framework calls "delegate" so the OP code snip solution:

[self.callCenter setCallEventHandler:^(CTCall *call)
     {
         NSLog(@"Event handler called");
         if ([call.callState isEqualToString: CTCallStateConnected])
         {
             NSLog(@"Connected");
         }
         else if ([call.callState isEqualToString: CTCallStateDialing])
         {
             NSLog(@"Dialing");
         }
         else if ([call.callState isEqualToString: CTCallStateDisconnected])
         {
             NSLog(@"Disconnected");

         } else if ([call.callState isEqualToString: CTCallStateIncoming])
         {
             NSLog(@"Incomming");
         }
     }];  

Would defiantly work and you will get notifications even if your app is in the background.

OhadM
  • 4,687
  • 1
  • 47
  • 57
  • Thats not a simple solution at all. You can't just add the voip capability - Apple would reject it from the app store unless it had a valid reason to be using voip i.e. it is an app that presents voip functionality to the user. Just adding voip like this would get your app rejected. – Gruntcakes Feb 12 '16 at 17:02
  • @Woofbeans In my case I was working on an enterprise app which doesn't need Apple's app store. I will change the "simple solution" phrase to the relevant one. Since this solution can be helpful if you develop an app which doesn't need the Apple app store. – OhadM Feb 12 '16 at 22:28
  • 1
    Hey, sorry to bump an old thread. I've tried what you provided above, but I still cannot get any code working when app is in the background. I launch the app, then press home button, then make a call, nothing happens. However after this point if I run my app, then I get call information. Can I get information about a call while app stays in the background? – NecipAllef Oct 04 '16 at 13:46
  • 1
    @NecipAllef, Back in the day when I worked on that project, the app actually did something in the background. If the app would do something in the background (red strip above the status bar while app is in the BG) you would get the callback. If the app wouldn't do something in the background you would get the callback (If I recall) the next time the app will go to the FG. Sorry but this is what I remember from this project. You can try and check though... – OhadM Oct 04 '16 at 15:56