Skip to content

Webex Calling using Mobile SDKs

Akshay Agarwal edited this page Jan 27, 2023 · 16 revisions

Webex Calling using Mobile SDKs

Webex Calling is a cloud-based phone system offered as part of Webex by Cisco that enables users to make and receive calls. With the new webex mobile SDK it is now possible to add calling capabilities to your own app.

This feature is available in SDK version 3.8.0 as a beta release for limited audience.

License requirements

A Webex Calling Professional license is required for using webex calling features in the mobile SDK. Please contact sales at https://pricing.webex.com/in/en/ or our support team at https://developer.webex.com/support for more information.

Prerequisites

Make sure you meet the following prerequisites before using the SDK calling features:

  • Register an app in the Developer Portal as described in the following link.
  • Follow the steps to integrate the SDK into your Android app as described in the following link.
  • For Webex Mobile SDK initialization and OAuth-based login configuration, refer to the first example in this link.
  • Incoming calls for Webex Calling are handled using APNS (Apple Push Notification Service) for iOS and FCM (Firebase Cloud Messaging) for Android. The APNS .p8 file and/or FCM server key from Firebase console, as well as the app ID, and the key ID (iOS only) need to be shared with the Webex developer support team so that the app can be provisioned on the backend.

Basic usage guide

1. Registering for callbacks

To be able to make and receive calls, the phone services needs to be ready. Once a user successfully signs into Webex, the authentication for Webex calling phone services happens in the background using the Single Sign On (SSO) mechanism. To know when the phone servivces are ready, an application can register the WebexUCLoginDelegate as given below. The onUCServerConnectionStateChanged callback in the delegate provides the phone services connection status as UCLoginServerConnectionStatus.Connected when it's ready for use.

class YourViewController: UIViewController {
    override func viewDidAppear(_ animated: Bool) {
        // You need to register your class as a delegate
        webex.ucLoginDelegate = self
    }
}

extension HomeViewController: WebexUCLoginDelegate {
    func onUCLoggedIn() {
        // login attempt was successful
    }
    
    func onUCLoginFailed() {
        // login attempt failed
    }
    
    func onUCServerConnectionStateChanged(status: UCLoginServerConnectionStatus, failureReason: PhoneServiceRegistrationFailureReason) {
        if status == .Connected {
            // server connection success
        }
    }
    
    func showUCSSOLoginView(to url: String) {
        webex.getUCSSOLoginView(parentViewController: self, ssoUrl: url) { success in
            if let success = success, success {
                // you're logged in
            }
        }
    }
    
    func showUCNonSSOLoginView() {
        webex.setCallServiceCredential(username: "user@example.com", password: "SuperSecret")
    }
}

2. Query phone services status

To query the current phone services status use the following API:

let phoneServicesStatus = webex.getUCServerConnectionStatus()
if (phoneServicesStatus == UCLoginServerConnectionStatus.Connected) {
    // Indicates that phone services are ready
}

3. Place an outgoing call

Once the phone services are ready, an outgoing call can be placed to any Webex calling or PSTN number. Typically, an application displays a dial pad UI to capture the phone number entered by a user. That phone number is then passed to the webex.phone.dial API. The dial API, if successful, provides a Call object. Use the Call object for further actions such as hangup, hold/resume, merge, transfer and many more. There are also certain call events that are emitted during the call for which an observer can be set:

webex.phone.dial("+1800123456", option: MediaOption.audioOnly()) { result in
    switch result {
    case .success(let call):
        call.onConnected = {
            // ...
        }
        call.onDisconnected = { reason in
            // ...
        }
    case .failure(let error):
        //call failure
    } 
}

4. Mute or unmute a call

To mute a call:

call?.sendingAudio = false 

To unmute a call:

call?.sendingAudio = true 

5. Hold or resume a call

To hold a call:

call.holdCall(putOnHold: true)

To resume a call:

call.holdCall(putOnHold: false)

To query the hold status of a call:

let isOnHold = call?.isOnHold()

6. Add a participant to a call

To add a participant to a call

  1. Put current active call on hold
oldCall?.holdCall(putOnHold: true)
  1. Start a call with new participant's number that can be merged later. Application needs to show a dial pad and capture the new participant's number.
