Skip to content

Webex Calling Usage Guide

Rohit Sharma edited this page Oct 16, 2023 · 10 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.9.1 onwards.

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 delivered through webhook for mobile apps with SDK integrations. Org admin can create a firehose webhook with resource as "telephony_push" and event as "updated". Once a webhook payload is received in customer's backend server it should be extracted and passed on to the mobile app for further processing. Contents that needs to be passed to mobile app is detailed in further section. Normally these are delivered from customer's backend server to their mobile app integrating SDK through APNS(Apple Push Notification Service) or FCM (Firebase Cloud Messaging)

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.

    private class UCDelegate() : WebexUCLoginDelegate{

        override fun hideUCSSOBrowser() {
            super.hideUCSSOBrowser()
        }

        override fun loadUCSSOViewInBackground(ssoUrl: String) {
            super.loadUCSSOViewInBackground(ssoUrl)
        }

        override fun onUCLoggedIn() {
            super.onUCLoggedIn()
        }

        override fun onUCLoginFailed() {
            super.onUCLoginFailed()
        }

        override fun onUCSSOLoginFailed(failureReason: UCSSOFailureReason) {
            super.onUCSSOLoginFailed(failureReason)
        }

        override fun onUCServerConnectionStateChanged(status: UCLoginServerConnectionStatus, failureReason: PhoneServiceRegistrationFailureReason) {
            super.onUCServerConnectionStateChanged(status, failureReason)
            
            // UCLoginServerConnectionStatus indicates phone services connection status
        }

        override fun showUCNonSSOLoginView() {
            super.showUCNonSSOLoginView()
        }

        override fun showUCSSOBrowser() {
            super.showUCSSOBrowser()
        }
    }

    ...
    webex?.delegate = UCDelegate()
    ...

2. Query phone services status

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

    var 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. Once you're successfully connected to phone services, You can start making calls using the webex.phone.dialPhoneNumber() api. This api should be used to dial only phone numbers. Incase of meeting links, spaces, meeting numbers, sip uris and email addresses, you can use the regular webex.phone.dial() api. If you use webex.phone.dialPhoneNumber()to dial anything other than phone numbers, you will get a call failure with INVALID_API_ERROR. 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.dialPhoneNumber() 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:

    val mediaOption = MediaOption.audioOnly()
    webex.phone.dialPhoneNumber("+1800123456", mediaOption, CompletionHandler { result ->
        if (result.isSuccessful) {
            // Dial api is successful. result.data is the Call object
            // Store this call object for further actions
            val call = result.data
            call?.setObserver(object : CallObserver {
                override fun onConnected(call: Call?) {
                    super.onConnected(call)
                }

                override fun onDisconnected(event: CallObserver.CallDisconnectedEvent?) {
                    super.onDisconnected(event)
                }

                override fun onRinging(call: Call?) {
                    super.onRinging(call)
                }
            })
        } else {
            // Dial failed, result.error an instance of WebexError.ErrorCode that has details on the error
        }
    })

NOTE: The dialPhoneNumber API is available from v3.9.2 onwards

4. Mute or unmute a call

To mute a call:

    call?.setSendingAudio(false)

To unmute a call:

    call?.setSendingAudio(true) 

5. Hold or resume a call

To hold a call:

    call?.holdCall(true) 

To resume a call:

    call?.holdCall(false)

To query the hold status of a call:

    var isOnHold = call?.isOnHold() : false

6. Add a participant to a call

