Skip to content

Infobip RTC calls and UI

Jakub Dzubak edited this page Dec 12, 2024 · 18 revisions

Infobip RTC Calls and UI

Find out more about Voice/Video Calls and Infobip RTC features in the Infobip docs.

Intro

InfobipRtcUi is an easy-to-use library that allows you to connect to the Infobip RTC by building a library.

This guide assumes that you have already set up your account and your mobile app profile with Mobile Push Notifications and the Infobip RTC.

InfobipRtcUi takes care of everything: the registration of your device to receive and trigger calls and handling of the calls themselves. It also offers you a powerful user interface with every feature your customer may need:

  • the ability to capture video through both, front and back camera,
  • an option to mute and use the speaker,
  • the ability to capture and share a screen of a mobile device,
  • an option to minimize the call UI in a picture-on-picture mode, and more.

InfobipRtcUi also allows you to take control at any step of the flow, if you wish or need so. How you handle the calls is up to you. You can become a delegate for the FirebaseMessagingService or use your own custom user interface.

Requirements

  • Android Studio
  • Supported API Levels: 21 (Android 5.0 - Lollipop) - 34 (Android 14.0 - Upside Down Cake)
  • AndroidX
  • InfobipRtcUi library source and target compatibility is set to Java 8.
  • Infobip account

Permissions

InfobipRtcUi library declares the following dangerous permissions:

Runtime check and request for dangerous permissions is handled by library UI components.

Below, is the list of normal permissions declared in the library:

Migration guide

