-
Notifications
You must be signed in to change notification settings - Fork 31
Webex Calling Usage Guide
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.
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.
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)
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(failureReason: UCLoginFailureReason) {
super.onUCLoginFailed(failureReason)
}
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()
...
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
}
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
To mute a call:
call?.setSendingAudio(false)
To unmute a call:
call?.setSendingAudio(true)
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
To add a participant to a call
- Put current active call on hold
oldCall?.holdCall(true)
- 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
}
})
- 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.
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.
- Put current active call on hold
oldCall?.holdCall(true)
- 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
}
})
- 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.
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
}
})
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
}
})
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:
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.
- Org admin creates a firehose webhook using below once.
{
"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.
-
The Webex incoming call payload is of type data message with custom keys. These are delivered to the
FirebaseMessagingService
classonMessageReceived
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 observerPhone.IncomingCallListener
set towards the SDK usingwebex.phone.setIncomingCallListener
. -
The call can be answered using
answer
API.call?.answer(MediaOption.audioOnly(), CompletionHandler { if (it.isSuccessful){ // ... } })
-
An incoming call can be declined using
reject
API.call?.reject { result -> if (result.isSuccessful) { // rejectCall successful } else { // rejectCall failed } }
"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:
-
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. -
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. -
onDisconnected
: Indicates call is disconnected. The CallDisconnectedEvent event param has acall
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. -
onInfoChanged
: Indicates call details or object id updated.
Incoming call sequence diagram:
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
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.
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.
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.
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
}
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()