To add a participant to a call

  1. Put current active call on hold
    oldCall?.holdCall(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("+1800123457", associationType = CallAssociationType.Merge, audioCall = true, CompletionHandler { result ->
            if (result.isSuccessful) {
                // Call association is successful, result.data gives new Call object
                val newCall: Call? = result.data
                // Store this call object
            } else {
                // Call association failed
            }
        })
  1. Merge old call with new one
    // Merge call
    newCall?.mergeCalls(oldCall.getCallId())

NOTE: Adding a participant is also supported when already 2 calls are active. This enables the user to add a participant to one of the call while other call in the queue is in hold state.

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(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("+1800123457", associationType = CallAssociationType.Transfer, audioCall = true, CompletionHandler { result ->
        if (result.isSuccessful) {
            // Call association is successful, result.data gives new Call object
            val newCall: Call? = result.data
            // Store this call object
        } else {
            // Call association failed
        }
    }) 
  1. Transfer the call to new participant
    // Transfer call
    oldCall?.transferCall(newCall.getCallId())

NOTE: Assisted transfer is also supported when already 2 calls are active. This enables the user to consult transfer one of the calls and resume the other call in queue.

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, CompletionHandler { result ->
        if (result.isSuccessful) {
            // 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 { result ->
            if (result.isSuccessful) {
                // Call end successful
            } else {
                // Call end failed
            }
        })

Incoming call handling

Webex calling for incoming calls is designed to be delivered for SDK integrations through webhook. By registering a webhook, developer can specify a targetUrl of their server backend that can receive and dispatch required payload to mobile apps. Typically, APNS or FCM push channels are used to deliver the payload. 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: wxc_incoming_call

Below are the steps that outline how to get a Webex calling payload through webhook through developer's backend. ANPS or FCM push channel is assumed for illustration.

    {
    "name": "Webex Calling XYZ Firehose",
    "targetUrl": "https://example.com/message-events", // Server url of backend server that can receive the webhook notification
    "resource": "telephony_push",
    "event": "updated"
    }
  • After successful login into mobile app that integrates SDK, application would send the Person Id and APNS or FCM token to their backend server and store it. Person Id would be required to look up a webex user when backend gets a webhook notification and push tokens is used to dispatch push notification. getMe() returns data that has personId.
    webex.people.getMe() { result ->
        val person = result.data
        person?.let {
            // Person id
            val personId = it.encodedId
        }
    }

NOTE: To setup Firebase Cloud Messaging for your Android app refer to the following link.

  • When backend server would get a webhook notification which is a json payload for incoming call. Root level key in json payload would have resource as "telephony_push". Server would retrieve the tokens for the user for whom this call is to be notified using "actorId" field at root level of json. Server should extract the json under data->apnsPayload and data->fcmPayload and dispatch apnsPayload to iOS devices and fcmPayload to android devices if any. Payload should not be altered. For APNS a voip notification can be sent.

  • Once the application receives the FCM or APNS payload, It should be passed to the SDK API. Further steps are detailed below.

  1. The Webex incoming call payload is of type data message with custom keys. These are delivered to the FirebaseMessagingService class onMessageReceived function implemented by the application. Once the message is received it needs to be set towards SDK for processing. The application needs to check if the webex SDK is initialized and authorization is successful, and also set an incoming call listener before processing the message.

        val authorised = webex.authenticator?.isAuthorized()
        if (authorised == true) { // SDK is initialized and authorized
            processPushMessageInternal(fcmDataPayload) {
                if (it.isSuccessful && it.data == PushNotificationResult.NoError) {
                    // Successfully sent payload to SDK
                }
            }
        } else { // SDK is not initialized. Application might have been in killed state
            webex.initialize {
                if (webex.authenticator?.isAuthorized() == true) {
                    processPushMessageInternal(fcmDataPayload) {
                        if (it.isSuccessful && it.data == PushNotificationResult.NoError) {
                            // Successfully sent payload to SDK
                        }
                    }
                }
            }
        }
        private fun processPushMessageInternal(msg :String, handler: CompletionHandler<PushNotificationResult>) {
            // SDK API
            if(!notRegistered){
                webex.phone.setIncomingCallListener(object : Phone.IncomingCallListener {
                    override fun onIncomingCall(call: Call?) {
                        // Using this call object, call can be answered 
                    }
                })
            }
    
            // SDK API to process push message
            webex.phone.processPushNotification(msg) {
                if (it.isSuccessful) {
                    // Successfully sent payload to SDK
                    if (it.data == PushNotificationResult.NoError) {
                        handler.onComplete(ResultImpl.success(PushNotificationResult.NoError))
                    }
                    else {
                        // Handle error 
                    }
                }
            }
        }

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

  2. The call can be answered using answer API.

        call?.answer(MediaOption.audioOnly(), CompletionHandler {
                if (it.isSuccessful){
                    // ...
                }
            })
  3. An incoming call can be declined using reject API.

        call.reject { result ->
            if (result.isSuccessful) {
                // rejectCall successful
            } else {
                // rejectCall failed 
            }
        }

Incoming call push events

"telephony_push" events from webhook is for NewCall type notifications which signifies a new incoming call for a particular user.

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

    call?.setObserver(object : CallObserver {
        override fun onConnected(call: Call?) {
            // Indicates call is successfully established
        }

        override fun onDisconnected(event: CallObserver.CallDisconnectedEvent?) {
            // Indicates call is disconnected
        }

        override fun onRinging(call: Call?) {
            // Indicates call is placed and is in ringing state
        }

        override fun onInfoChanged(call: Call?) {
            // Indicates call info is updated
            // call.getExternalTrackingId() can be used here to retrieve tracking id.
        }
    })

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.
  4. onInfoChanged : Indicates call details or object id updated.

Incoming call sequence diagram:

fcm_sequence_diagram

Limit on number of Incoming Calls

Mobile SDK supports a maximum of 2 incoming calls. 3rd incoming call is ignored/silently dimissed. At any moment only one call can be in resumed state. Typical flow in the application for 2 incoming calls is as follows:

  • Application gets an incoming call notification
  • Notification is shown to the user and user accepts the call
  • Application gets another incoming call notification
  • Notification of the new call is shown to the user
  • If user accepts the new call, existing call is put on hold and new call is answered.
    • Calls can be switched between hold and resume state as needed.
    • Calls can also be hungup using their respective call object.
    • Application needs to handle the events notified by individual CallObserver and update UI accordingly
  • If user rejects the new call, new call is rejected and existing call continues to remains active

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{ result ->
        if (result.isSuccessful) {
            // Indicates API call is successful
        }else{
            // Indicates API call failed
        }
    }

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{ result ->
        if (result.isSuccessful) {
            // Indicates API call is successful
        }else{
            // Indicates API call failed
        }
    }

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
    }

Get External tracking Id

To log and track metrics application can use below API to retrieve a tracking Id. Please note this is only available after onInfoChanged callback in CallObserver is notified.

    call.getExternalTrackingId()
Clone this wiki locally