call.startAssociatedCall(dialNumber: "+1800123457", associationType: .Merge, isAudioCall: true, completionHandler: { [weak self] result in
    switch result {
    case .success(let call):
        // Call association is successful
        let newCall: Call? = call
        // Store this call object
    case .failure(let error):
        // Call association failed
    }
})
  1. Merge old call with new one
// Merge call
newCall.mergeCall(targetCallId: oldCall.callId)

7. Assisted transfers

Suppose there is an ongoing call between user A and user B. User A wants to connect with user C and transfer the call so that user B and user C remain in the call while user A drops from the call after call gets connected. This type of transfer is called an assisted transfer. Initiate an assisted transfer using the same call?.startAssociatedCall APIs.

  1. Put current active call on hold
oldCall?.holdCall(putOnHold: true)
  1. Start a call with new participant's number that can be transferred later. Application needs to show a dial pad and capture the new participant's number.
call.startAssociatedCall(dialNumber: "+1800123457", associationType: .Transfer, isAudioCall: true, completionHandler: { [weak self] result in
    switch result {
    case .success(let call):
        // Call association is successful
        let newCall: Call? = call
        // Store this call object
    case .failure(let error):
        // Call association failed
    }
})
  1. Transfer the call to new participant
// Transfer call
oldCall.transferCall(toCallId: newCall.callId)

8. Direct transfers

There is also an option to blindly transfer a call to another participant without the call being established with new participant. This type of transfer is called a direct transfer. The application needs to capture the new number from the user and call the direct transfer API.

call?.directTransferCall(toPhoneNumber: phoneNumber, completionHandler: { err in
    if err == nil {
        // Direct transfer  is successful
    }
    else
    {
        // Direct transfer failed
    }
})

9. End a call

To end a call, call.hangup can be used. A call must be connected before it can be hanged up.

call.hangup(completionHandler: { error in
    if error == nil {
        // Call ended successful
    }
    else {
        // Call end failed
    }
})

Incoming call handling

Webex calling for incoming calls is designed to be delivered using APNS or FCM push channels on mobile devices (iOS and Android respectively). This enables the app to retrieve incoming calls when the app is in the foreground, background, or killed state. The following diagram gives an overview on how incoming call notifications are delivered: Webex Calling Incoming call overview

As shown in above diagram, an application needs to use the APIs provided by SDK to set push tokens, process push messages, and receive call notification.

NOTE: To setup Apple Push Notification Service on your iOS app refer this link.

To handle the VoIP type notification you'll need to setup CallKit in your app, refer this link.

  1. On successful login or whenever application receives new device token or VoIP token, it needs to be set using webex.phone.setPushTokens API.
webex.phone.setPushTokens(bundleId: "bundleid", deviceId: UIDevice.current.identifierForVendor?.uuidString ?? "", deviceToken: deviceToken, voipToken: voipToken)
  1. Webex incoming call payload is of type data message with custom keys. These would be delivered to PKPushRegistryDelegate class didReceiveIncomingPushWith function implemented by the application. Once the message is received it needs to be set towards SDK for processing. Application needs to check if the webex SDK is initialized and authorization is successful and also set a incoming call listener before processing the message.
if type == .voIP {
    // Report the call to CallKit, and let it display the call UI.
    guard let bsft = payload.dictionaryPayload["bsft"] as? [String: Any], let sender = bsft["sender"] as? String else {
        print("payload not valid")
        return
    }
    callKitManager?.reportIncomingCallFor(uuid: UUID(), sender: sender) {
        completion()
        self.establishConnection(payload: payload)
    }
} 
webex.initialize { [weak self] success in
    if success {
        webex.phone.onIncoming = { [weak self] call in
            self?.callKitManager?.updateCall(call: call)
        }
        do {
            let data = try JSONSerialization.data(withJSONObject: payload.dictionaryPayload, options: .prettyPrinted)
            let string = String(data: data, encoding: .utf8) ?? ""
            // Received push
            webex.phone.processPushNotification(message: string) { error in
                if let error = error {
                    // processPushNotification error
                    
                }
            }
        }
        catch (let error){
            // error
        }
        
    }
}

