Set up one-to-one calling and push notifications in the UI Library

The UI Library provides out-of-the-box support for making one-to-one calls by using Azure Communication Services participant identifiers. To support one-to-one calling, the UI Library provides incoming call notifications. You can also use Azure Communication Services as an Azure Event Grid event source for calls.

In this article, you learn how to make one-to-one calls correctly by using the UI Library in your application.

Important

This feature of Azure Communication Services is currently in preview.

Preview APIs and SDKs are provided without a service-level agreement. We recommend that you don't use them for production workloads. Some features might not be supported, or they might have constrained capabilities.

For more information, review Supplemental Terms of Use for Microsoft Azure Previews.

Prerequisites

Set up the features

For more information, see the open-source Android UI Library and the sample application code.

Set up permissions for push notifications

To set up push notifications, you need a Firebase account with Firebase Cloud Messaging (FCM) enabled. Your FCM service must be connected to an Azure Notification Hubs instance. For more information, see Communication Services notifications. You also need to use Android Studio version 3.6 or later to build your application.

For the Android application to receive notification messages from FCM, it needs a set of permissions. In your AndroidManifest.xml file, add the following set of permissions after the <manifest ...> or </application> tag.

<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.GET_ACCOUNTS"/>
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />

Register for push notifications

To register for push notifications, the application needs to call registerPushNotification() on a CallComposite instance with a device registration token.

To get the device registration token, add the Firebase SDK to your application module's build.gradle instance. To receive notifications from Firebase, integrate Azure Notification Hubs by following the instructions in Communication Services notifications.

To avoid current limitations, you can skip registerPushNotification by using Azure Event Grid for push notifications. For more information, see Connect calling native push notifications with Azure Event Grid.

    val deviceRegistrationToken = "" // From Firebase
    callComposite.registerPushNotification(
        applicationContext,
        CallCompositePushNotificationOptions(
            CommunicationTokenCredential...,
            deviceRegistrationToken,
            displayName
        )
    )

Handle push notifications

To receive push notifications for incoming calls, call handlePushNotification on a CallComposite instance with a payload.

To get the payload from FCM, begin by creating a new service (File > New > Service > Service) that extends the FirebaseMessagingService Firebase SDK class and overrides the onMessageReceived method. This method is the event handler that's called when FCM delivers the push notification to the application.

    // On Firebase onMessageReceived
    val pushNotificationInfo = CallCompositePushNotificationInfo(remoteMessage.data)

    // If pushNotificationInfo.eventType is an incoming call
    val remoteOptions = CallCompositeRemoteOptions(
            pushNotificationInfo,
            communicationTokenCredential,
            displayName
        )
    callComposite.handlePushNotification(
            applicationContext,
            remoteOptions
        )

Register for incoming call notifications

To receive incoming call notifications after handlePushNotification, subscribe to IncomingCallEvent and IncomingCallEndEvent.

    private var incomingCallEvent: IncomingCallEvent? = null
    private var incomingCallEndEvent: IncomingCallEndEvent? = null

    class IncomingCallEndEvent : CallCompositeEventHandler<CallCompositeIncomingCallEndEvent> {
        override fun handle(eventArgs: CallCompositeIncomingCallEndEvent?) {
            // Display incoming call UI to accept/decline a call
        }
    }

    class IncomingCallEndEvent : CallCompositeEventHandler<CallCompositeIncomingCallEndEvent> {
        override fun handle(eventArgs: CallCompositeIncomingCallEndEvent?) {
            // Call-ended event when a call is declined or not accepted
        }
    }

    // Event subscription
    incomingCallEvent = IncomingCallEvent()
    callComposite.addOnIncomingCallEventHandler(incomingCallEvent)

    incomingCallEndEvent = IncomingCallEndEvent()
    callComposite.addOnIncomingCallEndEventHandler(incomingCallEndEvent)

    // Event unsubscribe
    callComposite.removeOnIncomingCallEventHandler(incomingCallEvent)
    callComposite.removeOnIncomingCallEndEventHandler(incomingCallEndEvent)

Handle calls

To accept calls, make a call to acceptIncomingCall. To decline calls, make a call to declineIncomingCall.

// Accept call
callComposite.acceptIncomingCall(applicationContext, localOptions)

// Decline call
callComposite.declineIncomingCall()

Dial other participants

To start calls with other participants, create CallCompositeStartCallOptions with participants' raw IDs from CommunicationIdentity and launch.

    val participant = [] // Participant raw IDs
    val startCallOption = CallCompositeStartCallOptions(participant)
    val remoteOptions = CallCompositeRemoteOptions(startCallOption, communicationTokenCredential, displayName)
    callComposite.launch(context, remoteOptions, localOptions)

