Signed Call™ Android SDK for P2P Calls
Learn how to integrate Signed Call™ Android SDK in your app to avail the Signed Call feature for Person-to-Person (P2P) calls.
Overview
CleverTap enables In-App calling through its Signed Call™ Android SDK, allowing users to make and receive Person-to-Person (P2P) calls within any Android application. As long as the device has an internet connection and the Signed Call SDK is integrated, users can connect with each other securely and seamlessly without needing to switch to the native dialer. To learn more about the Signed Call feature, refer to Signed Call.
This document guides you through integrating the Signed Call Android SDK and managing P2P call flows within your app.
Prerequisites
The basic requirements for integrating the Signed Call Android SDK are:
- Minimum SDK Level: 21
- Java version 11 and above
- Application permissions for the following:
- Microphone (Required)
- Notification (Required for Android 13 and above)
- Read Phone State (Optional)
Emulator Support
Emulator support is available for voice calls, but voice transmission will not work.
Integrate Signed Call Android SDK
The SDK integration process is divided into the following six key steps:
- Install Signed Call Android SDK.
- Add Signed Call SDK Dependencies.
- Initialize Signed Call Android SDK.
- Manage Permissions.
- Receive P2P Calls.
- Initiate P2P Calls.
Install Signed Call Android SDK
Download the latest version of the Signed Call Android SDK from mavenCentral
as follows:
- Include
mavenCentral
in your project-levelbuild.gradle
file as follows:
allprojects {
repositories {
mavenCentral()
}
}
- You must use the specified SDK versions for the corresponding SDK features. For P2P calls, include SDK v0.0.9.0 with the following code in the
build.gradle
file of your application module:implementation "com.clevertap.android:clevertap-signedcall-sdk:0.0.9.0"
Compatibility Matrix
The Signed Call Android SDK has different compatibility requirements based on the CleverTap Android SDK version being used.
- Signed Call SDK v0.0.9.x: Supports breaking changes introduced in CleverTap SDK v7.3.1 and above.
- Signed Call SDK v0.0.8.x: Offers backward compatibility for CleverTap SDK versions ranging from v5.2.1 up to v7.1.2, including v7.0.3.
Signed Call Android SDK Version Compatible CleverTap Android SDK Version 0.0.9.x v7.3.1 and above 0.0.8.x v7.1.2 or any version ≤ v7.0.3 and ≥ v5.2.0
Add Signed Call SDK Dependencies
To enable voice calling with the Signed Call Android SDK, you must add the following dependencies to your application modulesbuild.gradle
file:
- CleverTap Android SDK
- Firebase Cloud Messaging (FCM)
- Socket-IO Client
- Glide
- Work Manager
- Constraint Layout
CleverTap Android SDK
The Signed Call Android SDK uses CleverTap Android SDK for analytics. The Signed Call Android SDK requires an active CleverTapAPI
instance as a parameter during the SDK initialization.
To integrate the CleverTap Android SDK, refer to CleverTap Android SDK Integration.
Minimum Supported Version
To learn about the minimum supported version, refer to the Compatibility Matrix as mentioned in Install Signed Call Android SDK.
FCM
The Signed Call Android SDK uses the FCM dependency to get the FCM token of the device required for the successful SDK initialization. This FCM token is then used by Signed Call to enable calls via FCM Based Call Routing Channel.
To add the FCM dependency to your application, perform the following steps:
- Add Firebase to your project if you have not already added it. For more information, refer to the Firebase Integration Guide.
- Add the following code to the dependency element of the application module:
implementation 'com.google.firebase:firebase-messaging:21.0.0'
FCM Version
For Signed Call Android SDK, the minimum supported version of FCM is v21.0.0.
Socket-IO Client
The Signed Call Android SDK uses a Socket-IO client library to enable the socket-based signaling channel for voice calls.
To add the Socket-IO client dependency to your application, add the following line to the dependency element of the application module:
implementation('io.socket:socket.io-client:2.1.0') {
exclude group: 'org.json', module: 'json'
}
Glide
The Signed Call Android SDK uses a Glide library for loading the image assets on the call screen.
To add the Glide dependency to your application, add the following line to the application module's dependency element:
implementation 'com.github.bumptech.glide:glide:4.12.0'
Work Manager
The Signed Call Android SDK uses a Work Manager dependency to process the incoming call push for the receiver.
To add the Work Manager dependency to your application, add the following line to the application module's dependency element:
implementation 'androidx.work:work-runtime:2.7.1'
ConstraintLayout
The Signed Call Android SDK uses a ConstraintLayout dependency to build a responsive UI for the call screens.
To add the ConstraintLayout dependency to your application, add the following line to the dependency element of the application module:
implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
Upgrade Java Compatibility (Optional)
The Signed Call Android SDK source and target compatibility must be set to Java 8.
To upgrade your application to target Java 8, use the following snippet:
android {
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
After updating your build.gradle
file, sync your project by clicking Sync Project.
Initialize Signed Call Android SDK
Initialize the Signed Call Android SDK using the SignedCallAPI
class instance:
SignedCallInitResponse signedCallInitListener = new SignedCallInitResponse() {
@Override
public void onSuccess() {
//App is notified on the main thread when the Signed Call SDK is initialized
}
@Override
public void onFailure(@NonNull InitException initException) {
//App is notified on the main thread when the initialization is failed
Log.d("SignedCall: ", "error code: " + initException.getErrorCode()
+ "\n error message: " + initException.getMessage()
+ "\n error explanation: " + initException.getExplanation());
if (initException.getErrorCode() == InitException.SdkNotInitializedException.getErrorCode()) {
//Handle this error here
}
}
};
//Create a Builder instance of SignedCallInitConfiguration and pass it inside the init() method
SignedCallInitConfiguration initConfiguration = new SignedCallInitConfiguration.Builder(initOptions, allowPersistSocketConnection)
.build();
SignedCallAPI.getInstance().init(getApplicationContext(), initConfiguration, cleverTapAPI, signedCallInitListener);
val signedCallInitListener: SignedCallInitResponse = object : SignedCallInitResponse {
override fun onSuccess() {
//App is notified on the main thread when the Signed Call SDK is initialized
}
override fun onFailure(initException: InitException) {
//App is notified on the main thread when the initialization is failed
Log.d("SignedCall: ", "error code: " + initException.errorCode
+ "\n error message: " + initException.message
+ "\n error explanation: " + initException.explanation)
if (initException.errorCode == InitException.SdkNotInitializedException.errorCode)
{
//Handle this error here
}
}
}
//Create a Builder instance of SignedCallInitConfiguration and pass it inside the init() method
val initConfiguration = SignedCallInitConfiguration.Builder(initOptions, allowPersistSocketConnection)
.build()
SignedCallAPI.getInstance().init(applicationContext, initConfiguration, cleverTapAPI, signedCallInitListener)
The following are the parameters passed inside the constructor of SignedCallInitConfiguration.Builder
:
initOptions
The initOptions
is a JSON object that is passed inside the constructor of SignedCallInitConfiguration.Builder
with the following properties:
Property | Description | Type | Notes |
---|---|---|---|
|
| String | Required |
|
| String | Required |
| Unique identity of the user. | String | Required |
|
| String | Optional |
|
| String | Optional |
ringtone | The URL of the ringtone played on the caller's end during the outgoing call. Note: The default ringtone plays without this parameter. | String | Optional |
CUID Validation Rules
The following are the validation rules for
cuid
:
- Must range between 5 and 50 characters.
- Must start with either alphabet or number.
- Must be alphanumeric, should contain at least 1 alphabet.
- The name is case-sensitive, and only '_' is allowed as a special character.
- The
cuid
parameter cannot be of type number-number, that is, a number followed by a special character, which is again followed by another number. For example, org_25 is allowed, but 91_8899555 is not permitted.- Must be unique for every user.
- Must be unique for every device to allow multiple logins for the user from different devices. In such cases, the user will have multiple
cuid
s. If the same CUID is associated with multiple devices, the device with the most recent SignedCall initialization is prioritized for receiving the call.
The syntax for initOptions
is as follows:
JSONObject initOptions = new JSONObject();
try {
initOptions.put("accountId", <string>);
initOptions.put("apiKey", <string>);
initOptions.put("cuid", <string>);
initOptions.put("appId", <string / optional>);
initOptions.put("name", <string / optional>);
initOptions.put("ringtone", <string / optional>);
} catch (JSONException e) {
e.printStackTrace();
}
val initOptions = JSONObject();
try {
initOptions.put("accountId", <string>);
initOptions.put("apiKey", <string>);
initOptions.put("cuid", <string>);
initOptions.put("appId", <string / optional>);
initOptions.put("name", <string / optional>);
initOptions.put("ringtone", <string / optional>);
} catch (e: JSONException) {
e.printStackTrace();
}
allowPersistSocketConnection
The socket connection plays a crucial role in processing the call request to initiate a call and receive calls on the socket channel. Android OS imposes several battery restrictions that lead to issues in maintaining a persistent socket connection. To overcome this issue, Signed Call Android SDK expects the allowPersistSocketConnection
flag, a boolean parameter, to be passed inside the SignedCallInitConfiguration.Builder
constructor.
The following is the Signed Call Android SDK behavior:
Value | Socket Connection | Signed Call Android SDK Behavior |
---|---|---|
true | Persistent | Uses a background service to keep the socket connection persistent in the foreground and background states of the application. |
false | Non-persistent | Does not use the background service for the socket connection; hence, it may be inconsistent under battery-restricted scenarios. |
Recommended Settings for Android 11 and Above
For Android 11 and above, if the socket connection is persistent, the application might show a system-generated notification to the user that the application is draining the device battery. We recommend setting the value as
false
and keeping the socket connection non-persistent for transactional businesses.
Manage Permissions
Signed Call Android SDK uses and manages the following permissions:
- Notification Permission
- Microphone Permission
- Read Phone State Permission
- Full-Screen Intent Permission
- Data Sync Permission
- Bluetooth Connect Permission
Notification Permission
All applications having Android 13 and above must request Runtime Notification Permission from the user before sending the notifications.
Signed Call Android SDK utilizes both local and remote notifications during VoIP calls. Therefore, notification permission is required to initialize the Signed Call Android SDK by default. If it is not provided, the SDK returns an InitException.NotificationPermissionRequiredException
exception through the onFailure(initException)
method. For more information, refer to Push Primer for Notification Permission.
Notification Permission is Now Optional
Signed Call Android versions starting from 0.0.5.x, SDK includes an API that allows developers to choose whether notification permissions are optional or required for SDK initialization. For detailed instructions on making notification permission optional, refer to Configure Notification Permission as Optional.
Microphone Permission
This is a required permission. The Signed Call Android SDK requires microphone permission to exchange voices during the call. At the receiver's end, the Signed Call Android SDK prompts for microphone permission and handles it accordingly when the receiver answers the call. CleverTap recommends you add the required handling to request the microphone permission before initiating a call.
The Signed Call Android SDK also uses the FOREGROUND_SERVICE_MICROPHONE permission to access the microphone for voice exchange even in the background and killed state. This prevents the Android OS from revoking microphone access. For more information on how to declare and manage this permission in the Google Play Console, refer to FOREGROUND_SERVICE_MICROPHONE Permission.
Microphone Permission Prompt
Starting with Signed Call Android SDK v0.0.3, the microphone permission prompt shown during a VoIP call on the receiving device now adheres to the permissible threshold set by the Android platform.
Previously, if the microphone permission had been denied even once, the SDK would block all incoming calls on the receiving device.
Read Phone State Permission
This is an optional permission. The Signed Call Android SDK uses this permission to enable busy handling of Public Switched Telephone Network (PSTN) calls. This permission determines if the receiver is available or busy on a PSTN call. CleverTap recommends you add the required handling to request the Read Phone State permission before initiating a call.
The Signed Call Android SDK exposes a promptReceiverReadPhoneStatePermission(boolean)
method via the SignedCallInitConfiguration.Builder
class. Pass the boolean flag as true
to allow the Signed Call Android SDK to prompt for the read phone state permission at the receiver end when the receiver answers the call.
SignedCallInitConfiguration initConfiguration = new SignedCallInitConfiguration.Builder(initOptions, allowPersistSocketConnection)
.promptReceiverReadPhoneStatePermission(<pass boolean here>)
.build();
SignedCallAPI.getInstance().init(getApplicationContext(), initConfiguration, cleverTapAPI, signedCallInitListener)
val initConfiguration = SignedCallInitConfiguration.Builder(initOptions, allowPersistSocketConnection)
.promptReceiverReadPhoneStatePermission(<pass boolean here>)
.build()
SignedCallAPI.getInstance().init(applicationContext, initConfiguration, cleverTapAPI, signedCallInitListener)
Note
When the Read Phone State permission is enabled, the SDK checks if the recipient is on another call. If so, it automatically declines the new call, marking the recipient as busy. When this permission is disabled, the incoming call enters a waiting state. The recipient still receives a call notification and can decide whether to end their ongoing call to accept the new one.
To optimize call handling and ensure faster conversations, CleverTap recommends restricting phone state permissions to users only, avoiding usage on the partner side (for example, delivery agents).
Full-Screen Intent Permission
The Signed Call Android SDK uses the Full-Screen Intent permission to display full-screen notifications for incoming VoIP calls when the app is not running in the foreground. This ensures a user experience similar to other VoIP-capable apps. For more information on how to declare and manage this permission in the Google Play Console, refer to Full-Screen Intent Permission in Android 14.
DataSync Permission
The Signed Call Android SDK uses the FOREGROUND_SERVICE_DATA_SYNC permission to handle incoming VoIP calls via the FCM channel in FOREGROUND mode. For more information on how to declare and manage this permission in the Google Play Console, refer to Usage declaration documentation.
Bluetooth Connect Permission
This is an optional permission, but it is recommended for a better user experience. The Signed Call Android SDK has built-in Bluetooth management support. However, enabling this feature on Android 12 and above requires Bluetooth Connect permission.
The Signed Call Android SDK uses this permission to enable communication with the paired Bluetooth device for audio management on Android 12 and above. To optimize the call experience, CleverTap recommends you request Bluetooth Connect permission.
Receive a Person-to-Person (P2P) Call
Signed Call uses the following routing channels to receive a P2P call:
- Socket Channel: A primary routing channel. Signed Call Android SDK requires a successful initialization to receive a call on the socket channel.
- FCM Channel: A secondary or fallback routing channel used when the receiver is not connected to the primary routing channel (Socket).
Enable FCM Channel
To enable the FCM channel, perform the following steps:
-
Add your FCM Server Key to the Signed Call section of the CleverTap dashboard. Ignore it if you have already added it.
-
Add the following code to your
Application
class:
CleverTapAPI.setSignedCallNotificationHandler(new SignedCallNotificationHandler());
CleverTapAPI.setSignedCallNotificationHandler(SignedCallNotificationHandler())
- Add the following code inside your
FirebaseMessagingService
:
public class MyFcmMessageListenerService extends FirebaseMessagingService {
@Override
public void onMessageReceived(RemoteMessage message){
new CTFcmMessageHandler().createNotification(getApplicationContext(), message);
}
}
class MyFcmMessageListenerService : FirebaseMessagingService() {
override fun onMessageReceived(message: RemoteMessage) {
super.onMessageReceived(message)
CTFcmMessageHandler().createNotification(applicationContext, message)
}
}
Avoid Duplicate VoIP Notifications
If you use the CleverTap Listener Service to handle push notifications, you can skip step 3. However, if both the CleverTap Listener Service and the Signed Call Android SDK are used simultaneously, the SDK may receive duplicate push notifications for the same VoIP call. In such cases, the SDK rejects the duplicate push, which can result in the call initiator seeing the receiver as “busy on another call” — even though the receiver can still answer the call associated with the original VoIP push.
Configure FCM Processing Mode
The Signed Call Android SDK connects to a network when receiving a call through the Firebase Cloud Messaging (FCM) channel. Starting from SDK version 0.0.7.3, the SDK supports the following two modes for processing FCM calls: FOREGROUND
and BACKGROUND
. These modes determine how the SDK handles incoming calls depending on whether the app is actively running or is running in the background.
The following is the Signed Call Android SDK behavior based on the mode value:
Value | Behavior Description |
---|---|
FCMProcessingMode.FOREGROUND | The SDK uses a foreground service to process FCM calls, ensuring higher priority and visibility for ongoing tasks. NOTE: The foreground job completes quickly, so the system usually does not render the foreground notification. |
FCMProcessingMode.BACKGROUND |
|
The Signed Call Android SDK exposes a setFCMProcessingMode(mode, notificationConfig)
method via the SignedCallInitConfiguration.Builder
class. To change the default FCMProcessingMode.BACKGROUND
to FCMProcessingMode.FOREGROUND
, use the following code when initializing the Signed Call SDK:
FCMProcessingNotification fcmProcessingNotification = null;
try {
fcmProcessingNotification = new FCMProcessingNotification.Builder(notiTitle, notiSubtitle)
.setLargeIcon(fcmNotificationLargeIcon)
.setCancelCtaLabel(cancelCta)
.build();
} catch (Exception e) {
e.printStackTrace();
}
SignedCallInitConfiguration initConfiguration = new SignedCallInitConfiguration.Builder(initOptions, allowPersistSocketConnection)
.setFCMProcessingMode(FCMProcessingMode.FOREGROUND, fcmProcessingNotification)
.build();
SignedCallAPI.getInstance().init(getApplicationContext(), initConfiguration, cleverTapAPI, signedCallInitListener);
var fcmProcessingNotification: FCMProcessingNotification? = null
try {
fcmProcessingNotification = FCMProcessingNotification.Builder(notiTitle, notiSubtitle)
.setLargeIcon(fcmNotificationLargeIcon)
.setCancelCtaLabel(cancelCta)
.build()
} catch (e: Exception) {
e.printStackTrace()
}
val initConfiguration = SignedCallInitConfiguration.Builder(initOptions, allowPersistSocketConnection)
.setFCMProcessingMode(FCMProcessingMode.FOREGROUND, fcmProcessingNotification)
.build()
SignedCallAPI.getInstance().init(applicationContext, initConfiguration, cleverTapAPI, signedCallInitListener)
The following table provides information about the parameters used in the FCMProcessingNotification
instance:
Method Name | Description | Type | Required/Optional |
---|---|---|---|
title | Denotes the title text. The maximum character limit is 65 characters. | String | Required |
subtitle | Denotes the subtitle text. The maximum character limit is 240 characters. | String | Required |
setLargeIcon | Denotes the ID of an image or graphics used for the icon. | int | Optional |
setCancelCtaLabel | Serves as an alternative for manual dismissal of the notification, ensuring users have an option if it does not disappear automatically | String | Optional |
Initiate a Person-to-Person (P2P) Call
Use the following code to initiate a Signed Call:
OutgoingCallResponse outgoingCallResponseListener = new OutgoingCallResponse() {
@Override
public void onSuccess() {
//App is notified on the main thread when the call-request is accepted and being processed by the signalling channel
}
@Override
public void onFailure(CallException callException) {
//App is notified on the main thread when the call is failed
Log.d("SignedCall: ", "error code: " + callException.getErrorCode()
+ "\n error message: " + callException.getMessage()
+ "\n error explanation: " + callException.getExplanation());
if (callException.getErrorCode() == CallException.BadNetworkException.getErrorCode()) {
//Handle this error here
}
}
};
SignedCallAPI.getInstance().call(getApplicationContext(), receiverCuid, contextOfCall, callOptions, outgoingCallResponseListener);
val outgoingCallResponseListener: OutgoingCallResponse = object : OutgoingCallResponse {
override fun onSuccess() {
//App is notified on the main thread when the call-request is accepted and being processed by the signalling channel
}
override fun onFailure(callException: CallException) {
//App is notified on the main thread when the call is failed
Log.d("SignedCall: ", "error code: ${callException.errorCode}"
+ "\n error message: ${callException.message}"
+ "\n error explanation: ${callException.explanation}")
}
}
SignedCallAPI.getInstance().call(applicationContext, receiverCuid, contextOfCall, callOptions, outgoingCallResponseListener)
The parameters to make a Signed Call are as follows:
Parameter | Description | Type | Notes |
---|---|---|---|
receiverCuid | It is the receiver's cuid .Note: The Signed Call Android SDK returns CallException.CalleeInfoRequiredException error if this parameter is not passed. | String | Required |
contextOfCall | It specifies the context of the call. For example, the Delivery Partner is calling, the Driver is calling, the Agent is calling, and so on. Validation rule:
| String | Required |
callOptions | It is a JSON object with the following properties:
| JSON Object | Optional |
Call with Request ID
The Signed Call Android SDK provides an additional call method that allows you to pass a custom requestId
. To use this feature, you must opt in during SDK initialization. Refer to the Configure Request ID section before implementing the method below:
OutgoingCallResponse outgoingCallResponseListener = new OutgoingCallResponse() {
@Override
public void onSuccess() {
//App is notified on the main thread when the call-request is accepted and being processed by the signalling channel
}
@Override
public void onFailure(CallException callException) {
//App is notified on the main thread when the call is failed
Log.d("SignedCall: ", "error code: " + callException.getErrorCode()
+ "\n error message: " + callException.getMessage()
+ "\n error explanation: " + callException.getExplanation());
if (callException.getErrorCode() == CallException.BadNetworkException.getErrorCode()) {
//Handle this error here
}
}
};
SignedCallAPI.getInstance().call(getApplicationContext(), receiverCuid, contextOfCall, callOptions, requestId, outgoingCallResponseListener);
val outgoingCallResponseListener: OutgoingCallResponse = object : OutgoingCallResponse {
override fun onSuccess() {
//App is notified on the main thread when the call-request is accepted and being processed by the signalling channel
}
override fun onFailure(callException: CallException) {
//App is notified on the main thread when the call is failed
Log.d("SignedCall: ", "error code: ${callException.errorCode}"
+ "\n error message: ${callException.message}"
+ "\n error explanation: ${callException.explanation}")
}
}
SignedCallAPI.getInstance().call(applicationContext, receiverCuid, contextOfCall, callOptions, requestId, outgoingCallResponseListener)
Note
If you opt in Request ID feature during SDK initialization but do not provide a
requestId
during call-attempt, the SDK throws theCallException.RequestIdNotPassed
in theonFailure(callException)
callback.
Configure Request ID
Every call attempt from the Signed Call Android SDK is associated with a unique identifier known as the requestId
. Starting from v0.0.8.1 , you can pass your own identifier. You can pass an order ID, a transaction-specific identifier, or any other relevant ID.
The Signed Call Android SDK exposes overrideRequestId(boolean)
method via SignedCallInitConfiguration.Builder
class. Set the boolean flag as true
to opt in for this feature. The requestId
can be any hash key or UUID, allowing you to encrypt sensitive identifiers before passing them if needed.
SignedCallInitConfiguration initConfiguration = new SignedCallInitConfiguration.Builder(initOptions, allowPersistSocketConnection)
.overrideRequestId(<pass boolean here>)
.build();
SignedCallAPI.getInstance().init(getApplicationContext(), initConfiguration, cleverTapAPI, signedCallInitListener)
val initConfiguration = SignedCallInitConfiguration.Builder(initOptions, allowPersistSocketConnection)
.overrideRequestId(<pass boolean here>)
.build()
SignedCallAPI.getInstance().init(applicationContext, initConfiguration, cleverTapAPI, signedCallInitListener)
Request ID Validation Rules
The following are the validation rules for
requestId
:
- Length must be between 8 to 64 characters.
- Must start with a letter or digit.
- Must not start or end with a hyphen (-).
Push Primer for Notification Permission
The Push Primer is a local In-App notification that educates users about the context of the notifications before requesting the notification permission.
To initialize the Signed Call Android SDK for Android 13 and above, enable the Push Primer using either of the following:
Enable Push Primer via CleverTap Android SDK
To integrate notification permission management, refer to Android Push Primer.
Enable Push Primer via Signed Call Android SDK
The Signed Call Android SDK enables you to display Push Primer using the promptPushPrimer(jsonObjectConfig)
method through the SignedCallInitConfiguration.Builder
class. It ensures the initialization happens after the notification permission is granted using Push Primer.
To configure the Push Primer, perform the following steps:
- Create a Push Primer using the Half-Interstitial or Alert template in an In-App campaign as follows:
//Creates push primer config using Half-Interstitial template
JSONObject jsonObjectConfig = CTLocalInApp.builder()
.setInAppType(CTLocalInApp.InAppType.HALF_INTERSTITIAL)
.setTitleText("Get Notified")
.setMessageText("Please enable notifications on your device to use Push Notifications.")
.followDeviceOrientation(true)
.setPositiveBtnText("Allow")
.setNegativeBtnText("Cancel")
.setBackgroundColor(Constants.WHITE)
.setBtnBorderColor(Constants.BLUE)
.setTitleTextColor(Constants.BLUE)
.setMessageTextColor(Constants.BLACK)
.setBtnTextColor(Constants.WHITE)
.setImageUrl("https://icons.iconarchive.com/icons/treetog/junior/64/camera-icon.png")
.setBtnBackgroundColor(Constants.BLUE)
.build();
//Creates push primer config using Alert template
JSONObject jsonObjectConfig = CTLocalInApp.builder()
.setInAppType(CTLocalInApp.InAppType.ALERT)
.setTitleText("Get Notified")
.setMessageText("Enable Notification permission")
.followDeviceOrientation(true)
.setPositiveBtnText("Allow")
.setNegativeBtnText("Cancel")
.build();
//Creates push primer config using Half-Interstitial template
val jsonObjectConfig = CTLocalInApp.builder()
.setInAppType(InAppType.HALF_INTERSTITIAL)
.setTitleText("Get Notified")
.setMessageText("Please enable notifications on your device to use Push Notifications.")
.followDeviceOrientation(true)
.setPositiveBtnText("Allow")
.setNegativeBtnText("Cancel")
.setBackgroundColor(Constants.WHITE)
.setBtnBorderColor(Constants.BLUE)
.setTitleTextColor(Constants.BLUE)
.setMessageTextColor(Constants.BLACK)
.setBtnTextColor(Constants.WHITE)
.setImageUrl("https://icons.iconarchive.com/icons/treetog/junior/64/camera-icon.png")
.setBtnBackgroundColor(Constants.BLUE)
.build()
//Creates push primer config using Alert template
val jsonObjectConfig = CTLocalInApp.builder()
.setInAppType(InAppType.ALERT)
.setTitleText("Get Notified")
.setMessageText("Enable Notification permission")
.followDeviceOrientation(true)
.setPositiveBtnText("Allow")
.setNegativeBtnText("Cancel")
.build()
- Pass the Push Primer configuration inside the
promptPushPrimer(jsonObjectConfig)
method of theSignedCallInitConfiguration.Builder
class.
SignedCallInitConfiguration initConfiguration = new SignedCallInitConfiguration.Builder(initOptions, allowPersistSocketConnection)
.promptPushPrimer(jsonObjectConfig)
.build();
SignedCallAPI.getInstance().init(getApplicationContext(), initConfiguration, cleverTapAPI, signedCallInitListener);
val initConfiguration = SignedCallInitConfiguration.Builder(initOptions, allowPersistSocketConnection)
.promptPushPrimer(jsonObjectConfig)
.build()
SignedCallAPI.getInstance().init(applicationContext, initConfiguration, cleverTapAPI, signedCallInitListener)
Note
- The above configuration enables the Push Primer only if the device and application both target Android 13 (API level 33) or above.
- To obtain the result of the push notification permission request, the Signed Call SDK registers a listener. After registration, this listener continues monitoring the permission result, even if the Push Primer prompt is displayed from the CleverTap Android SDK.
- If the notification permission is denied, the Signed Call Android SDK returns
InitException.NotificationPermissionRequiredException
within theonFailure(initException)
method.- The initialization of the Signed Call Android SDK with the Push Primer configuration mentioned above must be invoked within the
onResume
lifecycle method of the Activity.
Configure Notification Permission as Optional
Starting with Android 13, the Signed Call Android SDK requires notification permission to enable call notifications, allowing users to interact with and return to calls.
The SDK requires this permission for initialization by default. If it is not provided, the SDK returns an exception -InitException.NotificationPermissionRequiredException
through the onFailure(initException)
method.
To make notification permission optional during SDK initialization, use the setNotificationPermissionRequired(boolean)
method available in the SignedCallInitConfiguration.Builder
class. Pass the boolean flag to false as shown below:
SignedCallInitConfiguration initConfiguration = new SignedCallInitConfiguration.Builder(initOptions, allowPersistSocketConnection)
.setNotificationPermissionRequired(<pass boolean here>)
.build();
SignedCallAPI.getInstance().init(getApplicationContext(), initConfiguration, cleverTapAPI, signedCallInitListener)
val initConfiguration = SignedCallInitConfiguration.Builder(initOptions, allowPersistSocketConnection)
.setNotificationPermissionRequired(<pass boolean here>)
.build()
SignedCallAPI.getInstance().init(applicationContext, initConfiguration, cleverTapAPI, signedCallInitListener)
Expected SDK Behavior without Notification Permission
- Avoid making notification permissions as optional. Without this permission, the SDK can only display the call screen on Android 13 and above when the app is in the foreground. Otherwise, incoming calls will be automatically declined, triggering the
VoIPCallStatus.CALL_DECLINED_DUE_TO_NOTIFICATIONS_DISABLED
event in thecallStatus(callStatusDetails)
callback method.- If you want to set the notification permissions as non-mandatory, implementing the
callStatus(callStatusDetails)
callback method is essential.
Retrieve Initialization Status
The Signed Call Android SDK provides the isInitialized(context)
method through the SignedCallAPI
instance to check the initialization status of the SDK. This helps prevent duplicate initialization.
The following is the code to retrieve the initialization status:
boolean status = SignedCallAPI.getInstance().isInitialized(context);
Log.d(LOG_TAG, "Initialization status: " + status);
val status = SignedCallAPI.getInstance().isInitialized(context)
Log.d(LOG_TAG, "Initialization status: $status")
Log Out Signed Call Android SDK
When the Signed Call Android SDK initializes, it maintains the init configuration in a local session. Use the logout(context)
method to reset the active session and disable the Signed Call functionality (call initiation and reception).
SignedCallAPI.getInstance().logout(getApplicationContext());
SignedCallAPI.getInstance().logout(applicationContext)
Control Call Screen Display Timing
By default, the Signed Call Android SDK displays the outgoing call screen when you initiate a call request. At the same time, it starts sending a call initiation signal to the server to set up the connection.
However, you can adjust this timing to show the call screen only after the server successfully receives and confirms the call request. The SDK offers a callScreenOnSignalling(boolean)
method, part of the SignedCallInitConfiguration.Builder
class. This method lets you control when the outgoing call screen appears relative to the signaling process. If you set the boolean flag to true
in this method, the SDK delays the call screen until the server completes the signaling step.
SignedCallInitConfiguration initConfiguration = new SignedCallInitConfiguration.Builder(initOptions, allowPersistSocketConnection)
.callScreenOnSignalling(<pass boolean here>) // Set to true to display call screen only after signaling is complete
.build();
SignedCallAPI.getInstance().init(getApplicationContext(), initConfiguration, cleverTapAPI, signedCallInitListener)
val initConfiguration = SignedCallInitConfiguration.Builder(initOptions, allowPersistSocketConnection)
.callScreenOnSignalling(<pass boolean here>) // Set to true to display call screen only after signaling is complete
.build()
SignedCallAPI.getInstance().init(applicationContext, initConfiguration, cleverTapAPI, signedCallInitListener)
Note
This is an optional feature. It can be used when you want to show the user a custom loading screen while the signaling process is completed.
Call Quality Control
The Signed Call Android SDK assesses network conditions before initiating a call. Based on this assessment, the app determines whether to proceed with the call or reject it using the onNetworkQualityResponse(int score)
callback.
If the callback is not implemented, the SDK compares the network latency to a predefined threshold. If the latency exceeds this threshold, the SDK rejects the call.
Important
Call Quality Control is an experimental feature. CleverTap may refine it further based on the real-time implementation. If you have any feedback, contact your Account Manager.
Network Quality Callback
The onNetworkQualityResponse(int score)
callback is triggered on both the initiator and receiver sides. It returns a network quality score that helps the app decide whether to proceed with or reject the call. The score ranges from -1 to 100, as per the following breakdown of score values:
- Score >= 90: Ideal voice quality.
- Score 80-89: Good quality with the possibility of minor intermittent issues.
- Score 70-79: The call is usable but may have some audio concerns.
- Score 60-69: Significant audio issues, including voice breakage and delays. However, it is still operational and not ideal.
- Score 1-59: Major issues that could lead to call drops.
- Score 0: Possible network loss or major network connectivity concerns
- Score -1: Indeterminate state, possibly caused by network switching, proxy issues, or misconfigured routers.
Recommendation
CleverTap recommends going ahead with the call if the Score >=70.
Sample Implementation
To implement the onNetworkQualityResponse
callback in your app,
integrate the SignedCallAPI.getInstance().setNetworkQualityCheckHandler(handler)
in the Application
class to ensure the callback is triggered even when the app is not running.
SignedCallAPI.getInstance().setNetworkQualityCheckHandler(new SCNetworkQualityHandler() {
@Override
public boolean onNetworkQualityResponse(final int score) {
Log.d(LOG_TAG, "Network quality score: " + score);
// Return true to proceed with the call, or false to reject the call
return true/false;
}
});
SignedCallAPI.getInstance().setNetworkQualityCheckHandler(object : SCNetworkQualityHandler {
override fun onNetworkQualityResponse(score: Int): Boolean {
Log.d(LOG_TAG, "Network quality score: $score")
// Return true to proceed with the call, or false to reject the call
return true // or false
}
})
The onNetworkQualityResponse(score)
callback expects a boolean response from the app:
- If the app returns
true
, the SDK proceeds to process the call, ensuring optimal call quality. - If the app returns
false
, the SDK terminates or declines the call based on the initiator's or receiver's role:- Initiator's side: The SDK terminates the outgoing call request and triggers a
CallException.BadNetworkException
. This exception is reported in theonFailure(exception)
callback ofOutgoingCallResponse
, enabling the app to initiate a PSTN fallback. - Receiver's side: The SDK declines the incoming call and reports the status as
VoIPCallStatus.APP_INITIATED_CALL_DECLINED_DUE_TO_NETWORK_QUALITY
via thecallStatus(callStatusDetails)
callback.
- Initiator's side: The SDK terminates the outgoing call request and triggers a
Important
- The SDK call rejection behavior remains consistent in both scenarios, regardless of whether the callback is implemented.
- The SDK waits 500 ms for a response from
onNetworkQualityResponse
. If no response is received, it proceeds with the call. Therefore, ensure that you return the boolean response within this window.- The SDK invokes the
onNetworkQualityResponse
callback on a background thread. As a result, avoid performing any UI-related operations directly within this callback. If needed, switch to the main thread manually.
Handle Call Events
The call event handling varies depending on the version of the Signed Call Android SDK you are using.
For SDK Versions 0.0.5.x and Above
Deprecated Event
- The
CALLEE_BUSY_ON_ANOTHER_CALL
event is deprecated in SDK versions starting from 0.0.5.x.- It is split into the following two new events:
CALL_DECLINED_DUE_TO_BUSY_ON_VOIP
andCALL_DECLINED_DUE_TO_BUSY_ON_PSTN
. These events help differentiate calls declined due to another Signed Call(VoIP) or a PSTN call.
Add the following code to register the listener to receive changes in the VoIP call states:
SignedCallAPI.getInstance().registerVoIPCallStatusListener(new SCVoIPCallStatusListener() {
@Override
public void callStatus(final SCCallStatusDetails callStatusDetails) {
//App is notified on the main thread to notify the changes in the call-state
Log.d(LOG_TAG, "callStatus is invoked with: " + callStatusDetails.toString());
SCCallStatusDetails.CallDirection direction = callStatusDetails.getDirection();
VoIPCallStatus callStatus = callStatusDetails.getCallStatus();
CallDetails callDetails = callStatusDetails.getCallDetails()
String callId = callDetails.callId;
String requestId = callDetails.requestId;
SignallingChannel channel = callDetails.channel;
if (direction.equals(CallDirection.OUTGOING)) {
//Handle events for initiator of the call
if (callStatus == VoIPCallStatus.CALL_IS_PLACED) {
// When the call is successfully placed
} else if (callStatus == VoIPCallStatus.CALL_RINGING) {
// When the call starts ringing on the receiver's device
} else if (callStatus == VoIPCallStatus.CALL_CANCELLED) {
// When the call is cancelled from the initiator's end
} else if (callStatus == VoIPCallStatus.CALL_CANCELLED_DUE_TO_RING_TIMEOUT) {
// When the call is call is cancelled due to a ring timeout.
// This event is reported when the SDK fails to establish communication with the receiver, often due to an offline device or a device with low bandwidth.
} else if (callStatus == VoIPCallStatus.CALL_DECLINED) {
// When the call is declined from the receiver's end
} else if (callStatus == VoIPCallStatus.CALL_MISSED) {
// When the call is missed at the receiver's end
} else if (callStatus == VoIPCallStatus.CALL_ANSWERED) {
// When the call is picked up by the receiver
} else if (callStatus == VoIPCallStatus.CALL_IN_PROGRESS) {
// When the connection to the receiver is established
} else if (callStatus == VoIPCallStatus.CALL_OVER) {
// When the call has been disconnected
} else if (callStatus == VoIPCallStatus.CALL_OVER_DUE_TO_LOCAL_NETWORK_LOSS) {
// When the call is disconnected for the party that experienced a network loss
} else if (callStatus == VoIPCallStatus.CALL_OVER_DUE_TO_REMOTE_NETWORK_LOSS) {
// When the call has been disconnected for the party that dropped due to network loss at remote end
} else if (callStatus == VoIPCallStatus.CALL_OVER_DUE_TO_PROTOCOL_MISMATCH) {
// When the call has been disconnected due to protocol mismatch.
} else if (callStatus == VoIPCallStatus.CALL_OVER_DUE_TO_NETWORK_DELAY_IN_MEDIA_SETUP) {
// When the call disconnects due to network delays that prevent media setup.
} else if (callStatus == VoIPCallStatus.CALLEE_BUSY_ON_ANOTHER_CALL) {
// When the receiver is busy on another call(includes both VoIP or PSTN)
} else if (callStatus == VoIPCallStatus.CALL_DECLINED_DUE_TO_BUSY_ON_VOIP) {
// When the receiver is busy in a VoIP call
} else if (callStatus == VoIPCallStatus.CALL_DECLINED_DUE_TO_BUSY_ON_PSTN) {
// When the receiver is busy in a PSTN call
} else if (callStatus == VoIPCallStatus.CALL_DECLINED_DUE_TO_LOGGED_OUT_CUID) {
// When the receiver's cuid is logged out and logged in with different cuid
} else if (callStatus == VoIPCallStatus.CALL_DECLINED_DUE_TO_NOTIFICATIONS_DISABLED) {
// When the receiver's Notifications Settings are disabled from application settings
} else if (callStatus == VoIPCallStatus.CALLEE_MICROPHONE_PERMISSION_NOT_GRANTED) {
// When the Microphone permission is denied or blocked while receiver answers the call
} else if (callStatus == VoIPCallStatus.CALLEE_MICROPHONE_PERMISSION_BLOCKED) {
// When the microphone permission is blocked at the receiver's end.
} else if (callStatus == VoIPCallStatus.CALL_FAILED_DUE_TO_INTERNAL_ERROR) {
// When the call fails after signalling. Possible reasons could include low internet connectivity, low RAM available on device, SDK fails to set up the voice channel within the time limit
} else if(callStatus == VoIPCallStatus.CALL_OVER_DUE_TO_NETWORK_DELAY_IN_CONNECTION_SETUP) {
// When the call fails due to network delay during the call setup phase when a call is answered but not yet ready for voice exchange.
}
} else if (direction.equals(CallDirection.INCOMING)) {
//Handle events for receiver of the call
// Receiver will get the same list of events as above so handle accordingly
}
}
});
SignedCallAPI.getInstance().registerVoIPCallStatusListener(object : SCVoIPCallStatusListener {
override fun callStatus(callStatusDetails: SCCallStatusDetails) {
// App is notified on the main thread to notify the changes in the call-state
Log.d(LOG_TAG, "callStatus is invoked with: ${callStatusDetails.toString()}")
val direction = callStatusDetails.direction
val callStatus: VoIPCallStatus = callStatusDetails.callStatus
val callDetails: CallDetails = callStatusDetails.callDetails
val channel: SignallingChannel = callDetails.channel
val callId: String = callDetails.callId;
val requestId: String = callDetails.requestId;
if (direction == SCCallStatusDetails.CallDirection.OUTGOING) {
// Handle the events for initiator of the call
when (callStatus) {
VoIPCallStatus.CALL_IS_PLACED -> {
// When the call is successfully placed
}
VoIPCallStatus.CALL_RINGING -> {
// When the call starts ringing on the receiver's device
}
VoIPCallStatus.CALL_CANCELLED -> {
// When the call is cancelled from the initiator's end
}
VoIPCallStatus.CALL_DECLINED -> {
// When the call is declined from the receiver's end
}
VoIPCallStatus.CALL_MISSED -> {
// When the call is missed at the receiver's end
}
VoIPCallStatus.CALL_ANSWERED -> {
// When the call is picked up by the receiver
}
VoIPCallStatus.CALL_IN_PROGRESS -> {
// When the connection to the receiver is established
}
VoIPCallStatus.CALL_OVER -> {
// When the call has been disconnected
}
VoIPCallStatus.CALL_OVER_DUE_TO_LOCAL_NETWORK_LOSS -> {
// // When the call is disconnected for the party that experienced a network loss
}
VoIPCallStatus.CALL_OVER_DUE_TO_REMOTE_NETWORK_LOSS -> {
// When the call has been disconnected for the party that dropped due to network loss at remote end
}
VoIPCallStatus.CALL_OVER_DUE_TO_PROTOCOL_MISMATCH -> {
// When the call has been disconnected due to protocol mismatch.
}
VoIPCallStatus.CALL_OVER_DUE_TO_NETWORK_DELAY_IN_MEDIA_SETUP -> {
When the call disconnects due to network delays that prevent media setup.
}
VoIPCallStatus.CALLEE_BUSY_ON_ANOTHER_CALL -> {
// When the receiver is busy on another call
}
VoIPCallStatus.CALL_DECLINED_DUE_TO_LOGGED_OUT_CUID -> {
// When the receiver's cuid is logged out and logged in with a different cuid
}
VoIPCallStatus.CALL_DECLINED_DUE_TO_NOTIFICATIONS_DISABLED -> {
// When the receiver's Notifications Settings are disabled from application settings
}
VoIPCallStatus.CALLEE_MICROPHONE_PERMISSION_NOT_GRANTED -> {
// When the Microphone permission is denied or blocked while the receiver answers the call
}
VoIPCallStatus.CALLEE_MICROPHONE_PERMISSION_BLOCKED -> {
// When the microphone permission is blocked at the receiver's end.
}
VoIPCallStatus.CALL_FAILED_DUE_TO_INTERNAL_ERROR -> {
// When the call fails after signalling. Possible reasons could include low internet connectivity, low RAM available on device, SDK fails to set up the voice channel within the time limit
}
VoIPCallStatus.CALL_OVER_DUE_TO_NETWORK_DELAY_IN_CONNECTION_SETUP {
// When the call fails due to network delay during the call setup phase when a call is answered but not yet ready for voice exchange.
}
}
} else if (direction == SCCallStatusDetails.CallDirection.INCOMING) {
// Handle the events for the receiver of the call
// Receiver will get the same list of events as above so handle accordingly
}
}
})
Retrieve Current Call State
To retrieve the current call state, the Signed Call SDK exposes the getCallState()
method via SCCallController
class. Refer to the code snippets below to retrieve the current call state:
SCCallController callController = SignedCallAPI.getInstance().getCallController();
if (callController != null) {
SCCallState callState = callController.getCallState();
Log.d(LOG_TAG, "call is in " + callState.name() + " state");
}
val callController: SCCallController? = SignedCallAPI.getInstance().callController
callController?.let {
val callState: SCCallState = callController.callState
Log.d(LOG_TAG, "call is in ${callState.name} state")
}
Possible call states returned by the SDK includeOUTGOING_CALL
, INCOMING_CALL
, ONGOING_CALL
, CLEANUP_CALL
and NO_CALL
. The NO_CALL
state is returned when there is no ongoing active call.
Return to Active Call
To return to an active call, the Signed Call SDK exposes the getBackToCall(context)
method via SCCallController
class. Refer to the code snippets below to return to an active call:
SCCallController callController = SignedCallAPI.getInstance().getCallController();
if (callController != null && callController.getCallState() != SCCallState.NO_CALL) {
boolean result = callController.getBackToCall(getApplicationContext());
Log.d(LOG_TAG, "result: " + result);
}
val callController = SignedCallAPI.getInstance().callController
callController?.let { controller ->
if (controller.callState != SCCallState.NO_CALL) {
val result = controller.getBackToCall(applicationContext)
Log.d(LOG_TAG, "result: $result")
}
}
To navigate the user to the active call, call the getBackToCall(context)
method if an active call exists. The Signed Call Android SDK logs an error message if no call is in progress. The SDK returns true if the call screen is successfully relaunched. Otherwise, it returns false in case of an error or if there is no call in progress.
Close Socket Connection
The Signed Call Android SDK uses the socket connection to signal VoIP calls from the initiator to the receiver. After successful SDK initialization, the socket connection to initiate or receive VoIP calls opens.
If the socket is left open for a longer period, the application might show a system-generated notification to the user that the application is draining the device's battery. Therefore, we recommend disconnecting the socket after all expected/pending transactions are over.
To close the socket connection, use the following method as per your business use case:
SignedCallAPI.getInstance().disconnectSignallingSocket(getApplicationContext());
SignedCallAPI.getInstance().disconnectSignallingSocket(applicationContext);
The following is the Signed Call Android SDK behavior after the SDK calls the disconnectSignallingSocket(appContext)
method:
Functionality | Signed Call Android SDK Behavior |
---|---|
Initiate a call | Users cannot initiate calls. If they attempt a VoIP call request, the Signed Call Android SDK returns CallException.SignallingSocketConnectionRequiredException within the onFailure(callException) method. It is essential to reinitialize the Signed Call SDK if a use case arises where a call needs to be initiated.For example, if a user happens to place an order in the application and you want to give the option to initiate a VoIP call, the Signed Call Android SDK needs to be reinitialized. |
Receive a call | Users can still receive calls as Signed Call uses FCM as a fallback channel to receive the calls. |
Busy Handling
Signed Call Android SDK smartly handles the scenarios when the user is busy on a call.
The following scenarios describe the Signed Call Android SDK behavior:
Scenario 1: The user is busy on a call (VoIP or PSTN), and another user initiates a VoIP call to the busy user. In this case, the Signed Call Android SDK displays User is busy on another call on the outgoing call screen and declines the initiated call.
Scenario 2: The user is busy on a VoIP call, and meanwhile, the user answers a PSTN call, meaning that two calls (VoIP and PSTN) are connected simultaneously. In this case, the Signed Call Android SDK prioritizes the PSTN call over the VoIP call by putting the VoIP call on hold for both the receiver and initiator of the VoIP call. After the PSTN call ends, the Signed Call Android SDK resumes the VoIP call.
Prerequisites of Busy Handling Scenarios
The Signed Call Android SDK needs Read Phone State permission to handle the busy handling scenarios of PSTN calls.
- In scenario 1, Signed Call Android SDK uses this permission to determine if the user is available or busy on a PSTN call. This permission is required for Android 12 and onwards only.
- In scenario 2, the Signed Call Android SDK supports the underlying implementation only if the user has granted the Read Phone State permission.
Configure Missed Call CTA
If the receiver misses a call, the Signed Call Android SDK shows a missed call notification to the receiver. The Signed Call Android SDK uses action buttons on the missed call notification to display a Call to Action (CTA).
To configure the CTA on the missed call notification, perform the following steps during the initialization of the Signed Call Android SDK:
- Create a list of CTAs using the
MissedCallAction
class.
List<MissedCallAction> missedCallActionsList = new ArrayList<>();
missedCallActionsList.add(new MissedCallAction("<Unique Identifier>", "<label on action-button>"));
val missedCallActionsList: MutableList<MissedCallAction> = ArrayList()
missedCallActionsList.add(MissedCallAction("<Unique Identifier>", "<label on action-button>"))
Action Buttons
You can configure a maximum of three action buttons on a missed call notification.
- Handle the click events of the missed call CTAs. To do so, create a custom
MissedCallActionsHandler
class by implementing theMissedCallNotificationOpenedHandler
.
public class MissedCallActionsHandler implements MissedCallNotificationOpenedHandler {
@Override
public void onMissedCallNotificationOpened(Context context, MissedCallNotificationOpenResult result) {
//get the the action-details from result object and handle accordingly
Log.d(LOG_TAG, "actionId: " + result.action.actionID
+ ", actionLabel: " + result.action.actionLabel
+ ", context of call: " + result.callDetails.callContext
+ ", cuid of caller: " + result.callDetails.callerCuid
+ ", cuid of callee: " + result.callDetails.calleeCuid
+ ", initiator's image: " + result.callDetails.initiatorImage
+ ", receiver's image: " + result.callDetails.receiverImage
+ ", call id: " + result.callDetails.callId
+ ", request id " + result.callDetails.requestId
);
}
}
class MissedCallActionsHandler : MissedCallNotificationOpenedHandler {
override fun onMissedCallNotificationOpened(context: Context, result: MissedCallNotificationOpenResult) {
//get the the action-details from result object and handle accordingly
Log.d(TAG, "actionId: " + result.action.actionID
+ " actionLabel: " + result.action.actionLabel
+ " context of call: " + result.callDetails.callContext
+ " cuid of caller: " + result.callDetails.callerCuid
+ " cuid of callee: " + result.callDetails.calleeCuid
+ ", call id: " + result.callDetails.callId
+ ", request id " + result.callDetails.requestId);
}
}
Note
The
MissedCallActionsHandler
must not be a singleton class.
- Pass the list of
MissedCallAction
and the canonical path of theMissedCallActionsHandler
in the
setMissedCallActions(List<MissedCallAction> list, String path)
method of theSignedCallInitConfiguration.Builder
class.
List<MissedCallAction> missedCallActionsList = new ArrayList<>();
missedCallActionsList.add(new MissedCallAction("<Unique Identifier>", "<label on action-button>"));
//gets the name of the class including its package
String missedCallHandlerPath = MissedCallActionsHandler.class.getCanonicalName();
SignedCallInitConfiguration initConfiguration = new SignedCallInitConfiguration.Builder(initOptions, allowPersistSocketConnection)
.setMissedCallActions(missedCallActionsList, missedCallHandlerPath)
.build();
SignedCallAPI.getInstance().init(getApplicationContext(), initConfiguration, cleverTapAPI, signedCallInitListener);
val missedCallActionsList: MutableList<MissedCallAction> = ArrayList()
missedCallActionsList.add(MissedCallAction("<Unique Identifier>", "<label on action-button>"))
//gets the name of the class including its package
val missedCallHandlerPath = MissedCallActionsHandler::class.java.canonicalName
val initConfiguration = SignedCallInitConfiguration.Builder(options, allowPersistSocketConnection)
.setMissedCallActions(missedCallActionsList, missedCallHandlerPath)
.build()
SignedCallAPI.getInstance().init(applicationContext, initConfiguration, cleverTapAPI, signedCallInitListener)
Starting from SDK v0.0.7.3, the setMissedCallActions(missedCallActionsList, missedCallHandlerPath)
method has been deprecated. The SDK provides the overloaded method, setMissedCallActions(missedCallActions)
, eliminating the need to pass the handler class path as a second parameter. To pass the missedCallHandler
, SDK provides the setMissedCallNotificationOpenedHandler(MissedCallNotificationOpenedHandler handlerInstance)
API through SignedCallAPI.getInstance()
class which allows you to create MissedCallNotificationOpenedHandler
on the application side.
List<MissedCallAction> missedCallActionsList = new ArrayList<>();
missedCallActionsList.add(new MissedCallAction("<Unique Identifier>", "<label on action-button>"));
SignedCallInitConfiguration initConfiguration = new SignedCallInitConfiguration.Builder(initOptions, allowPersistSocketConnection)
.setMissedCallActions(missedCallActionsList)
.build();
SignedCallAPI.getInstance().init(getApplicationContext(), initConfiguration, cleverTapAPI, signedCallInitListener);
val missedCallActionsList = mutableListOf<MissedCallAction>()
missedCallActionsList.add(MissedCallAction("<Unique Identifier>", "<label on action-button>"))
val initConfiguration = SignedCallInitConfiguration.Builder(initOptions, allowPersistSocketConnection)
.setMissedCallActions(missedCallActionsList)
.build()
SignedCallAPI.getInstance().init(applicationContext, initConfiguration, cleverTapAPI, signedCallInitListener)
Note
Integrate the
SignedCallAPI.getInstance().setMissedCallNotificationOpenedHandler(handlerInstance)
in the application class to ensure callback is triggered when the user clicks the CTA while the app is running in the background or not running at all.
Handle Missed Call CTA for Callback Implementation
When implementing a "callback" CTA for missed call notifications, ensure to check if the Signed Call SDK is initialized before initiating a call. Use SignedCallAPI.getInstance().isInitialized(context)
to verify initialization status, as explained under Retrieve Initialization Status.
Example
The following is a sample code to handle the callback click event:
public class MissedCallNotificationHandler implements MissedCallNotificationOpenedHandler {
@Override
public void onMissedCallNotificationOpened(Context context, MissedCallNotificationOpenResult result) {
if (SignedCallAPI.getInstance().isInitialized(context)) {
initiateCall(context, result);
} else {
Log.d(LOG_TAG, "SDK is being initialized, please wait!");
initSignedCallSDK(context, () -> initiateCall(context, result));
}
}
private void initiateCall(Context context, MissedCallNotificationOpenResult result) {
SignedCallAPI.getInstance().call(context, result.callDetails.callerCuid, result.callDetails.callContext, null, new OutgoingCallResponse() {
@Override
public void onSuccess() {
Log.d(LOG_TAG, "Call successfully initiated!");
}
@Override
public void onFailure(CallException callException) {
Log.d(LOG_TAG, "Call failed: " + callException.getMessage());
}
});
}
private void initSignedCallSDK(Context context, Runnable onSuccessCallback) {
try {
SignedCallAPI.getInstance().init(context, initConfiguration, CleverTapAPI.getDefaultInstance(context), new SignedCallInitResponse() {
@Override
public void onSuccess() {
onSuccessCallback.run();
}
@Override
public void onFailure(@NonNull InitException initException) {
Log.d(LOG_TAG, "Initialization failed: " + initException.getMessage());
}
});
} catch (Exception e) {
Log.e(LOG_TAG, "Exception while initializing SignedCallSDK: " + e.getLocalizedMessage());
}
}
}
class MissedCallNotificationHandler : MissedCallNotificationOpenedHandler {
override fun onMissedCallNotificationOpened(context: Context, result: MissedCallNotificationOpenResult) {
if (SignedCallAPI.getInstance().isInitialized(context)) {
initiateCall(context, result)
} else {
Log.d(LOG_TAG, "SDK is being initialized, please wait!")
initSignedCallSDK(context) { initiateCall(context, result) }
}
}
private fun initiateCall(context: Context, result: MissedCallNotificationOpenResult) {
SignedCallAPI.getInstance().call(
context, result.callDetails.callerCuid, result.callDetails.callContext, null,
object : OutgoingCallResponse {
override fun onSuccess() {
Log.d(LOG_TAG, "Call successfully initiated!")
}
override fun onFailure(callException: CallException) {
Log.d(LOG_TAG, "Call failed: ${callException.message}")
}
}
)
}
private fun initSignedCallSDK(context: Context, onSuccessCallback: () -> Unit) {
try {
SignedCallAPI.getInstance().init(
context,
initConfiguration,
CleverTapAPI.getDefaultInstance(context),
object : SignedCallInitResponse {
override fun onSuccess() {
onSuccessCallback()
}
override fun onFailure(initException: InitException) {
Log.d(LOG_TAG, "Initialization failed: ${initException.message}")
}
}
)
} catch (e: Exception) {
Log.e(LOG_TAG, "Exception while initializing SignedCallSDK: ${e.localizedMessage}")
}
}
}
Dismiss Missed Call Notification
To dismiss the missed call notification, the Signed Call SDK exposes thedismissMissedCallNotification(context)
method throughSignedCallAPI.getInstance()
instance.
The following is the code to dismiss the missed call notification:
boolean result = SignedCallAPI.getInstance().dismissMissedCallNotification(getApplicationContext());
Log.d(LOG_TAG, "Notification dismissal result: " + result);
val result = SignedCallAPI.getInstance().dismissMissedCallNotification(applicationContext)
Log.d(LOG_TAG, "Notification dismissal result: $result")
Configure Swipe Off Behavior
By default, the Signed Call SDK terminates a call when the user swipes off the call screen. However, if your app runs a foreground service to maintain operation even after the app's swipe-off, you can modify this default behavior. The SDK allows you to override the default behavior and persist the call within the foreground service managed by your application.
To change the swipe-off behavior, the Signed Call Android SDK exposes a setSwipeOffBehaviourInForegroundService(SCSwipeOffBehaviour)
method via the SignedCallInitConfiguration.Builder
class. Pass the enum constant SCSwipeOffBehaviour.PERSIST_CALL
to ensure the call remains persistent in a foreground service upon swipe off.
SignedCallInitConfiguration initConfiguration = new SignedCallInitConfiguration.Builder(initOptions, allowPersistSocketConnection)
.setSwipeOffBehaviourInForegroundService(<pass SCSwipeOffBehaviour constant here>)
.build();
SignedCallAPI.getInstance().init(getApplicationContext(), initConfiguration, cleverTapAPI, signedCallInitListener)
val initConfiguration = SignedCallInitConfiguration.Builder(initOptions, allowPersistSocketConnection)
.setSwipeOffBehaviourInForegroundService(<pass SCSwipeOffBehaviour constant here>)
.build()
SignedCallAPI.getInstance().init(applicationContext, initConfiguration, cleverTapAPI, signedCallInitListener)
Note
- To persist a call after the app is swiped off, the app must continue running as a foreground service. If it doesn't, the call will end because there will be no active process to maintain it.
- For details on ensuring users can still access the call screen after the app is swiped off, refer to Return to Active Call. It is highly recommended to implement these instructions if you are altering the default behavior.
Call Hangup Functionality
The call hangup functionality is user-driven, and the user's decision to end the call depends on it. For example, if one of the users in a call clicks the call hangup button from the ongoing call screen, the Signed Call Android SDK internally manages the call hangup functionality to end the call.
In the case of a metered call, when a business wants to end the call after a specific duration, you must maintain a timer in the application and use the following method to terminate the call when the timer ends:
SCCallController callController = SignedCallAPI.getInstance().getCallController();
if (callController != null) {
callController.endCall();
}
SignedCallAPI.getInstance().callController?.endCall()
Set Local Branding for Call Screen
For P2P calls, you can set the branding from the dashboard or set it up locally.
Starting with SDK v0.0.7.6, the SignedCallScreenBranding
class introduces support for the Builder pattern, simplifying the process of defining and overriding specific branding properties without requiring all properties to be set at once.
To set up the branding locally, use the overrideDefaultBranding(SignedCallScreenBranding branding)
method exposed via the SignedCallInitConfiguration.Builder
class to set up the branding.
You can set up branding for the call screen in the following ways:
- Using the Constructor
SignedCallScreenBranding callScreenBranding = new SignedCallScreenBranding(bgColor, fontColor, logoUrl, buttonTheme, cancelCountdownColor);
callScreenBranding.setShowPoweredBySignedCall(<true/false>); //set false to hide the label from VoIP call screens. Default value is true.
SignedCallInitConfiguration initConfiguration = new SignedCallInitConfiguration.Builder(initOptions, allowPersistSocketConnection)
.overrideDefaultBranding(callScreenBranding)
.build();
SignedCallAPI.getInstance().init(getApplicationContext(), initConfiguration, cleverTapAPI, signedCallInitListener);
val callScreenBranding = SignedCallScreenBranding(bgColor, fontColor, logoUrl, buttonTheme)
val initConfiguration = SignedCallInitConfiguration.Builder(initOptions, allowPersistSocketConnection)
.overrideDefaultBranding(callScreenBranding)
.build()
SignedCallAPI.getInstance().init(applicationContext, initConfiguration, cleverTapAPI, signedCallInitListener)
Using the Builder Pattern (SDK v0.0.7.6 or later):
SignedCallScreenBranding callScreenBranding = new SignedCallScreenBranding.Builder()
.setBgColor(bgColor)
.setFontColor(fontColor)
.setLogoUrl(logoUrl)
.setButtonTheme(buttonTheme)
.setCancelCountdownColor(cancelCountdownColor)
.setShowPoweredBySignedCall(<true/false>)
.build();
SignedCallInitConfiguration initConfiguration = new SignedCallInitConfiguration.Builder(initOptions, allowPersistSocketConnection)
.overrideDefaultBranding(callScreenBranding)
.build();
SignedCallAPI.getInstance().init(getApplicationContext(), initConfiguration, cleverTapAPI, signedCallInitListener);
val callScreenBranding = SignedCallScreenBranding.Builder()
.setBgColor(bgColor)
.setFontColor(fontColor)
.setLogoUrl(logoUrl)
.setButtonTheme(buttonTheme)
.setCancelCountdownColor(cancelCountdownColor)
.setShowPoweredBySignedCall(<true/false>)
.build()
val initConfiguration = SignedCallInitConfiguration.Builder(initOptions, allowPersistSocketConnection)
.overrideDefaultBranding(callScreenBranding)
.build()
SignedCallAPI.getInstance().init(applicationContext, initConfiguration, cleverTapAPI, signedCallInitListener)
The parameters to override the dashboard call screen branding are as follows:
Parameter | Description | Type | Notes |
---|---|---|---|
bgColor | The background color of the call screens. | String | |
fontColor | The color of the text displayed on the call screens. | String | |
logoUrl | The image URL that renders on the call screens. | String | |
buttonTheme | The theme of the control buttons shown on the ongoing call screen (Mute, Speaker, and Bluetooth). | SignedCallScreenBranding.ButtonTheme.LIGHT OR SignedCallScreenBranding.ButtonTheme.DARK | |
cancelCountdownColor | By default, the SDK launches the outgoing call screen immediately without waiting for signaling confirmation. A countdown ProgressBar is displayed around the cancel button until signaling is completed. | String |
Debugging
Signed Call Android SDK logs are, by default, set to the SignedCallAPI.LogLevel.INFO
level. We recommend you set the Signed Call Android SDK to VERBOSE mode to log warnings or other important messages to the Android logging system during development. To do so, set the debug level to SignedCallAPI.LogLevel.VERBOSE
. If you want to disable the Signed Call Android SDK logs for the production environment, you can set the debug level to SignedCallAPI.LogLevel.OFF
.
To debug your application with the Signed Call Android SDK:
- Set the debug level for the Signed Call Android SDK.
SignedCallAPI.setDebugLevel(SignedCallAPI.LogLevel.INFO); //Default Log level
SignedCallAPI.setDebugLevel(SignedCallAPI.LogLevel.DEBUG); //Set Log level to DEBUG log warnings or other important messages
SignedCallAPI.setDebugLevel(SignedCallAPI.LogLevel.VERBOSE); //Set Log level to VERBOSE
SignedCallAPI.setDebugLevel(SignedCallAPI.LogLevel.OFF); //Switch off the logs for Production environment
SignedCallAPI.setDebugLevel(SignedCallAPI.LogLevel.INFO) //Default Log level
SignedCallAPI.setDebugLevel(SignedCallAPI.LogLevel.DEBUG) //Set Log level to DEBUG log warnings or other important messages
SignedCallAPI.setDebugLevel(SignedCallAPI.LogLevel.VERBOSE) //Set Log level to VERBOSE
SignedCallAPI.setDebugLevel(SignedCallAPI.LogLevel.OFF) //Switch off the logs for Production environment
- After setting the debug level to
SignedCallAPI.LogLevel.VERBOSE
, search for
[CT]:[SignedCall]. The logcat window displays the handshakes between the Signed Call Android SDK and your application.
Error Handling
The Signed Call Android SDK provides error reporting and handling. The onFailure(InitException)
of the SignedCallInitResponse
reports all the initialization-related errors, where the onFailure(CallException)
of the OutgoingCallResponse
reports all the call-related errors.
Initialization Errors
The following is the list of error objects that you may receive when initializing the Signed Call Android SDK:
Error Object | Error Code | Error Description |
---|---|---|
NoInternetException | 1000 | No internet connection. |
AppContextRequiredException | 2000 | The application context is missing. |
CleverTapApiInstanceRequiredException | 2001 | The CleverTapApi instance is missing. |
InitConfigRequiredException | 2002 | The initOptions is missing. |
SdkNotInitializedException | 2003 | The Signed Call Android SDK is not initialized. |
MissingAccountIdOrApiKeyException | 2004 | The accountId and apiKey parameters are missing. |
MissingCuIdException | 2005 | The cuid is missing. |
InvalidCuidLengthException | 2006 | The cuid length is invalid. |
InvalidCuidException | 2007 | Invalid cuid due to violation of valid cuid rules. |
InvalidNameLengthException | 2008 | The length of the name parameter is invalid. |
InvalidAppIdException | 2009 | The appId is invalid. |
InvalidBrandingConfigException | 2010 | The branding configuration is invalid. |
BadRequestException | 2011 | The values in initOptions are invalid. |
AuthFailureException | 2012 | The user authentication is not successful. |
NotificationPermissionRequiredException | 2013 | The notification permission was not given during the SDK initialization. |
Call Errors
The following is the list of possible error objects when making a call:
Error Object | Error Code | Error Description | Minimum Supported Version |
---|---|---|---|
NoInternetException | 1000 | No internet connection. | v0.0.7.0 |
MicrophonePermissionNotGrantedException | 5001 | Microphone permission is not available. | v0.0.7.0 |
InternetLostAtReceiverEndException | 5002 | The Internet is lost at the receiver's end before the call connects. | v0.0.7.0 |
ContactNotReachableException | 5003 | The receiver is unreachable. | v0.0.7.0 |
BadNetworkException | 5004 | The Signed Call Android SDK can not initiate the call because of a poor network. | v0.0.7.0 |
CanNotCallSelfException | 5005 | The Receiver and Initiator cuid is the same. | v0.0.7.0 |
CallContextRequiredException | 5006 | The context of the call is missing. | v0.0.7.0 |
CallContextExceededBy64Exception | 5007 | The length of the context message exceeds the limit of 64 characters. | v0.0.7.0 |
InvalidAppContextException | 5008 | Invalid context of the application. | v0.0.7.0 |
CalleeInfoRequiredException | 5009 | The receiver cuid is missing. | v0.0.7.0 |
VoIPCallException | 5010 | The signed call can not be initiated to the unregistered/invalid cuid . | v0.0.7.0 |
SignallingSocketConnectionRequiredException | 5011 | The socket required to initiate a call is not connected to the signaling channel. | v0.0.7.0 |
IncorrectParamsInCallOptionsException | 5012 | The callOptions parameters are invalid. | v0.0.7.0 |
CanNotProcessCallRequest | 5013 | Cannot process new call requests as the Signed Call Android SDK is already processing another. | v0.0.7.0 |
CallFeatureNotAvailable | 5014 | The call feature is not enabled to initiate the call. | v0.0.7.0 |
InvalidActionDueToIncompleteSignalingSetup | 5015 | Outgoing Call Action is invalid at this stage. | v0.0.7.0 |
CallRequestException | 5016 | An unknown exception occurred while processing the call request. | v0.0.8.1 |
RequestIdNotPassed | 5017 | Request ID feature is opted in but requestId is not passed for the call. | v0.0.8.1 |
InvalidRequestId | 5018 | Passed requestId does not follow the validation criteria. | v0.0.8.1 |
RequestedEntityNotFoundException | 9001 | The receiver cuid is not reachable due to the FCM token expiring. | v0.0.8.1 |
SenderIdMismatchException | 9002 | The receiver FCM account, being used in the app, does not match with the FCM account configured on the CleverTap dashboard. | v0.0.8.1 |
TargetCtAccountIdNotFoundException | 9003 | The receiver cuid does not exist or register. | v0.0.8.1 |
TargetDeviceIdNotFoundException | 9004 | The receiver cuid is not reachable due to missing FCM token. | v0.0.8.1 |
FcmConfigNotFoundException | 9005 | The FCM account setup for Signed Call is missing on the CleverTap dashboard. | v0.0.8.1 |
FcmServerKeyNotFoundExceptionException | 9006 | The FCM server key (HTTP v1) is missing on the CleverTap dashboard. | v0.0.8.1 |
FeatureNotAvailableException | 9007 | The receiver is on the web platform and not reachable via the socket channel. | v0.0.8.1 |
FcmPushQuotaExceededException | 9008 | The Send requests per minute limit is exceeded; check your Firebase quota. | v0.0.8.1 |
FAQs
Is Signed Call accountId
and apiKey
the same as CleverTap accountId
and token
?
accountId
and apiKey
the same as CleverTap accountId
and token
?No. Signed Call accountId
and apiKey
differ from CleverTap accountId
and token.
You can find these details under Signed Call Settings from the CleverTap dashboard.
Does the Signed Call Android SDK support In-App calls over Bluetooth?
Yes. The Signed Call Android SDK has built-in Bluetooth support. It requires a runtime BLUETOOTH_CONNECT permission for Android 12 and onwards.
What channels does the Signed Call Android SDK use for call routing?
Signed Call Android SDK uses an active socket connection as soon as the SDK is initialized. The socket connection is a primary routing channel to receive the calls, whereas FCM is a fallback channel in case the receiver is not connected to the socket channel. This socket connection processes the call requests raised to make a call. For more information, refer to Best practices for initializing Signed Call SDKs.
Updated 1 day ago