NOTE: If the call is answered by someone else or picked up on another device, we will get a normal APNS push, which need to be passed to the processPushNotification API.

func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
    guard let authType = UserDefaults.standard.string(forKey: "loginType") else { return }
    if authType == "jwt" {
        // init Webex using JWT
    } else if authType == "token" {
        // init Webex using token
    } else {
        // init Webex using oauth 
    }
    DispatchQueue.main.async {
        webex.initialize { success in
            if success {
                do {
                    let data = try JSONSerialization.data(withJSONObject: userInfo, options: .prettyPrinted)
                    let string = String(data: data, encoding: .utf8) ?? ""
                    // Received push
                    webex.phone.processPushNotification(message: string) { error in
                        if let error = error {
                            // processPushNotification error
                        }
                    }
                }
                catch (let error){
                    // error
                }
                
            }
        }
    }
}

The application will receive a incoming Call object in the observer Phone.IncomingCallListener set towards the SDK using webex.phone.setIncomingCallListener.

  1. The call can be answered using answer API.
call.answer(option: MediaOption.audioOnly(), completionHandler: { error in
    if error != nil {
        ...
    }
})
  1. An incoming call can be declined using reject API.
call?.reject(completionHandler: { error in
    if error == nil {
        // Reject call successful
    } else {
        // Reject call failed
    }
})

Incoming call push events

The following push events are sent from the Webex Calling Server. The type of message is denoted in the type field of the payload:

  1. NewCall - Indicates a new incoming call
  2. CallUpd - Possible values for reason: a. call_answered_alt_location - Call was answered on another location by a user. b. abandoned - Call was terminated by the caller. c. ring_no_answer - Call was redirected to service like Voice messaging.
  3. Dereg - Possible values for reason: a. DeviceTokenLimitExceeded - Indicates that maximum count of Push registrations has been reached and the oldest device registration belonging to this device is removed. b. RegistrationFailed - Indicates a Push registration failure.
  4. MWI - Indicates a voicemail update.
  5. RngSplsh - Indicates an incoming call is being redirected because modes like DND is set.

You can set a Call observer to get certain call state changes.

call.onConnected = {
    // Indicates call is successfully established
}

call.onDisconnected = { reason in
    // Indicates call is disconnected
}

call.onRinging = {
    // Indicates call is placed and is in ringing state
}

A few key events are:

  1. onRinging : Indicates that a call is placed and the remote participant's device is in the ringing state. An application can play a ring tone here.
  2. onConnected : Indicates that a call is successfully established. If a ring tone was playing, the application can stop it with this event. An application can show an appropriate in-call UI.
  3. onDisconnected : Indicates call is disconnected. The CallDisconnectedEvent event param has a call object of the disconnected call. This event is also notified if the call is answered on a different device by the same user or by a different user belonging to same hunt group. In such cases, if the incoming call notification is shown by the application then it needs to be dismissed.

Incoming call sequence diagram: Incoming call sequence diagram

Phone services sign in/sign out APIs

Applications can sign out of only Webex calling phone services to stop receiving and making calls while users are still able to use other functionality such as messaging and meeting.

1. Sign out of phone services

To sign out of phone services:

webex.phone.disconnectPhoneServices(completionHandler: { result in
    switch result {
    case .success:
        // PhoneServices disconnected successfully
    case .failure:
        // PhoneServices disconnection failure
    }
})

NOTE: The actual phone service status is notified in onUCServerConnectionStateChanged callback.

2. Sign in of phone services

To sign in to phone services:

webex.phone.connectPhoneServices(completionHandler: { result in
    switch result {
    case .success:
        // PhoneServices connected successfully
    case .failure:
        // PhoneServices connection failure
    }
})

NOTE: The actual phone service status is notified in onUCServerConnectionStateChanged callback.

Get the calling type of a user

The SDK supports different types of calling like CUCM and WebexCalling. A user can be associated with at most one calling type. The following API can be used to check the user's calling type, in this case, WebexCalling.

if(webex.phone.getCallingType() == Phone.CallingType.WebexCalling){
    // Indicates Webex Calling is supported for signed in user
}

Clone this wiki locally