Android In-App Voice SDK
Android · In-App Voice SDK

Integrate the SecuredCalls Voice SDK

Add branded, verified in-app voice calling to your Android app. Follow these sections to add the SDK, configure Firebase, initialize the SDK, handle permissions, and place your first call.

Estimated time: 32 minutes

Android 8.0+Android StudioKotlin 2.0.21+Gradle 8.14.1+

Overview

The SecuredCalls Voice SDK delivers branded VoIP and PSTN calls with Firebase Cloud Messaging-driven incoming calls. Before you start, register on SecuredCalls.com and obtain your Config.dat file and client secret.

Topics

Jump to any part of the integration.

Prerequisites

Make sure your environment is ready.

  • Mac or Windows OS with developer mode enabled
  • Android Studio with Meerkat Feature Drop | 2024.3.2 or above version
  • Android Gradle Plugin 8.10.1 and above with Gradle version 8.14.1 and above
  • Kotlin version 2.0.21 and above
  • At least one physical Android device running Android 8 or later
  • A SecuredCalls.com account with your Config.dat file and client secret
1

Section 1

Add the SDK to your project

Register the SDK in your version catalog, then add the plugins and dependencies in your Gradle files.

STEP 1

Declare versions

Open your project libs.versions.toml file and add the library and plugin versions under the [versions] table.

libs.versions.tomlgroovy
1firebaseBom = "33.14.0"2gms = "4.4.2"3kotlin = "2.0.21"4scVoice = "1.0.52"
STEP 2

Declare libraries

Add the Firebase and SecuredCalls library entries under the [libraries] table.

libs.versions.tomlgroovy
1firebase-bom = { group = "com.google.firebase", name = "firebase-bom", version.ref = "firebaseBom" }2firebase-messaging-ktx = { group = "com.google.firebase", name = "firebase-messaging-ktx" }3sc-voice = { module = "com.securedcalls:sc-voice", version.ref = "scVoice" }
STEP 3

Declare plugins

Add the Google Services and Kotlin Compose plugin entries under the [plugins] table.

libs.versions.tomlgroovy
1gms = { id = "com.google.gms.google-services", version.ref = "gms" }2kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
STEP 4

Apply plugins & dependencies

Open your app level build.gradle file and apply the plugins and add the dependencies.

app/build.gradlegroovy
1plugins {2    alias(libs.plugins.gms)3    alias(libs.plugins.kotlin.compose)4}5 6dependencies {7    implementation(platform(libs.firebase.bom))8    implementation(libs.firebase.messaging.ktx)9    implementation(libs.sc.voice)10}
STEP 5

Apply project-level plugins

Open your project level build.gradle file and add the plugins with apply false.

build.gradle (project)groovy
1plugins {2    alias(libs.plugins.gms) apply false3    alias(libs.plugins.kotlin.compose) apply false4}
STEP 6

Add ProGuard rules

Add the following keep rules so the SDK’s native and Vonage classes survive minification.

proguard-rules.progroovy
1-keep class com.vonage.**  { *; }2-keep class org.webrtc.**  { *; }3-keep class org.vonage.**  { *; }
2

Section 2

Add Config.dat and Firebase

Drop in the configuration file from the SecuredCalls portal and connect your Firebase project.

STEP 7

Add the Config.dat file

Add the Config.dat file downloaded from the SecuredCalls portal into your app’s assets folder.

  1. Go to your Android Studio project target.
  2. Select the File tab.
  3. Right click on the project’s module (e.g. app) app → New → Folder → Assets Folder, then select the Target source set option and click Finish.
  4. An assets folder is created at the path app/src/main/assets.
  5. Paste the downloaded Config.dat file into the assets folder.
STEP 8

Add the google-services.json file

Enable Firebase Cloud Messaging for your app and add the generated config file.

  1. Create your app’s Google Firebase project with the same package name you provided while registering the app with the SecuredCalls portal.
  2. Enable Firebase Cloud Messaging API in the Google Cloud developer console for the registered app.
  3. Go to Project settings, select the General tab and scroll down. You can see your app with the google-services.json file to download.
  4. Paste the downloaded google-services.json file into the project’s app folder.
3

Section 3

Initialize the SDK

Initialize SecuredVoiceCallSDK in your project’s Application class, then register it in the manifest.

STEP 9

Initialize in the Application class

