Request microphone permissions from within your application code:
ActivityCompat.requestPermissions(
this,
arrayOf(Manifest.permission.RECORD_AUDIO),
MIC_PERMISSION_REQUEST_CODE // a code you set for monitoring whether or not the permission was granted
)
Configure your Android application for incoming calls.
Incoming calls take advantage of Firebase Cloud Messaging. To get started with Cloud Messaging follow the Firebase set up instructions here. **
Making an Outgoing Call
Outgoing calls are made using the createVoiceCall method. The active call is returned to the provided delegate or listener once it connects. During this time the call can be considered to be in progress.
val localNumber: PhoneNumberval remoteNumber ="2024561414"val listener: ActiveCallListenertelephonyClient.createVoiceCall(localNumber, remoteNumber, listener)
Interacting With an Active Call
The active call delegate (iOS) / listener (Android) must be provided when a call is created and is used to communicate active call events such as when the call connects, disconnects, or fails.
If a call connects successfully, the delegate/listener will be provided with anActiveVoiceCall object via the activeVoiceCallDidConnect method. This object provides functions for manipulating the call such as muting the microphone, ending the call, or switching the audio to speakerphone.
publicprotocolActiveCallDelegate {/// Notifies the delegate that the call has connected/// - Parameters:/// - call: The `ActiveVoiceCall`funcactiveVoiceCallDidConnect(_call: ActiveVoiceCall)/// Notifies the delegate that the call failed to connect/// - Parameters:/// - error: `CallingError` that occurred.funcactiveVoiceCallDidFailToConnect(withErrorerror: CallingError)/// Notifies the delegate that the call has been disconnected/// - Parameters:/// - call: The `ActiveVoiceCall`/// - error: Error that caused the call to disconnect if one occurred.funcactiveVoiceCall(_call: ActiveVoiceCall, didDisconnectWithErrorerror: Error?)/// Notifies the delegate that the call has been muted or un-muted/// - Parameters:/// - call: The `ActiveVoiceCall`/// - isMuted: Whether outgoing call audio is mutedfuncactiveVoiceCall(_call: ActiveVoiceCall, didChangeMuteStateisMuted: Bool)/// Called when the system audio route has changed./// Use `call.isOnSpeaker` to determine if the call is on speaker.////// - Parameter call: The `ActiveVoiceCall`funcactiveVoiceCallAudioRouteDidChange(_call: ActiveVoiceCall)}
interfaceActiveCallListener : TelephonySubscriber {/** * Notifies the listener that the call has connected * @param call The `ActiveVoiceCall` */funactiveVoiceCallDidConnect(call: ActiveVoiceCall)/** * Notifies the listener that the call failed to connect * @param exception An Exception that occurred */funactiveVoiceCallDidFailToConnect(exception: Exception)/** * Notifies the listener that the call has been disconnected * @param call The `ActiveVoiceCall` * @param exception An optional Exception that is the cause of the disconnect if not null */funactiveVoiceCallDidDisconnect(call: ActiveVoiceCall, exception: Exception?)/** * Notifies the listener that the call has been muted or un-muted * @param call The `ActiveVoiceCall` * @param isMuted Whether outgoing call audio is muted */funactiveVoiceCallDidChangeMuteState(call: ActiveVoiceCall, isMuted: Boolean)/** * Notifies the listener that the call audio routing has changed * @param call The `ActiveVoiceCall` * @param audioDevice The `AudioDevice` that the audio has been routed to */funactiveVoiceCallDidChangeAudioDevice(call: ActiveVoiceCall, audioDevice: VoiceCallAudioDevice)}
Receiving an Incoming Call
VOIP Notifications for Incoming Calls. Receiving incoming voice calls on iOS and Android devices requires APNS and FCM push credentials, respectively. If you would like to enable this functionality, contact your solutions engineer to assist you in configuring your environments with push credentials for your applications.
Subscribe to Incoming Calls
In AppDelegate.swift import PushKit and add a PKPushRegistry variable:
Implement the PKPushRegistryDelegate methods to register, deregister and handle incoming call push notifications. When a push notification comes in, pass the payload from the voip push on to the SudoTelephonyClient along with an IncomingCallNotificationDelegate:
publicprotocolIncomingCallNotificationDelegate:class {/// Called when an incoming call is recieved. This is called before reporting the call to callkit so the correct/// display name can be provided./// /// In your implementation of this function you should set the `displayName` property of the incoming call that should be displayed by callKit.
/// /// You should also set the `delegate` of the incoming call. When the call is answered, `incomingCallAnswered(_ call: ActiveVoiceCall)` will be called
/// and future call related events will be delivered to the delegate.funcincomingCallReceived(_call: IncomingCall) ->Void/// Indicates that the incoming call was canceled./// - Parameter call: The incoming call/// - Parameter error: The error that occured, if any.funcincomingCall(_call: IncomingCall, cancelledWithErrorerror: Error?)/// An incoming call was answered through CallKit and is in the process of connecting./// No more updates on the incoming call will be provided to this delegate. /// Future call updates will be provided to the delegate set on the incoming call delegate in `incomingCallReceived(_ call: IncomingCall) -> Void`.
funcincomingCallAnswered(_call: ActiveVoiceCall)}
When the incomingCallReceived() function is called, you can take the IncomingCall object and either accept the call or decline the call. The accept method takes an ActiveCallDelegate instance which will then handle all of the call events.
IncomingCall:
publicprotocolIncomingCall:class {/// When an incoming call is recieved the delegate should be set in order to get updates on call related events./// For example if the call is answered through CallKit the delegate will recieve the call connected event with/// a handle to the call.var delegate: ActiveCallDelegate? { getset }/// The phone number receiving the callvar localNumber: String { get }/// The phone number the call is coming fromvar remoteNumber: String { get }/// The UUID for the callvar uuid: UUID { get }/// Accepts the call with a delegate to provide call lifecycle updates.funcaccept(withdelegate: ActiveCallDelegate)/// Decline the call.funcdecline()/// The display name used by callKit when reporting a callvar displayName: CallKitDisplayName? { getset }}
Subscribe to Incoming Calls
In order to receive calls you must first set up Firebase Cloud Messaging in your app. Instructions can be found here.
_**_Subscribe to incoming calls using the fcmToken provided by Firebase. The following code will fail if Google Play Services are not enabled on the device.
FirebaseInstanceId.getInstance().instanceId .addOnCompleteListener(OnCompleteListener { task ->if (!task.isSuccessful) {// failed to get instance idreturn@OnCompleteListener }val fcmToken = task.result!!.token sudoTelephonyClient.calling.registerForIncomingCalls(fcmToken) { result ->when (result) {is Result.Success -> {// successfully registered }is Result.Error -> {// failed to register } } } })
Handle Incoming Firebase Messages
When a message comes in from Firebase, pass it on to the SudoTelephonyClient along with an IncomingCallNotificationListener:
/** * Listener for receiving notifications about new `IncomingCall` events. */interfaceIncomingCallNotificationListener: TelephonySubscriber {/** * Notifies the listener that the call was received * @param call The `IncomingCall` */funincomingCallReceived(call: IncomingCall)/** * Notifies the listener that the call was canceled * @param call The `IncomingCall` */funincomingCallCanceled(call: IncomingCall, error: Throwable?)}
When the incomingCallReceived() function is called, you can take the IncomingCall object and either accept the call or decline the call. The accept method takes an ActiveCallListener instance which will then handle all of the call events.
IncomingCall:
classIncomingCall() {/** * The phone number receiving the call */var localNumber: String/** * The phone number the call is coming from */var remoteNumber: String/** * The unique string identifying the call invite */var callSid: String/** * Accepts the call * @param listener A listener to receive call lifecycle updates */funacceptWithListener(listener: ActiveCallListener)/** * Declines the call */fundecline()}
Manage Call Records
A call record is generated for each call made and received. The SDK provides three methods for accessing that data and one for deleting call records.
Get Call Records
Retrieve a list of call records with a provided PhoneNumber and an optional limit and token for pagination:
Subscribe to CallRecord events and state changes. On iOS the subscribeToCallRecords() function returns a SubscriptionToken which can be used to cancel the subscription. On Android the function takes a CallRecordSubscriber instance which will receive CallRecord updates.
let subscriptionToken =try telephonyClient.subscribeToCallRecords(resultHandler: { (result) inswitch result {case .success(let callRecord):// callRecord: CallRecordcase .failure(let error):// error: SudoTelephonyClientError }})
sudoTelephonyClient.calling.subscribeToCallRecords(this, "CallRecordSubscriberId")/** * Subscriber for receiving notifications about new `CallRecord` objects. */interfaceCallRecordSubscriber : TelephonySubscriber {/** * Notifies the subscriber of a new 'CallRecord'. */funcallRecordReceived(callRecord: CallRecord)}
A CallRecord contains all the information for a voice call made or received.
publicstructCallRecord {/// Enum that represents the direction of the call.publicenumDirection {case inboundcase outboundcase unknowninit(internalDirection: SudoTelephony.Direction) {switch internalDirection {case .inbound: self = .inboundcase .outbound: self = .outboundcase .unknown: self = .unknown } } }// Enum that represents the state of the call record.publicenumState {case authorizedcase queuedcase ringingcase answeredcase completedcase unansweredcase unknowninit(internalState: CallState) {switch internalState {case .authorized: self = .authorizedcase .queued: self = .queuedcase .ringing: self = .ringingcase .answered: self = .answeredcase .completed: self = .completedcase .unanswered: self = .unansweredcase .unknown: self = .unknown } } }/// Voicemail data belonging to a call recordpublicstructVoicemailData {/// Unique ID of the voicemail recordpubliclet id: String/// The duration of the voicemail recording in secondspubliclet durationSeconds: UInt/// The media object that can be used to download the voicemail recordingpubliclet media: MediaObject }/// Unique ID of the call recordpubliclet id: String/// The user ID of the owner of the call record (also referred to as the subject)publiclet owner: String/// The ID of the sudo that made or received the callpubliclet sudoOwner: String/// The ID of the `PhoneNumber` associated with the call recordpubliclet phoneNumberId: String/// The direction of the call. Either `outbound` or `inbound`publiclet direction: Direction/// The state of the call recordpubliclet state: State/// Timestamp that represents the last time the call was updatedpubliclet updated: Date/// Timestamp that represents the time the call was createdpubliclet created: Date/// The E164Number of the local phone numberpubliclet localPhoneNumber: String/// The E164Number of the remote phone numberpubliclet remotePhoneNumber: String/// The duration of the call in secondspubliclet durationSeconds: UInt32/// The voicemail data of this call record if it existspubliclet voicemail: VoicemailData?}
dataclassCallRecord(/** * Unique ID of the call record */val id: String,/** * The user ID of the owner of the call record (also referred to as the subject) */val owner: String,/** * The ID of the sudo that made or received the call */val sudoOwner: String,/** * The ID of the `PhoneNumber` associated with the call record */val phoneNumberId: String,/** * The direction of the call. Either `outbound` or `inbound` */val direction: Direction,/** * The state of the call record */val state: CallRecordState,/** * Timestamp that represents the last time the call was updated */val updated: Instant,/** * Timestamp that represents the time the call was created */val created: Instant,/** * The E164Number of the local phone number */val localPhoneNumber: String,/** * The E164Number of the remote phone number */val remotePhoneNumber: String,/** * The duration of the call in seconds */val durationSeconds: Int,/** * The voicemail data of this call record if it exists */val voicemail: VoicemailData?) : Parcelable/** * Enum that represents the state of the call record. */enumclassCallRecordState() { AUTHORIZED, QUEUED, RINGING, ANSWERED, COMPLETED, UNANSWERED, UNKNOWN;}/** * Voicemail data belonging to a call record */dataclassVoicemailData (/** * Unique ID of the voicemail record */val id: String,/** * The duration of the voicemail recording in seconds */val durationSeconds: Int,/** * The media object that can be used to download the voicemail recording */val media: MediaObject) : Parcelable
Voicemail
When an incoming call is rejected or times out, the caller is sent to voicemail. After hearing a recorded greeting, the caller can record up to 3 minutes of voicemail. The SDK provides several methods to manage these stored voicemail recordings.
Get Voicemails
Retrieve a list of voicemail records with a provided PhoneNumber and an optional limit and token for pagination:
Subscribe to Voicemail create events, update events, and delete events. On iOS the subscribeToVoicemails() function returns a SubscriptionToken which can be used to cancel the subscription. On Android the function takes a VoicemailSubscriber instance which will receive Voicemail events.
let subscriptionToken =try telephonyClient.subscribeToVoicemails(resultHandler: { (result) inswitch result {case .success(let voicemail):// voicemail: Voicemailcase .failure(let error):// error: SudoTelephonyClientError }})
sudoTelephonyClient.calling.subscribeToVoicemails(this, "VoicemailSubscriberId")/** * Subscriber for receiving notifications about new `Voicemail` updates. */interfaceVoicemailSubscriber : TelephonySubscriber {/** * Notifies the subscriber of a new 'Voicemail' update. */funvoicemailUpdated(voicemail: Voicemail)}
A Voicemail contains all the information for a voicemail recording.
publicstructVoicemail {/// Unique ID of the voicemail recordpubliclet id: String/// The ID of the Sudo Platform user that owns the voicemail.publiclet owner: String/// The ID of the Sudo that received the voicemail.publiclet sudoOwner: String/// The ID of the `PhoneNumber` associated with the voicemail.publiclet phoneNumberId: String/// The ID of the `CallRecord` associated with the voicemail.publiclet callRecordId: String?/// Timestamp that represents the time the voicemail was created.publiclet created: Date/// Timestamp that represents the last time the voicemail was updated.publiclet updated: Date/// The E.164 formatted phone number that received the voicemail.publiclet localPhoneNumber: String/// The E.164 formatted phone number of the caller.publiclet remotePhoneNumber: String/// The duration of the voicemail recording in seconds.publiclet durationSeconds: UInt/// The media object that can be used to download the voicemail recording.publiclet media: MediaObject}
dataclassVoicemail(/** * Unique ID of the voicemail record */val id: String,/** * The ID of the Sudo Platform user that owns the voicemail. */val owner: String,/** * The ID of the Sudo that received the voicemail. */val sudoOwner: String,/** * The ID of the `PhoneNumber` associated with the voicemail. */var phoneNumberId: String,/** * The ID of the `CallRecord` associated with the voicemail. */var callRecordId: String?,/** * Timestamp that represents the time the voicemail was created. */val created: Instant,/** * Timestamp that represents the last time the voicemail was updated. */val updated: Instant,/** * The E.164 formatted phone number that received the voicemail. */val localPhoneNumber: String,/** * The E.164 formatted phone number of the caller. */val remotePhoneNumber: String,/** * The duration of the voicemail recording in seconds. */val durationSeconds: Int,/** * The media object that can be used to download the voicemail recording. */val media: MediaObject) : Parcelable
Downloading Voicemail Audio Data
When voicemail records are retrieved, the audio data is not automatically downloaded. This gives you control over performance when retrieving voicemail records, particularly those with long recordings.
To download voicemail audio data, use the downloadData() method, passing it a MediaObject which can be retrieved from the media property of a Voicemail. This method returns the raw audio data.
Voicemail records and call records can be managed independently. However, a call record that has a corresponding voicemail has an additional voicemail property containing a copy of the voicemail data. When said voicemail is deleted this copy is also deleted.
let callRecord: CallRecordiflet voicemailData: CallRecord.VoicemailData = callRecord.voicemail {// The call record has associated voicemail data.// voicemailData.id: String// voicemailData.durationSeconds: UInt// voicemailData.media: MediaObject}
val callRecord: CallRecordcallRecord.voicemail?.let { voicemailData ->// The call record has associated voicemail data.// voicemailData.id: String// voicemailData.durationSeconds: Int// voicemailData.media: MediaObject}
The Voicemailrecord also has an optional callRecordId property which relates the voicemail to its call record if the call record has not been deleted.