Following the major release of Infobip WebRTC SDK 2.0, content and setup exclusive of Infobip WebRTC SDK 1.x will be deprecated on 31/10/2023. If you were using InfobipRtcUi previously (version 9.X or older), please update to the latest version and read carefully the Quick Start Guide below. You will need a new setup and change function to enable calls. Things to consider:

  • Portal UI for handling WebRTC has been replaced with REST API calls.
  • The previous WebRTC application you used must be replaced with two new separate models: CPaaS X Application and WebRTC Push Configuration.
  • The WebRTC applicationId you used before to enable calls in mobile needs to be replaced with a new WebRTC Push configrationId.
  • If you want to use InfobipRtcUi and InAppChat together (meaning, you want to receive calls from Infobip Conversation's agents), you need to use the new withInAppChatCalls() builder function, instead of the previous enableInAppCalls().

You can find complete list of changes in migration guide.

Quick-start guide

  1. Include the InfobipRtcUi dependency

    implementation ('com.infobip:infobip-rtc-ui:12.3.0') {
        transitive = true
    }
  2. Set up the Mobile Messaging SDK.

    Note:

    Obtain a Firebase configuration file (google-services.json) as described in documentation and move your config file into the module (app-level) root directory of your app.

  3. Create your CPaaS X Application and WebRTC Push Configuration resource as described in a guide. You can create CPaaS X Application in our REST API. Then create WebRTC Push Configuration with the REST API with applicationId of CPaaS X Application from previous request and your Google Private Key JSON file.

  4. Provide WebRTC Push Configuration id obtained in the step 3 to InfobipRtcUi. There are 2 options how to do it:

    • Introduce new string variable in resources file values/strings.xml.
      <resources>
           <string name="infobip_webrtc_configuration_id">WEBRTC CONFIGURATION ID</string>
         ...
      </resources>
    • Provides it in runtime using builder function showed in next step.
  5. Build an InfobipRtcUi SDK:

    class App: android.app.Application() {
        override fun onCreate() {
            super.onCreate()
                
            InfobipRtcUi.Builder(this)
                 .withConfigurationId("WebRTC Configuration ID") //optional step, builder provided ID has precedent over ID provided in resources
                 .withInAppChatCalls() //optional step, enables InAppChat calls  
                 .build()
        }
    }
    expand to see Java code

    public class Application extends android.app.Application {
    
        @Override
        public void onCreate() {
            super.onCreate();
    
            new InfobipRtcUi.Builder(this)
                 .withConfigurationId("WebRTC Configuration ID") //optional step, builder provided ID has precedent over ID provided in resources
                 .withInAppChatCalls() //optional step, enables InAppChat calls  
                 .build();
        }
    }
  6. Register for incoming calls based on your use case:

    • If you use InfobipRtcUi together with InAppChat, there is prepared function you need use:

      InfobipRtcUi.getInstance(context).enableInAppChatCalls(
          successListener = {},
          errorListener = { throwable -> }
      )
      expand to see Java code

      InfobipRtcUi.getInstance(context).enableInAppChatCalls(
          () -> {},
          (throwable) -> {}
      );

      Note:

      There is builder function withInAppChatCalls(successListener, errorListener) which also registers you for incoming calls, where you don't need to use InfobipRtcUi instance.

    • If you plan to use InfobipRtcUi where you can define call identity and listen type on your own, there is prepared function you can use:

      InfobipRtcUi.getInstance(context).enableCalls(
          identity = "customIdentity",
          listenType = ListenType.PUSH,
          successListener = {},
          errorListener = { throwable -> }
      )
      expand to see Java code

      InfobipRtcUi.getInstance(context).enableCalls(
          "customIdentity",
          ListenType.PUSH,
          () -> {},
          (throwable) -> {}
      );

      Note:

      There is builder function withCalls(identity, listenType, successListener, errorListener) which also registers you for incoming calls, where you don't need to use InfobipRtcUi instance.

    • If you don't want to care about identity, you can left responsibility for picking unique identity on InfobipRtcUi. InfobipRtcUi will use per device, per installation unique pushRegistrationId as identity. There is also prepared function for such use case you can use:

      InfobipRtcUi.getInstance(context).enableCalls(
          successListener = {},
          errorListener = { throwable -> }
      )
      expand to see Java code

      InfobipRtcUi.getInstance(context).enableCalls(
          () -> {},
          (throwable) -> {}
      );

      Note:

      There is builder function withCalls(successListener, errorListener) which also registers you for incoming calls, where you don't need to use InfobipRtcUi instance.

Customizing the calls UI

The UI for interacting with calls is important. For this reason, we offer several options of customization:

  • Use our default UI that will work out of the box.
  • You can override colors, icons and a message shown on the incoming call screen using InfobipRtcUi theme customization.
  • You can customize buttons that will be available during a call.
  • You can use your own Activity to handle a call.

InfobipRtcUi Theme

To use InfobipRtcUi theme customization you have to define your own custom theme. There are two ways to do that:

  1. Define style in application's styles.xml with name InfobipRtcUi.

    <style name="InfobipRtcUi">
        <!-- color of text and elements in foreground -->
        <item name="rtc_ui_color_foreground">#ffffff</item>
        <!-- background color of UI elements and layouts -->
        <item name="rtc_ui_color_background">#242424</item>
        <!-- color of less prominent texts -->
        <item name="rtc_ui_color_text_secondary">#7AFFFFFF</item>
        <!-- background color of toolbar in video call -->
        <item name="rtc_ui_color_overlay_background">#80242424</item>
        <!-- background color of accept call action button, icon use rtc_ui_color_actions_icon -->
        <item name="rtc_ui_color_accept">#29B899</item>
        <!-- background color of hangup call action button, icon use rtc_ui_color_actions_icon -->
        <item name="rtc_ui_color_hangup">#C84714</item>
        <!-- background color of alerts during a call -->
        <item name="rtc_ui_color_alert_background">#99050708</item>
        <!-- color of text in alerts during a call -->
        <item name="rtc_ui_color_alert_text">#ffffff</item>
        <!-- muted microphone alert icon during a call -->
        <item name="rtc_ui_icon_alert_mic_off">@drawable/ic_mic_off</item>
        <!-- weak connection alert icon during a call -->
        <item name="rtc_ui_icon_alert_weak_connection">@drawable/ic_alert_triangle</item>
        <!-- call reconnecting alert icon during a call -->
        <item name="rtc_ui_icon_alert_reconnecting">@drawable/ic_alert_triangle</item>
        <!-- background color of call action buttons excluding hangup and accept call -->
        <item name="rtc_ui_color_actions_background">#15FFFFFF</item>
        <!-- background color of call action buttons in active state -->
        <item name="rtc_ui_color_actions_background_checked">#ffffff</item>
        <!-- color of call action icon -->
        <item name="rtc_ui_color_actions_icon">#ffffff</item>
        <!-- color of call action icon in active state -->
        <item name="rtc_ui_color_actions_icon_checked">#B2242424</item>
        <!-- background color of secondary call action buttons -->
        <item name="rtc_ui_color_actions_row_background">#242424</item>
        <!-- color of secondary call action icon -->
        <item name="rtc_ui_color_actions_row_icon">#ffffff</item>
        <!-- color of secondary call action label -->
        <item name="rtc_ui_color_actions_row_label">#ffffff</item>
        <!-- color of actions bottom sheet dragging "pill" -->
        <item name="rtc_ui_color_sheet_pill">#15FFFFFF</item>
        <!-- color of actions bottom sheet divider line -->
        <item name="rtc_ui_color_actions_divider">#15FFFFFF</item>
        <!-- background color of actions bottom sheet -->
        <item name="rtc_ui_color_sheet_background">#242424</item>
        <!-- drawable references for action icons and call images -->
        <item name="rtc_ui_icon_mic">@drawable/ic_mic</item>
        <item name="rtc_ui_icon_micOff">@drawable/ic_mic_off</item>
        <item name="rtc_ui_icon_screenShare">@drawable/ic_screen_share</item>
        <item name="rtc_ui_icon_screenShareOff">@drawable/ic_screen_share_off</item>
        <item name="rtc_ui_icon_video">@drawable/ic_video</item>
        <item name="rtc_ui_icon_videoOff">@drawable/ic_video_off</item>
        <item name="rtc_ui_icon_speaker">@drawable/ic_speaker</item>
        <item name="rtc_ui_icon_speakerOff">@drawable/ic_speaker_off</item>
        <item name="rtc_ui_icon_flipCamera">@drawable/ic_flip_camera</item>
        <item name="rtc_ui_icon_collapse">@drawable/ic_collapse</item>
        <item name="rtc_ui_icon_accept">@drawable/ic_calls_30</item>
        <item name="rtc_ui_icon_decline">@drawable/ic_clear_large</item>
        <item name="rtc_ui_icon_endCall">@drawable/ic_endcall</item>
        <item name="rtc_ui_icon_avatar">@drawable/ic_user_grayscale</item>
        <item name="rtc_ui_icon_caller">@drawable/ic_calls_30</item>
        <!-- incoming call screen customizations -->
        <item name="rtc_ui_incoming_call_headline">@string/incoming_call_headline</item>
        <item name="rtc_ui_incoming_call_headline_appearance">@style/TextAppearance.AppCompat.Title</item>
        <item name="rtc_ui_incoming_call_headline_text_color">#ffffff</item>
        <item name="rtc_ui_incoming_call_headline_background">@null</item>
        <item name="rtc_ui_incoming_call_message">@string/incoming_call_message</item>
        <item name="rtc_ui_incoming_call_message_appearance">@style/TextAppearance.AppCompat.Title</item>
        <item name="rtc_ui_incoming_call_message_text_color">#ffffff</item>
        <item name="rtc_ui_incoming_call_message_background">@null</item>
        <item name="rtc_ui_incoming_call_caller_name">@null</item>
        <item name="rtc_ui_incoming_call_caller_name_appearance">@style/TextAppearance.AppCompat.Body</item>
        <item name="rtc_ui_incoming_call_caller_icon_visible">true</item>
        <!-- in call screen customizations -->
        <item name="rtc_ui_in_call_caller_name">@string/in_call_caller_name</item>
    </style>
  2. In code, you can use InfobipRtcUi.setTheme(InfobipRtcUiTheme), which provides same customization as the xml approach.

    InfobipRtcUi.getInstance(context).setTheme(
        InfobipRtcUiTheme(
            incomingCallScreenStyle = IncomingCallScreenStyle(
                headlineText = "your custom headline",
                messageText = "your custom message"
            ),
            inCallScreenStyle = InCallScreenStyle(
                callerName = "your custom caller name"
            ),
            colors = Colors(
                //define your custom colors
            ),
            icons = Icons(
                //define your custom icons
            )
        )
    )
    expand to see Java code

    InfobipRtcUi.getInstance(context).setTheme(
        new InfobipRtcUiTheme(
            new IncomingCallScreenStyle(
                 //define your custom style
            ),
            new InCallScreenStyle(
                 //define your custom style
            ),
            new Colors(
                 //define your custom colors 
            ),
            new Icons(
                 //define your custom icons   
            )
        )
    );

Final value for every InfobipRtcUiTheme theme attribute is resolved from multiple source-by-source priority. The source with the highest priority defines a final attribute value. If source does not define an attribute value, there is fallback to the source with lower priority.

Sources by priority:

  1. InfobipRtcUiTheme theme provided in runtime using this function.
  2. InfobipRtcUi style provided in xml.
  3. Default InfobipRtcUi style defined by InfobipRtcUi library

Final value for every theme attribute is evaluated separately. It means you can define InfobipRtcUiTheme.incomingCallScreenStyle in runtime, colors in xml and skip icons. Library will use InfobipRtcUiTheme.incomingCallScreenStyle you defined in runtime, colors you defined in xml and default icons provided by library itself.

Call action buttons

To customize order (or presence) of call action buttons use InfobipRtcUi.setInCallButtons(buttons: List<InCallButton>). First 3 buttons are visible in main area (half-expanded state of bottom sheet) with HangUp button, rest is visible in full expanded state (secondary buttons). There are these types of buttons available:

sealed class InCallButton(
   ...
) {
   object HangUp: InCallButton() // always at the first place, non-editable
   data class Mute(override var onClick: () -> Unit = {}): InCallButton()
   data class Speaker(override var onClick: () -> Unit = {}): InCallButton()
   data class FlipCam(override var onClick: () -> Unit = {}): InCallButton() // only visible when local video is active
   data class ScreenShare(override var onClick: () -> Unit = {}): InCallButton()
   data class Video(override var onClick: () -> Unit = {}): InCallButton()
   data class Custom(
      @StringRes override val labelRes: Int,
      @DrawableRes override val iconRes: Int,
      @DrawableRes override var checkedIconRes: Int? = null,
      override var onClick: () -> Unit,
      val isChecked: (() -> Boolean)? = null,
      val isEnabled: (() -> Boolean)? = null
   ): InCallButton()
}

InfobipRtcUi.getInstance(context).setInCallButtons(
   listOf(
      InCallButton.Mute(onClick = {}),
      InCallButton.Video(onClick = {})
   )
)
expand to see Java code

import kotlin.Unit;

InCallButton mute = new InCallButton.Mute(() -> {
   return Unit.INSTANCE;
});
InCallButton video = new InCallButton.Mute(() -> {
   return Unit.INSTANCE;
});
List<InCallButton> buttons = new ArrayList<>();
buttons.add(mute);
buttons.add(video);
InfobipRtcUi.getInstance(this).setInCallButtons(buttons);

All buttons, except Custom, have a defined behaviour (isEnabled and isChecked values) and icons. You can only enrich the behaviour of the onClick event. Custom buttons are customizable via constructor-provided values. Labels are visible only for buttons in secondary area.

Own call UI

Use a completely new user interface of your own. You can define own Activity to handle a call.

InfobipRtcUi.Builder(context)
    .withCustomActivity(YourActivity.class)
    .build()
expand to see Java code

new InfobipRtcUi.Builder(context)
    .withCustomActivity(YourActivity.class)
    .build();

Note:

An incoming call push notification will be handled by the InfobipRtcUi SDK. To build your own UI, visit the InfobipRTC wiki page for all options.

Changing localization

Whole UI provided by InfobipRtcUi is by default localized for the English locale, but it can be changed by providing your own locale. The call's UI must be recreated to apply a new locale.

InfobipRtcUi.getInstance(context).setLanguage(Locale("es", "ES"))
expand to see Java code

InfobipRtcUi.getInstance(context).setLanguage(new Locale("es", "ES"))

Errors mapping

InfobipRtcUi defines default localised human-readable messages for certain errors, but you can replace default messages with your own messages. Implement RtcUiCallErrorMapper interface and provide the implementation into InfobipRtcUi library using InfobipRtcUi.setErrorMapper(errorMapper: RtcUiCallErrorMapper) function.

RtcUiCallErrorMapper interface contains single function you must implement fun getMessageForError(error: RtcUiError): String?. Function provides you RtcUiError instance with all error specific data. Based on the error you should return message to be show to the user. If returned message is null, empty or blank, it is not shown to the user. This way you can ignore particular errors.

You can find all possible errors defined by InfobipRtcUi library in RtcUiError.Companion class. There are also general WebRTC errors defined in Infobip WebRTC documentation.

import com.infobip.webrtc.sdk.api.model.ErrorCode
import com.infobip.webrtc.ui.model.RtcUiError

InfobipRtcUi.getInstance(context).setErrorMapper(
   object : RtcUiCallErrorMapper {
      override fun getMessageForError(error: RtcUiError): String? {
         return when(error.name){
            RtcUiError.MISSING_POST_NOTIFICATIONS_PERMISSION.name -> "Missing permission"
               ...
            ErrorCode.NORMAL_HANGUP.name -> null //do not show message when it is normal call hangup
            else -> error.description ?: error.name
         }
      }
   }
)
expand to see Java code

InfobipRtcUi.getInstance(context).setErrorMapper(error -> {
   String errorMsg = error.getDescription();
   if (error.getName().equals(RtcUiError.MISSING_POST_NOTIFICATIONS_PERMISSION.getName())) {
      errorMsg = "Missing permission";
   }
   if (error.getName().equals(ErrorCode.NORMAL_HANGUP.getName())) {
      errorMsg = null;
   }
   return errorMsg;
});

Firebase Messaging Service delegation

Let's assume that you use an FCM to process push-from-native backend and you don't want to migrate all code to use Infobip. There still can be a use case where you want to send notifications directly to Firebase. In that case, you have your own service that extends FirebaseMessagingService. Infobip SDK also extends the same service and if your service is registered in the manifest, Infobip's service won't be used for message handling (won't be able to receive calls).

We provide InfobipRtcUiFirebaseService to solve this issue. You have 2 options:

  1. Inherit from the service - it is easier, faster and more error-prone way.
  2. Use service's static functions - it provides you full control over Firebase's RemoteMessage handling, but it is your responsibility to integrate it in proper way to make calls working.

Inherit form InfobipRtcUiFirebaseService

Inherit from our InfobipRtcUiFirebaseService and put your Firebase implementation into delegates methods you must override. Message delegation to Mobile Messaging SDK and token handling is covered. Don't forget to register your service in Manifest.xml.

import com.google.firebase.messaging.RemoteMessage
import com.infobip.webrtc.ui.service.InfobipRtcUiFirebaseService

class YourFirebaseService : InfobipRtcUiFirebaseService() {
    override fun onMessageReceivedDelegate(message: RemoteMessage) {
        // process non-Infobip notifications here
        // TODO your code
    }
    override fun onNewTokenDelegate(token: String) {
        // process Firebase token here
        // TODO your code
    }
}
expand to see Java code

import com.google.firebase.messaging.RemoteMessage;
import com.infobip.webrtc.ui.service.InfobipRtcUiFirebaseService;

class YourFirebaseService extends InfobipRtcUiFirebaseService {
   @Override
   void onMessageReceivedDelegate(RemoteMessage message) {
      // process non-Infobip notifications here
      // TODO your code
   }
   @Override
   void onNewTokenDelegate(String token) {
      // process Firebase token here
      // TODO your code
   }
}

Declare service inside your AndroidManifest.xml.

<service
        android:name=".YourFirebaseService"
        android:exported="false">
   <intent-filter>
      <action android:name="com.google.firebase.MESSAGING_EVENT" />
   </intent-filter>
</service>

Use InfobipRtcUiFirebaseService's static functions

There are cases when you need full control over message handling and cannot inherit from our service. Both Mobile Messaging SDK and InfobipRtcUi SDK provides public functions you are supposed to use. It is your responsibility to call them in correct places to achieve expected behaviour. Without proper integration:

  • Mobile Messaging SDK will not process push notifications
  • InfobipRtcUi SDK will not receive incoming calls
import com.google.firebase.messaging.FirebaseMessagingService
import com.google.firebase.messaging.RemoteMessage

class YourFirebaseService : FirebaseMessagingService() {
   override fun onMessageReceived(remoteMessage: RemoteMessage) {
      super.onMessageReceived(remoteMessage)
      if (MobileMessagingFirebaseService.onMessageReceived(this, remoteMessage)) {
         return
      }
      if (InfobipRtcUiFirebaseService.onMessageReceived(this, remoteMessage)) {
         return
      }
      // process non-Infobip notifications here
      // TODO your code
   }

   override fun onNewToken(token: String) {
      super.onNewToken(token)
      MobileMessagingFirebaseService.onNewToken(this, token)
      InfobipRtcUiFirebaseService.onNewToken(this, token)
      // process Firebase token here
      // TODO your code
   }
}
expand to see Java code

import com.google.firebase.messaging.FirebaseMessagingService;
import com.google.firebase.messaging.RemoteMessage;

class YourFirebaseService extends FirebaseMessagingService {
   @Override
   public void onMessageReceived(@NonNull RemoteMessage remoteMessage) {
      super.onMessageReceived(remoteMessage);
      if (MobileMessagingFirebaseService.onMessageReceived(this, remoteMessage)) {
         //remoteMessage belongs to Mobile Messaging SDK and was handled
         return;
      }
      if (InfobipRtcUiFirebaseService.onMessageReceived(this, remoteMessage)) { 
         //remoteMessage belongs to InfobipRtcUi SDK and was handled
         return;
      }
      // process non-Infobip notifications here
      // TODO your code
   }

   @Override
   public void onNewToken(@NonNull String token) {
      super.onNewToken(s);
      MobileMessagingFirebaseService.onNewToken(this, token);
      InfobipRtcUiFirebaseService.onNewToken(this, token);
      // process Firebase token here
      // TODO your code
   }
}

Declare service inside your AndroidManifest.xml.

<service
        android:name=".YourFirebaseService"
        android:exported="false">
   <intent-filter>
      <action android:name="com.google.firebase.MESSAGING_EVENT" />
   </intent-filter>
</service>
Clone this wiki locally