Paste the following code into your Application class (e.g. SCVoiceCallApp). Typography customization is optional — omit it to use the SDK’s default system fonts.

Important:

Replace xxxxxxxSECRETxxxxxxx with your actual client secret. The appRedirectionIcon supports PNG and Vector XML from the drawable resource folder — do not use the launcher icon from the mipmap folder.

SCVoiceCallApp.ktkotlin
1import android.app.Application2import com.es.sc.voice.main.SecuredVoiceCallSDK3import com.es.sc.voice.main.models.ScSDKSettingsModel4import com.es.sc.voice.utils.LogLevel5import androidx.compose.ui.text.font.Font6import androidx.compose.ui.text.font.FontFamily7import androidx.compose.ui.text.font.FontWeight8import androidx.compose.ui.unit.sp9import com.es.sc.voice.views.compose.ScSDKFont10import com.es.sc.voice.views.compose.ScSDKTypography11 12class SCVoiceCallApp : Application() {13 14    companion object {15        lateinit var instance: SCVoiceCallApp16    }17 18    init {19        instance = this20    }21 22    val securedVoiceCallSDK: SecuredVoiceCallSDK = SecuredVoiceCallSDK(this)23    override fun onCreate() {24        super.onCreate()25 26        // MARK: - Optional Typography Customization27        //28        // The Typography object allows you to customize fonts used across29        // different UI surfaces of the SecuredCalls SDK.30        //31        // IMPORTANT:32        // - ALL typography fields are OPTIONAL33        // - If Typography is not provided, or if any font is nil,34        //   the SDK will automatically fall back to its DEFAULT system fonts35        // - You can replace below fonts with your fonts and adjust font size accordingly36        // - Font customization is purely visual and does NOT affect SDK behavior37 38        val fontRegular = FontFamily(Font(R.font.avenir_next_regular, FontWeight.Normal))39        val fontDemiBold = FontFamily(Font(R.font.avenir_next_demi_bold, FontWeight.SemiBold))40        val fontMedium = FontFamily(Font(R.font.avenir_next_medium, FontWeight.Medium))41        val fontLtProDemi = FontFamily(Font(R.font.avenir_next_lt_pro_demi, FontWeight.Bold))42 43        val scSdkTypography = ScSDKTypography(44            displayName = ScSDKFont(fontFamily = fontLtProDemi, fontSize = 36.sp), // Optional // Used for: large display titles, marquee-style text45            timer = ScSDKFont(fontFamily = fontMedium, fontSize = 32.sp), // Optional  // Used for: Call duration timer46            callStatus = ScSDKFont(fontFamily = fontRegular, fontSize = 20.sp), // Optional // Used for: Call status labels such as "Incoming Call", "Connecting", "Calling"47            poweredBy = ScSDKFont(fontFamily = fontMedium, fontSize = 18.sp), // Optional // Used for: "Powered by" branding text48            callIntentBody = ScSDKFont(fontFamily = fontDemiBold, fontSize = 24.sp), // Optional // Used for: Call intent body as a primary message text49            callIntentTitle = ScSDKFont(fontFamily = fontRegular, fontSize = 16.sp), // Optional // Used for: Call intent title text in in-app call UI50            buttonTitle = ScSDKFont(fontFamily = fontDemiBold, fontSize = 14.sp), // Optional // Used for: Button titles across SDK UI51            pipDisplayName = ScSDKFont(fontFamily = fontDemiBold, fontSize = 16.sp), // Optional // Used for: Display name shown in Picture-in-Picture (PiP) mode52            keypadButtonTitle = ScSDKFont(fontFamily = fontDemiBold, fontSize = 24.sp), // Optional // Used for: Dial pad / keypad button text53        )54 55        securedVoiceCallSDK.initialize(clientSecret = "xxxxxxxSECRETxxxxxxx", configFileName = "Config.dat", scSDKSettingsModel = ScSDKSettingsModel(logLevel = LogLevel.Info, typography = scSdkTypography), appRedirectionIcon = R.drawable.app_redirection_icon)56    }57}
Declaration
securedVoiceCallSDK.initialize(
  clientSecret: String,
  configFileName: String,
  scSDKSettingsModel: ScSDKSettingsModel,
  appRedirectionIcon: Int,
  typography: ScSDKTypography,
  autoDismissNotification: Boolean,
  biometricMetadata: BiometricMetadata,
  showDataChannelConnectionStatus: Boolean
)
clientSecretString

Mandatory. Replace xxxxxxxSECRETxxxxxxx with your actual API key.

configFileNameString

Optional. The config file name you pasted into the assets folder.

scSDKSettingsModelScSDKSettingsModel

Optional. SDK settings such as handlePermission, showPipView, showDataChannelConnectionDetails, and logLevel.

appRedirectionIconInt

Optional. App redirection icon shown on the voice call screen, from a drawable resource (PNG or Vector XML) — not the mipmap launcher icon.

typographyScSDKTypography

Optional. Typography object to customize the SDK UI fonts.

autoDismissNotificationBoolean

Optional. Whether to auto-dismiss the branding notification. Default false.

biometricMetadataBiometricMetadata

Optional. Metadata sent alongside a successful biometric verification.

showDataChannelConnectionStatusBoolean

Optional. Whether to show the data channel connection status. Default false.

STEP 10

Register the Application class

Add your application class name (e.g. SCVoiceCallApp) and allowBackup="false" to the application tag of your AndroidManifest.xml file.

AndroidManifest.xmlxml
1android:name=".SCVoiceCallApp"2android:allowBackup="false"
4

Section 4

Log in and handle callbacks

Declare the SDK instance, log the user in, and implement the callback interface.

STEP 11

Declare the SDK & identifiers

Declare the SDK instance and identifiers at Activity level.

Note:

userIdentifier can be any user identifier if you are only using in-app calls. If you have configured both in-app and PSTN calls, the userIdentifier must be a mobile number.

MainActivity.ktkotlin
1private lateinit var securedVoiceCallSDK: SecuredVoiceCallSDK2private val userIdentifier = "userIdentifier"3private val callbackIdentifier = "callbackIdentifier"4private var needToCheckPermission = false5private var permissionRequestType = 0
STEP 12

Initialize in onCreate()

Initialize securedVoiceCallSDK and the needToCheckPermission variable inside the Activity’s onCreate() function.

MainActivity.ktkotlin
1securedVoiceCallSDK = SCVoiceCallApp.instance.securedVoiceCallSDK2if (securedVoiceCallSDK.isConsumerRegistered()) {3    needToCheckPermission = true4}
STEP 13

Log the user in

Provide the userIdentifier and a SecuredVoiceCallBack implementation to handle login and voice call session success/error callbacks.

Important:

Call Task 12 of Re-initialize SDK session on app launch inside the onLoginSuccess() callback to initialize the SDK session once after login.

MainActivity.ktkotlin
1securedVoiceCallSDK.setSecuredCallBack(this)2securedVoiceCallSDK.login(userIdentifier)
STEP 14

Log the user out

Call the logout function to end the user session. On completion you receive an onLogoutSuccess() callback.

MainActivity.ktkotlin
1securedVoiceCallSDK.logout(onLogoutSuccess = {})
STEP 15

Implement SecuredVoiceCallBack

Implement the SecuredVoiceCallBack interface at Activity level (e.g. MainActivity.kt) to receive login and voice call session success/error callbacks.

MainActivity.ktkotlin
1class MainActivity : ComponentActivity(), SecuredVoiceCallBack {2    override fun onLoginError(message: String) {3        //Handle onLoginError callback4    }5 6    override fun onLoginSuccess() {7        //Handle onLoginSuccess callback checkPermissions()8        checkPermissions()9    }10 11    override fun onVoiceSessionError(message: String) {12        //Handle onVoiceSessionError callback13    }14 15    override fun onVoiceSessionSuccess() {16        //Handle onVoiceSessionSuccess callback.17        needToCheckPermission = true18    }19 20    override fun onCallStarted() {21        //Handle onCallStarted callback of outbound call22    }23 24    override fun onCallFailed() {25        //Handle onCallFailed callback of outbound call26    }27}
5

Section 5

Handle incoming pushes

Create a FirebaseMessagingService to receive push messages for voice and PSTN call branding and initiation.

STEP 16

Create the messaging service

Right click on a project source folder (e.g. notification) and choose New → Kotlin Class/File → Class, name it (e.g. ScFirebaseMessagingService), and paste the following code.

ScFirebaseMessagingService.ktkotlin
1import com.es.sc.SCVoiceCallApp2import com.google.firebase.messaging.FirebaseMessagingService3import com.google.firebase.messaging.RemoteMessage4 5class ScFirebaseMessagingService : FirebaseMessagingService() {6    private val securedVoiceCallSDK = SCVoiceCallApp.instance.securedVoiceCallSDK7 8    override fun onNewToken(token: String) {9        super.onNewToken(token)10        securedVoiceCallSDK.savePushToken(token)11    }12 13    override fun onMessageReceived(message: RemoteMessage) {14        super.onMessageReceived(message)15        if (securedVoiceCallSDK.isVoiceSDKPush(message.data)) {16            securedVoiceCallSDK.processingIncomingPush(message.data)17        }18    }19}
STEP 17

Add permissions to the manifest

Add the following permissions and feature declaration into your AndroidManifest.xml file.

AndroidManifest.xmlxml
1<uses-feature2    android:name="android.hardware.telephony" android:required="false" />3<uses-permission android:name="android.permission.INTERNET"/>4<uses-permission android:name="android.permission.READ_PHONE_STATE"/>5<uses-permission android:name="android.permission.CALL_PHONE"/>6<uses-permission android:name="android.permission.WRITE_CONTACTS"/>7<uses-permission android:name="android.permission.READ_CONTACTS"/>8<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />9<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>10<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
STEP 18

Register the service

Add the FirebaseMessagingService class (e.g. ScFirebaseMessagingService.kt) into your AndroidManifest.xml file.

AndroidManifest.xmlxml
1<service2    android:name=".notification.ScFirebaseMessagingService"3    android:exported="false">4    <intent-filter> <action android:name="com.google.firebase.MESSAGING_EVENT" />5    </intent-filter></service>
6

Section 6

Request and handle permissions

Show a permission sheet when permissions are denied, and handle the runtime permission callbacks.

STEP 19

Show the permission sheet

Copy the following code into your composable view to show the permission sheet at runtime when permissions are denied.

MainActivity.ktkotlin
1val showPermissionRequiredBottomSheet by PermissionState.showPermissionRequiredBottomSheet2val hasMicrophoneAndPhonePermission by PermissionState.hasMicrophoneAndPhonePermission3val hasContactPermission by PermissionState.hasContactPermission4val hasNotificationPermission by PermissionState.hasNotificationPermission5val hasLocationPermission by PermissionState.hasLocationPermission6val hasLocationServiceEnabled by PermissionState.hasLocationServiceEnabled7 8NonDismissibleBottomDialogSheet(9showBottomSheet = showPermissionRequiredBottomSheet,10onDismissRequest = {11    PermissionState.showPermissionRequiredBottomSheet.value = false12}, ) {13    PermissionRequiredContent(14        modifier = Modifier,15        hasMicrophonePhonePermission = hasMicrophoneAndPhonePermission,16        hasContactPermission = hasContactPermission,17        hasNotificationPermission = hasNotificationPermission,18        hasLocationPermission = hasLocationPermission,19        hasLocationServiceEnabled = hasLocationServiceEnabled,20        onRequestMicrophonePhonePermission = {21            if (securedVoiceCallSDK.isPermissionDeniedTwice(securedVoiceCallSDK.MICROPHONE_PERMISSION_DENIED)) {22                securedVoiceCallSDK.openAppPermissionsSettings(this@MainActivity, null)23                needToCheckPermission = true24            } else {25                securedVoiceCallSDK.requestMicrophoneAndPhonePermission(this@MainActivity, true)26            }27        },28        onRequestContactPermission = {29            if (securedVoiceCallSDK.isPermissionDeniedTwice(securedVoiceCallSDK.CONTACT_PERMISSION_DENIED)) {30                securedVoiceCallSDK.openAppPermissionsSettings(this@MainActivity, null)31                needToCheckPermission = true32            } else {33                securedVoiceCallSDK.requestContactPermission(this@MainActivity, true)34            }35        },36        onRequestNotificationPermission = {37            if (securedVoiceCallSDK.isPermissionDeniedTwice(securedVoiceCallSDK.NOTIFICATION_PERMISSION_DENIED)) {38                securedVoiceCallSDK.openAppPermissionsSettings(this@MainActivity, null)39                needToCheckPermission = true40            } else {41                securedVoiceCallSDK.requestNotificationPermission(this@MainActivity, true)42            }43        },44        onRequestLocationPermission = {45            if (!securedVoiceCallSDK.hasLocationPermission()) {46                if (securedVoiceCallSDK.isPermissionDeniedTwice(securedVoiceCallSDK.LOCATION_PERMISSION_DENIED)) {47                    securedVoiceCallSDK.openAppPermissionsSettings(this@MainActivity, null)48                    needToCheckPermission = true49                } else {50                    securedVoiceCallSDK.requestLocationPermission(this@MainActivity, true)51                }52            } else if (!securedVoiceCallSDK.hasLocationServiceEnabled()) {53                securedVoiceCallSDK.checkAndRequestLocationServices(54                    this@MainActivity,55                    null,56                    true57                )58                needToCheckPermission = true59            }60        }61    )62}
STEP 20

Add the PermissionState object

Copy the PermissionState singleton object into a Kotlin class to drive the permission sheet state.

PermissionState.ktkotlin
1object PermissionState {2    var showPermissionRequiredBottomSheet = mutableStateOf(false)3    var hasMicrophoneAndPhonePermission = mutableStateOf(false)4    var hasContactPermission = mutableStateOf(false)5    var hasNotificationPermission = mutableStateOf(false)6    val hasLocationPermission = mutableStateOf(false)7    val hasLocationServiceEnabled = mutableStateOf(false)8}
STEP 21

Check runtime permissions

We need 1. Microphone and Phone, 2. Contact, 3. Notification, and 4. Location permissions. Copy the following code to check these runtime permissions after a successful login.

Note:

Location permissions are only needed if your app requires location-based call redirection to a nearby center.

MainActivity.ktkotlin
1private val locationSettingsLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { _ ->2    if (securedVoiceCallSDK.isLocationServiceEnabled()) {3        lifecycleScope.launch { securedVoiceCallSDK.initializeSDKOnLaunch(this@MainActivity) }4    }5}6 7private val permissionsLauncher = registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions ->8    val deniedPermissions = permissions.filterValues { !it }.keys.toList()9    val allGranted = deniedPermissions.isEmpty()10    onSCPermissionsResult(allGranted, permissionRequestType)11}12 13private fun checkPermissions() {14    if (securedVoiceCallSDK.hasMicrophoneAndPhonePermission()) {15        if (securedVoiceCallSDK.hasContactPermission()) {16            if (securedVoiceCallSDK.hasNotificationPermission()) {17                if (securedVoiceCallSDK.hasLocationPermission()) {18                    if (!securedVoiceCallSDK.isLocationServiceEnabled()) {19                        securedVoiceCallSDK.checkAndRequestLocationServices(20                            this@MainActivity,21                            locationSettingsLauncher,22                            true23                        )24                    } else {25                        lifecycleScope.launch { securedVoiceCallSDK.initializeSDKOnLaunch(this@MainActivity) }26                    }27                } else {28                    permissionRequestType = securedVoiceCallSDK.PERMISSIONS_REQUEST_LOCATION29                    securedVoiceCallSDK.requestLocationPermission(permissionsLauncher)30                }31            } else {32                permissionRequestType = securedVoiceCallSDK.PERMISSIONS_REQUEST_POST_NOTIFICATIONS33                securedVoiceCallSDK.requestNotificationPermission(permissionsLauncher)34            }35        } else {36            permissionRequestType = securedVoiceCallSDK.PERMISSIONS_REQUEST_WRITE_CONTACTS37            securedVoiceCallSDK.requestContactPermission(permissionsLauncher)38        }39    } else {40        permissionRequestType = securedVoiceCallSDK.PERMISSIONS_REQUEST_MICROPHONE_PHONE41        securedVoiceCallSDK.requestMicrophoneAndPhonePermission(permissionsLauncher)42    }43}44 45private fun checkPermissionsToShowPermissionSheet() {46    if (securedVoiceCallSDK.isConsumerRegistered()) {47        if (securedVoiceCallSDK.areAllPermissionsGranted) {48            PermissionState.showPermissionRequiredBottomSheet.value = false49        } else {50            PermissionState.showPermissionRequiredBottomSheet.value = true51            PermissionState.hasMicrophoneAndPhonePermission.value = securedVoiceCallSDK.hasMicrophoneAndPhonePermission()52            PermissionState.hasContactPermission.value = securedVoiceCallSDK.hasContactPermission()53            PermissionState.hasNotificationPermission.value = securedVoiceCallSDK.hasNotificationPermission()54            PermissionState.hasLocationPermission.value = securedVoiceCallSDK.hasLocationPermission()55            PermissionState.hasLocationServiceEnabled.value = securedVoiceCallSDK.hasLocationServiceEnabled()56        }57    }58}
STEP 22

Handle permission callbacks

Copy this code into your Activity class to handle the permission result callbacks for each request type.

MainActivity.ktkotlin
1private fun onSCPermissionsResult(granted: Boolean, permissionRequestType: Int) {2 3    when (permissionRequestType) {4        securedVoiceCallSDK.PERMISSIONS_REQUEST_MICROPHONE_PHONE,5        securedVoiceCallSDK.PERMISSIONS_REQUEST_WRITE_CONTACTS,6        securedVoiceCallSDK.PERMISSIONS_REQUEST_POST_NOTIFICATIONS,7        securedVoiceCallSDK.PERMISSIONS_REQUEST_LOCATION -> {8            if (granted) {9                checkPermissions()10            }11            return12        }13 14        securedVoiceCallSDK.PERMISSIONS_REQUEST_MICROPHONE_PHONE_POPUP -> {15            if (granted) {16                checkPermissionsToShowPermissionSheet()17            } else {18                securedVoiceCallSDK.handlePermissionDenied(securedVoiceCallSDK.MICROPHONE_PERMISSION_DENIED)19            }20            return21        }22 23        securedVoiceCallSDK.PERMISSIONS_REQUEST_WRITE_CONTACTS_POPUP -> {24            if (granted) {25                checkPermissionsToShowPermissionSheet()26            } else {27                securedVoiceCallSDK.handlePermissionDenied(securedVoiceCallSDK.CONTACT_PERMISSION_DENIED)28            }29            return30        }31 32        securedVoiceCallSDK.PERMISSIONS_REQUEST_POST_NOTIFICATIONS_POPUP -> {33            if (granted) {34                checkPermissionsToShowPermissionSheet()35            } else {36                securedVoiceCallSDK.handlePermissionDenied(securedVoiceCallSDK.NOTIFICATION_PERMISSION_DENIED)37            }38            return39        }40 41        securedVoiceCallSDK.PERMISSIONS_REQUEST_LOCATION_POPUP -> {42            if (granted) {43                checkPermissionsToShowPermissionSheet()44            } else {45                securedVoiceCallSDK.handlePermissionDenied(securedVoiceCallSDK.LOCATION_PERMISSION_DENIED)46            }47            return48        }49    }50}
STEP 23

Show the sheet on every launch

Add the following code into the onResume() function of your Activity to show the permission sheet on every app launch.

MainActivity.ktkotlin
1if (needToCheckPermission) {2    checkPermissionsToShowPermissionSheet()3    needToCheckPermission = false4}
7

Section 7

Xiaomi (MIUI) call reliability

Xiaomi/Redmi/POCO devices running MIUI require two proprietary OS-level permissions for incoming call screens to appear when the device is locked or the app runs in the background.

These two MIUI toggles live at Settings → Apps → Manage apps → [Your App] → Other permissions. They have no standard Android API.

  • Show on Lock screen — allows the call screen activity to render above the MIUI keyguard.
  • Display pop-up windows while running in the background — allows the app to launch a call screen from the background, bypassing MIUI’s background activity launch restrictions.
Note:

This entire section applies only to Xiaomi/Redmi/POCO devices (MIUI). The call reliability prompt is automatically suppressed on all other manufacturers (OnePlus, Samsung, Vivo, Oppo, etc.) since they honour full-screen call intents through standard Android APIs.

STEP 24

Call the SDK methods

Use shouldPromptCallReliabilityPermissions() to decide whether to show the prompt, isCallReliabilityPermissionEnabled() to read the real status, and requestCallReliabilityPermissions(...) to show the native dialog that routes the user to the MIUI permission editor.

MainActivity.ktkotlin
1// Returns true only on Xiaomi MIUI devices.2// Use this to decide whether to show the permission prompt screen or Settings row.3val shouldShow: Boolean = securedVoiceCallSDK.shouldPromptCallReliabilityPermissions()4 5// Returns the best-effort real status of the MIUI background pop-up permission.6// Internally checked via AppOpsManager reflection (MIUI-specific op code 10021).7// Falls back to a persisted best-effort flag if the AppOps check is unavailable on8// the running MIUI build. Always returns false on non-Xiaomi devices.9val isEnabled: Boolean = securedVoiceCallSDK.isCallReliabilityPermissionEnabled()10 11// Shows a native dialog explaining the two required permissions and routes the user12// to the MIUI permission editor (falls back to standard app-settings page if the13// MIUI editor activity cannot be resolved on the running MIUI version).14//15// Parameters:16//   activity             — the foreground Activity to attach the dialog to.17//   settingsLauncher     — ActivityResultLauncher<Intent> registered in your Activity18//                          that fires when the user returns from OS Settings.19//   skipIfAlreadyEnabled — true (onboarding): skip dialog entirely if permission is20//                          already granted and call onDismissed directly.21//                          false (Settings page): always show dialog so the user can22//                          revisit and toggle the permissions off if desired.23//   onDismissed          — invoked when the user taps "Not now", cancels the dialog,24//                          or when skipIfAlreadyEnabled=true and already granted.25//                          Do NOT update Settings row state here if you want26//                          "Not now" to leave the toggle unchanged.27securedVoiceCallSDK.requestCallReliabilityPermissions(28    activity = this,29    settingsLauncher = callReliabilitySettingsLauncher,30    skipIfAlreadyEnabled = true,31) {32    // "Not now" tapped or already granted — handle navigation/no-op here33}
STEP 25

Register an ActivityResultLauncher

Register this as a class-level property in your Activity (before onCreate) so it can receive the result when the user returns from the MIUI Settings screen.

MainActivity.ktkotlin
1private val callReliabilitySettingsLauncher =2    registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { _ ->3        // User returned from MIUI Settings. Re-read the real permission state —4        // we cannot know what was toggled without checking the OS again.5        val isGranted = securedVoiceCallSDK.isCallReliabilityPermissionEnabled()6        // Update your UI state accordingly (e.g. toggle in ViewModel, navigate forward).7    }
STEP 26

Read the status after return

When the user returns from the MIUI Settings screen, call isCallReliabilityPermissionEnabled() to read the real (AppOps-based) permission state rather than assuming it was granted. Optionally persist the best-effort result with markCallReliabilityPermissionResult(...).

MainActivity.ktkotlin
1// In callReliabilitySettingsLauncher result callback:2val realState = securedVoiceCallSDK.isCallReliabilityPermissionEnabled()3 4// Optionally persist the best-effort result for the fallback path5// (used only when the AppOps reflection check is unavailable on a given MIUI build):6securedVoiceCallSDK.markCallReliabilityPermissionResult(realState)
8

Section 8

Place outbound calls

Make outbound callbacks for in-app calls, missed-call notifications, and PSTN calls.

STEP 27

Outbound in-app callback

To make an outbound callback to an in-app voice call customer care, make sure a new voice call session is created first. You can show a loader until the session is created and dismiss it on the onVoiceSessionSuccess() callback.

MainActivity.ktkotlin
1val intent = "" //Set Call Intent text2val setupId = "" //Set Call log setupId"3securedVoiceCallSDK.initializeSDKOnLaunch(object : SecuredVoiceCallBack {4    override fun onLoginSuccess() {}5    override fun onLoginError(message: String) {}6    override fun onVoiceSessionSuccess() {7        securedVoiceCallSDK.startOutBoundCall(this@MainActivity, callbackIdentifier, intent, setupId)8    }9 10    override fun onVoiceSessionError(message: String) {}11    override fun onCallStarted() {}12    override fun onCallFailed() {}13})
STEP 28

Outbound callback from a missed-call notification

The in-app outbound callback triggered from the missed-call notification’s Call back button passes a boolean flag to the launcher activity. Add this code to your launcher Activity.

Note:

If SHOW_MICROPHONE_PERMISSION_POPUP is true, show the microphone permission dialog to the user before proceeding with the call.

LauncherActivity.ktkotlin
1var showMicrophonePermissionPopup = false2if (intent.hasExtra(SecuredVoiceCallSDK.SHOW_MICROPHONE_PERMISSION_POPUP)) {3    showMicrophonePermissionPopup = intent.getBooleanExtra(SecuredVoiceCallSDK.SHOW_MICROPHONE_PERMISSION_POPUP, false)4}
STEP 29

Outbound PSTN callback

To make an outbound callback to a PSTN based call customer care, use makeCallWithPSTN.

MainActivity.ktkotlin
1MainScope().launch { securedVoiceCallSDK.makeCallWithPSTN(phoneNumberSelected, setupIdSelected, onCallStarted = {}) }
9

Section 9

History & re-initialize

Read call and branding history from the SDK, and re-initialize the SDK session on app launch.

STEP 30

Show call & branding history

Access call history and branding history from the SDK to show them in your app. Define a coroutine exception handler for SDK errors, then collect the callHistoryListFlow and brandingHistoryListFlow flows, which return lists of call and branding history data.

HistoryViewModel.ktkotlin
1viewModelScope.launch(Dispatchers.IO + exceptionHandler) {2    launch {3        securedVoiceCallSDK.callHistoryListFlow.collect { callList ->4            val callHistoryList = callList.map { it.toBaseCallData() }5        }6    }7 8    launch {9        securedVoiceCallSDK.brandingHistoryListFlow.collect { brandingList ->10            val brandingHistoryList = brandingList.map { it.toBaseCallData() }11        }12    }13}
STEP 31

Re-initialize the session

Re-initialize the SDK session on app launch by adding this code to your launcher activity class.

MainActivity.ktkotlin
1lifecycleScope.launch { securedVoiceCallSDK.initializeSDKOnLaunch(null) }
Declaration
securedVoiceCallSDK.initializeSDKOnLaunch(
  callBack: SecuredVoiceCallBack?,
  needToCallAuthToken: Boolean,
  needToUpdateAppConfig: Boolean,
  needToCreateSession: Boolean
)
callBackSecuredVoiceCallBack?

Optional. The SecuredVoiceCallBack interface to receive callbacks of session creation.

needToCallAuthTokenBoolean

Optional. Refreshes the auth token if needed.

needToUpdateAppConfigBoolean

Optional. Refreshes the SDK configs.

needToCreateSessionBoolean

Optional. Creates a new voice call session.

Firebase service account setup

Make sure FCM is enabled and the service account has the right roles.

Enable the FCM API

  1. Open the Google Cloud API library and search for Firebase Cloud Messaging API, then enable it.

Grant the service account notification permissions

So that your backend can send push notifications using the firebase-admin SDK:

  1. All permissions for service accounts must be set in the Google Cloud Console, not the Firebase console. Open the IAM admin console and make sure the same Firebase project is selected.
  2. Look for firebase-adminsdk-xxxxx@<project-id>.iam.gserviceaccount.com or your custom service account used by your backend.
  3. Click on it → Edit principal.
  4. Add roles such as Firebase Admin or Firebase Cloud Messaging Sender and Service Account Token Creator.

By following these steps, you’ll integrate the SecuredCalls Voice SDK effectively, meeting user privacy expectations and handling notifications efficiently.

Implementation time

A typical end-to-end integration takes about 38 minutes.

TaskDescriptionEstimated time
1. Add the SDK to your projectAdd the libraries in build.gradle and sync the project.3 min
2. Add Config.dat fileAdd the Config.dat file from the SecuredCalls portal into the assets folder.2 min
3. Add google-services.json fileAdd the google-services.json file to the app folder to enable FCM.2 min
4. SDK initializationInitialize the SDK in the Application class with the provided API key.4 min
5. User loginAdd login code defining the userIdentifier to receive incoming calls.3 min
6. Handle SecuredVoiceCallBack callbacksHandle callbacks for login and voice call session.2 min
7. Create FirebaseMessagingService classCreate the service and handle incoming Voice SDK push.3 min
8. Add permissions to AndroidManifest.xmlAdd permissions and the Firebase service to the manifest.3 min
9. Show permission sheet when deniedCheck runtime permissions and show the sheet after login.2 min
10. Handle permission callbacksHandle permission callbacks and create a new session.3 min
11. Xiaomi MIUI call reliability permissionsRegister the launcher and wire up the call-reliability prompt for MIUI.3 min
12. Make outbound callbackAdd code for in-app and PSTN outbound callbacks.3 min
13. Show call & branding historyAccess call and branding history from the SDK to show in the app.3 min
14. Re-initialize SDK session on launchRe-initialize the SDK session on app launch.2 min

Total: ~38 minutes