iOS - make calls and get call state
New here? Learn about Bountify and follow @bountify to get notified of new bounties! x

We have an app we are working on where we want users to be able to initiate a phone call from within the app and ideally not use VOIP. The standard flow for iOS is to launch the target phone number to a dialog and the user clicks to call and then the default phone app takes over. Couple of questions

1- Can we make a call without this dialog step? so the user doesn't need to click again

2- Can we see the state of the call? our app needs to push something to an API (GET or POST request) call once the call state has established. We dont want to just assume it connected and then push as the phone call and the api call need to be timed

3- Do you have some sample code, sdk, lib, notes to point us in the right direction
4- if the above is not possible with native functions, is there a way to make a dialer app that would be able to perform these functions (as a last resort)

this bounty is to get thorough answers to the above on this topic. We are tyring to make a POC (not this bounty) with the following objective. (sharing for context)

We need a POC of an app that can automatically dial a number, and when the call is established, makes a POST request in the background.

There are two possible approaches that we need to look into,

a) Can we invoke the native dialer from an app and make a phone call, and receive a notification when the call is established so we can make a POST request. A POC for this includes the following

i) A simple app with a button in the middle, which when tapped
ii) Opens the native Android dialer and dials a static (can be hardcoded) number,
iii) When the call is established, makes a POST request using a background service. The POST request would include payload that the app would define, for this POC it can be a simple {code: "1234567890"} - the endpoint to make the POST request will be shared at a later time.

b) If #a isn't possible, can we make a custom dialer and make a phone call, and listen in on call status changes. A successful POC for this includes the following

i) A simple app with a button in the middle, which when tapped dials a hard coded number using a custom dialer
ii) When the call is established, makes a POST request using a asynchronous process. The POST request would include payload that the app would define, for this POC it can be a simple {code: "1234567890"} - the endpoint to make the POST request will be shared at a later time.
This is what you can do with VOIP phone call on iOS: https://developer.apple.com/documentation/callkit
minhtc 5 months ago
5 months ago
Tags
ios

Crowdsource coding tasks.

1 Solution


Hey Qdev, here's my research on the topic. I'm saying research because I don't have any actual experience for this specific problem, nor do I have access to an iOS development environment right now to manually test this. If you feel like this solution lacks credibility and doesn't qualify for the bounty - that's absolutely fine.

  1. "When a user opens a URL with the tel scheme in a native app, iOS 10.3 and later displays an alert and requires user confirmation before dialing", so unfortunately it's not possible to skip the confirmation dialog anymore. At least not in a manner that would get your app approved for the AppStore. Source

  2. It's possible to use CallKit to get not only VoIP, but also cellular call states. "VoIP apps typically interact with the CXCallObserver object returned by the callObserver property of a CXCallController instance. However, any app can create a new CXCallObserver object to be notified of any calls activity on the system." Source

Making a call:

let url = URL(string: "tel://123456789")
UIApplication.shared.open(url)

Getting call states: Check out this StackOverflow answer for a basic implementation example.

Now you need to keep your app working in the background so you can actually get the events and do the API calls while the user is on a call.

The most reliable way to do this would be to use Background Modes. Unfortunately I don't think your app qualifies for any of the background modes, so this is out of the question.

The next best option is Background Execution, specifically the Executing Finite-Length Tasks section. This will allow your app to stay active in the background for a short time (3 minutes seems to be reported as a common time). Although it's a short time, it should be more than enough to receive a connected call state. Depending on the nature of the call, it might even be enough time to receive a "call ended" state. Of course, it might very well not be the case, and the "call ended" state might never be received, if the call is too long and the application is suspended. But this is fine, because when the call ends, the user is immediately sent back to your application. This can be used to signal the API that the call has ended, in case we never got the "call ended" state.

Here's a basic implementation of background execution.

And here's what I imagine the overall workflow of the app would look like:

  • User opens application/applicationDidBecomeActive is fired
  • App checks for a saved state where the user has previously had a call connected, but did not receive a "call ended" state
    • If such state exists, immediately do an asynchronous API request to signal that the call has ended. (Might be worth checking CXCallObserver.calls property for any active calls, in case the user switched back to the app while still being on the call)
  • User is presented with a call button
  • User clicks the call button
  • App calls beginBackgroundTask and stores the returned task id
  • App registers a CXCallObserverDelegate to listen for states (or this might be registered at the activation of the app)
  • App launches the tel:// URI and makes the call
  • Inside the CXCallObserver delegate:
    • If a connected state is received:
      • App does an asynchronous API request to signal that the call has started
      • App sets/saves its state to signify that it's awaiting a "call ended" state
    • If a "call ended" state is received:
      • App does an asynchronous API request to signal that the call has ended
      • App removes its state that signifies that it's awaiting a "call ended" state
      • App calls endBackgroundTask with the previously saved background task id