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
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.
Wire up the version catalog and Gradle dependencies.
Add Config.dat and google-services.json files.
Boot SecuredVoiceCallSDK in your Application class.
Log the user in and handle session callbacks.
Receive incoming call pushes in a service.
Request microphone, contacts, notifications, and location.
Extra call-reliability permissions for Xiaomi/Redmi/POCO devices.
In-app, missed-call, and PSTN outbound callbacks.
Read call/branding history and re-initialize the session.
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.datfile and client secret
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.
Declare versions
Open your project libs.versions.toml file and add the library and plugin versions under the [versions] table.
1firebaseBom = "33.14.0"2gms = "4.4.2"3kotlin = "2.0.21"4scVoice = "1.0.52"Declare libraries
Add the Firebase and SecuredCalls library entries under the [libraries] table.
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" }Declare plugins
Add the Google Services and Kotlin Compose plugin entries under the [plugins] table.
1gms = { id = "com.google.gms.google-services", version.ref = "gms" }2kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }Apply plugins & dependencies
Open your app level build.gradle file and apply the plugins and add the dependencies.
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}Apply project-level plugins
Open your project level build.gradle file and add the plugins with apply false.
1plugins {2 alias(libs.plugins.gms) apply false3 alias(libs.plugins.kotlin.compose) apply false4}Add ProGuard rules
Add the following keep rules so the SDK’s native and Vonage classes survive minification.
1-keep class com.vonage.** { *; }2-keep class org.webrtc.** { *; }3-keep class org.vonage.** { *; }Section 2
Add Config.dat and Firebase
Drop in the configuration file from the SecuredCalls portal and connect your Firebase project.
Add the Config.dat file
Add the Config.dat file downloaded from the SecuredCalls portal into your app’s assets folder.
- Go to your Android Studio project target.
- Select the File tab.
- 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.
- An assets folder is created at the path app/src/main/assets.
- Paste the downloaded
Config.datfile into the assets folder.
Add the google-services.json file
Enable Firebase Cloud Messaging for your app and add the generated config file.
- Create your app’s Google Firebase project with the same package name you provided while registering the app with the SecuredCalls portal.
- Enable Firebase Cloud Messaging API in the Google Cloud developer console for the registered app.
- Go to Project settings, select the General tab and scroll down. You can see your app with the google-services.json file to download.
- Paste the downloaded google-services.json file into the project’s app folder.
Section 3
Initialize the SDK
Initialize SecuredVoiceCallSDK in your project’s Application class, then register it in the manifest.
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.
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.
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}securedVoiceCallSDK.initialize( clientSecret: String, configFileName: String, scSDKSettingsModel: ScSDKSettingsModel, appRedirectionIcon: Int, typography: ScSDKTypography, autoDismissNotification: Boolean, biometricMetadata: BiometricMetadata, showDataChannelConnectionStatus: Boolean )
Mandatory. Replace xxxxxxxSECRETxxxxxxx with your actual API key.
Optional. The config file name you pasted into the assets folder.
Optional. SDK settings such as handlePermission, showPipView,
showDataChannelConnectionDetails, and logLevel.
Optional. App redirection icon shown on the voice call screen, from a drawable resource (PNG or Vector XML) — not the mipmap launcher icon.
Optional. Typography object to customize the SDK UI fonts.
Optional. Whether to auto-dismiss the branding notification. Default
false.
Optional. Metadata sent alongside a successful biometric verification.
Optional. Whether to show the data channel connection status. Default
false.
Register the Application class
Add your application class name (e.g. SCVoiceCallApp) and allowBackup="false" to the application tag of your AndroidManifest.xml file.
1android:name=".SCVoiceCallApp"2android:allowBackup="false"Section 4
Log in and handle callbacks
Declare the SDK instance, log the user in, and implement the callback interface.
Declare the SDK & identifiers
Declare the SDK instance and identifiers at Activity level.
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.
1private lateinit var securedVoiceCallSDK: SecuredVoiceCallSDK2private val userIdentifier = "userIdentifier"3private val callbackIdentifier = "callbackIdentifier"4private var needToCheckPermission = false5private var permissionRequestType = 0Initialize in onCreate()
Initialize securedVoiceCallSDK and the needToCheckPermission variable inside the Activity’s onCreate() function.
1securedVoiceCallSDK = SCVoiceCallApp.instance.securedVoiceCallSDK2if (securedVoiceCallSDK.isConsumerRegistered()) {3 needToCheckPermission = true4}Log the user in
Provide the userIdentifier and a SecuredVoiceCallBack implementation to handle login and voice call session success/error callbacks.
Call Task 12 of Re-initialize SDK session on app launch inside the
onLoginSuccess() callback to initialize the SDK session once after login.
1securedVoiceCallSDK.setSecuredCallBack(this)2securedVoiceCallSDK.login(userIdentifier)Log the user out
Call the logout function to end the user session. On completion you receive an onLogoutSuccess() callback.
1securedVoiceCallSDK.logout(onLogoutSuccess = {})Implement SecuredVoiceCallBack
Implement the SecuredVoiceCallBack interface at Activity level (e.g. MainActivity.kt) to receive login and voice call session success/error callbacks.
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}Section 5
Handle incoming pushes
Create a FirebaseMessagingService to receive push messages for voice and PSTN call branding and initiation.
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.
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}Add permissions to the manifest
Add the following permissions and feature declaration into your AndroidManifest.xml file.
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"/>Register the service
Add the FirebaseMessagingService class (e.g. ScFirebaseMessagingService.kt) into your AndroidManifest.xml file.
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>Section 6
Request and handle permissions
Show a permission sheet when permissions are denied, and handle the runtime permission callbacks.
Show the permission sheet
Copy the following code into your composable view to show the permission sheet at runtime when permissions are denied.
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}Add the PermissionState object
Copy the PermissionState singleton object into a Kotlin class to drive the permission sheet state.
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}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.
Location permissions are only needed if your app requires location-based call redirection to a nearby center.
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}Handle permission callbacks
Copy this code into your Activity class to handle the permission result callbacks for each request type.
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}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.
1if (needToCheckPermission) {2 checkPermissionsToShowPermissionSheet()3 needToCheckPermission = false4}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.
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.
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.
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}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.
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 }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(...).
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)Section 8
Place outbound calls
Make outbound callbacks for in-app calls, missed-call notifications, and PSTN calls.
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.
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})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.
If SHOW_MICROPHONE_PERMISSION_POPUP is true, show the microphone permission
dialog to the user before proceeding with the call.
1var showMicrophonePermissionPopup = false2if (intent.hasExtra(SecuredVoiceCallSDK.SHOW_MICROPHONE_PERMISSION_POPUP)) {3 showMicrophonePermissionPopup = intent.getBooleanExtra(SecuredVoiceCallSDK.SHOW_MICROPHONE_PERMISSION_POPUP, false)4}Outbound PSTN callback
To make an outbound callback to a PSTN based call customer care, use makeCallWithPSTN.
1MainScope().launch { securedVoiceCallSDK.makeCallWithPSTN(phoneNumberSelected, setupIdSelected, onCallStarted = {}) }Section 9
History & re-initialize
Read call and branding history from the SDK, and re-initialize the SDK session on app launch.
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.
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}Re-initialize the session
Re-initialize the SDK session on app launch by adding this code to your launcher activity class.
1lifecycleScope.launch { securedVoiceCallSDK.initializeSDKOnLaunch(null) }securedVoiceCallSDK.initializeSDKOnLaunch( callBack: SecuredVoiceCallBack?, needToCallAuthToken: Boolean, needToUpdateAppConfig: Boolean, needToCreateSession: Boolean )
Optional. The SecuredVoiceCallBack interface to receive callbacks of
session creation.
Optional. Refreshes the auth token if needed.
Optional. Refreshes the SDK configs.
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
- 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:
- 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.
- Look for
firebase-adminsdk-xxxxx@<project-id>.iam.gserviceaccount.comor your custom service account used by your backend. - Click on it → Edit principal.
- 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.
| Task | Description | Estimated time |
|---|---|---|
| 1. Add the SDK to your project | Add the libraries in build.gradle and sync the project. | 3 min |
| 2. Add Config.dat file | Add the Config.dat file from the SecuredCalls portal into the assets folder. | 2 min |
| 3. Add google-services.json file | Add the google-services.json file to the app folder to enable FCM. | 2 min |
| 4. SDK initialization | Initialize the SDK in the Application class with the provided API key. | 4 min |
| 5. User login | Add login code defining the userIdentifier to receive incoming calls. | 3 min |
| 6. Handle SecuredVoiceCallBack callbacks | Handle callbacks for login and voice call session. | 2 min |
| 7. Create FirebaseMessagingService class | Create the service and handle incoming Voice SDK push. | 3 min |
| 8. Add permissions to AndroidManifest.xml | Add permissions and the Firebase service to the manifest. | 3 min |
| 9. Show permission sheet when denied | Check runtime permissions and show the sheet after login. | 2 min |
| 10. Handle permission callbacks | Handle permission callbacks and create a new session. | 3 min |
| 11. Xiaomi MIUI call reliability permissions | Register the launcher and wire up the call-reliability prompt for MIUI. | 3 min |
| 12. Make outbound callback | Add code for in-app and PSTN outbound callbacks. | 3 min |
| 13. Show call & branding history | Access call and branding history from the SDK to show in the app. | 3 min |
| 14. Re-initialize SDK session on launch | Re-initialize the SDK session on app launch. | 2 min |
Total: ~38 minutes