Integrate TelecomManager samples

To integrate TelecomManager, use the samples provided in the open-source library. Use CallComposite APIs for hold, resume, mute, and unmute. Create CallComposite with CallCompositeTelecomIntegration.APPLICATION_IMPLEMENTED_TELECOM_MANAGER to use TelecomManager in an application.

callComposite.hold()
callComposite.resume()
callComposite.mute()
callComposite.unmute()

For more information, see the open-source iOS UI Library and the sample application code.

Set up push notifications

A mobile push notification is the pop-up notification that you get in the mobile device. For calling, this article focuses on voice over Internet Protocol (VoIP) push notifications.

The following sections describe how to register for, handle, and unregister push notifications. Before you start those tasks, complete these prerequisites:

  1. In Xcode, go to Signing & Capabilities. Add a capability by selecting + Capability, and then select Push Notifications.
  2. Add another capability by selecting + Capability, and then select Background Modes.
  3. Under Background Modes, select the Voice over IP and Remote notifications checkboxes.

Register for push notifications

To register for push notifications, the application needs to call registerPushNotification() on a CallComposite instance with a device registration token.

To avoid current limitations, you can skip registerPushNotification by using Azure Event Grid for push notifications. For more information, see Connect calling native push notifications with Azure Event Grid.

    let deviceToken: Data = pushRegistry?.pushToken(for: PKPushType.voIP)
    let displayName = "DISPLAY_NAME"
    let notificationOptions = CallCompositePushNotificationOptions(
        deviceToken: deviceToken,
        credential: credential,
        displayName: displayName,
        callKitOptions: callKitOptions) // CallKit options
    try await callComposite.registerPushNotification(notificationOptions: notificationOptions)

Handle push notifications

To receive push notifications for incoming calls, call handlePushNotification() on a CallComposite instance with a dictionary payload.

When you use handlePushNotification(), you get a CallKit notification to accept or decline calls.

    // App is in the background
    let pushNotificationInfo = CallCompositePushNotificationInfo(pushNotificationInfo: dictionaryPayload)
    let cxHandle = CXHandle(type: .generic, value: "\(pushNotificationInfo.callId)")
    let cxProvider = CallCompositeCallKitOption.getDefaultCXProviderConfiguration()
    let remoteInfo = CallCompositeCallKitRemoteInfo(displayName: pushNotificationInfo.fromDisplayName,
                                                    cxHandle: cxHandle)
    let callKitOptions = CallCompositeCallKitOption(cxProvideConfig: cxProvider,
                                                    isCallHoldSupported: true,
                                                    remoteInfo: remoteInfo)
    CallComposite.reportIncomingCall(callKitOptions: callKitOptions,
                                        callNotification: pushNotificationInfo) { result in
        if case .success() = result {
            DispatchQueue.global().async {
                // Handle push notification
                // You don't need to wait for a Communication Services token to handle the push because 
                // Communication Services commonly receives a callback function to get the token
            }
        }
    }

    // App is in the foreground
    let pushNotificationInfo = CallCompositePushNotificationInfo(pushNotificationInfo: dictionaryPayload)
    let displayName = "display name"
    let remoteOptions = RemoteOptions(for: pushNotificationInfo,
                                        credential: credential,
                                        displayName: displayName,
                                        callKitOptions: callKitOptions)
    try await callComposite.handlePushNotification(remoteOptions: remoteOptions)

Register for incoming call notifications

To receive incoming call notifications after handlePushNotification, subscribe to IncomingCallEvent and IncomingCallEndEvent.

    let onIncomingCall: (CallCompositeIncomingCallInfo) -> Void = { [] _ in
        // Incoming call
    }
    let onIncomingCallEnded: (CallCompositeIncomingCallEndedInfo) -> Void = { [] _ in
        // Incoming call ended
    }

    callComposite.events.onIncomingCall = onIncomingCall
    callComposite.events.onIncomingCallEnded = onIncomingCallEnded

Dial other participants

To start calls with other participants, create CallCompositeStartCallOptions with participants' raw IDs from CommunicationIdentity and launch.

    let startCallOptions = CallCompositeStartCallOptions(participants: <list of participant IDs>)
    let remoteOptions = RemoteOptions(for: startCallOptions,
                                        credential: credential,
                                        displayName: "DISPLAY_NAME",
                                        callKitOptions: callKitOptions)
    callComposite.launch(remoteOptions: remoteOptions,
                         localOptions: localOptions)

Next steps