Developer guide - Background Handling, Notifications and Reminder Scheduling.

Sonde Health API Platform Documentation

Developer guide - Background Handling, Notifications and Reminder Scheduling.

Overview

This guide helps developers understand how the Sonde Passive SDK manages:

Background Voice Analysis

User Notifications

Reminder Scheduling

so you can integrate seamlessly into your existing Android app.


🚀 App Running in the Background

 

Background Handling Using Foreground Service

The Sonde Passive SDK uses a foreground service for background voice analysis while respecting user privacy via persistent notifications.

  • Voice analysis runs continuously in the background.

  • Users see real-time progress in the notification tray.

  • Scores can be generated even while the app is in background.


Required Permissions and Service Declaration

Add the following in your AndroidManifest.xml:

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.POST_NOTIFICATIONS" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE" /> <service android:name=".service.VoiceAnalysisService" android:enabled="true" android:foregroundServiceType="microphone" android:stopWithTask="true" />

 

Foreground Service Setup (VoiceAnalysisService.kt)

Create one kotlin class as VoiceAnalysisService.kt extends Service.

Binder for Fragment/Activity Communication

private val binder: IBinder = VoiceAnalysisBinder() inner class VoiceAnalysisBinder : Binder() { val service: VoiceAnalysisService = this@VoiceAnalysisService } override fun onBind(intent: Intent?): IBinder { return binder }

Starting Foreground Service with Notification

private fun createNotificationChannel() { val channel = NotificationChannel( CHANNEL_ID, CHANNEL_NAME, NotificationManager.IMPORTANCE_HIGH ).apply { description = CHANNEL_DESCRIPTION } notificationManager.createNotificationChannel(channel) } private fun startForegroundServiceWithNotification() { notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager createNotificationChannel() startForeground( NOTIFICATION_ID, createNotification().build() ) } private fun createNotification(): NotificationCompat.Builder { return NotificationCompat.Builder(this, CHANNEL_ID) .setChannelId(CHANNEL_ID) .setContentTitle(NOTIFICATION_TITLE) .setContentText(NOTIFICATION_CONTENT) .setOngoing(true) .setOnlyAlertOnce(true) .setSound(null) .setStyle(NotificationCompat.BigTextStyle()) .setSmallIcon(R.drawable.ic_mic_filled) }

Event Handling Using Sealed Interfaces

Create VoiceAnalysisServiceEvent:

sealed interface VoiceAnalysisServiceEvent { object StartService : VoiceAnalysisServiceEvent object StopService : VoiceAnalysisServiceEvent }

Handling Events:

suspend fun voiceAnalysisServiceEvent(event: VoiceAnalysisServiceEvent) { when (event) { VoiceAnalysisServiceEvent.StartService -> { // Start or restart voice analysis startForegroundServiceWithNotification() } VoiceAnalysisServiceEvent.StopService -> { // Stop voice analysis } } }
  • Based on the above event, we have to start or restart the service by calling startVoiceAnalysis & to stop the service call stopVoiceAnalysis.

  • To send updates back to fragment we have used the StateFlow.

  • Create on sealed interface as VoiceAnalysisServiceState in same package of VoiceAnalysisService.kt means parallel to service for send updates back from service to Fragment

    sealed interface VoiceAnalysisServiceState { object ServiceStarted : VoiceAnalysisServiceState object ServiceStopped : VoiceAnalysisServiceState object ServiceRestarted : VoiceAnalysisServiceState object SessionCompleted : VoiceAnalysisServiceState data class SegmentAnalysed(val voiceSegmentData: VoiceSegmentData) : VoiceAnalysisServiceState data class SessionAnalysed(val score: VoiceScore, val cfScore: VoiceScore?, val sessionNumber: Int) : VoiceAnalysisServiceState data class Error(val errorMsg: String) : VoiceAnalysisServiceState }
  • Add below code to declare state in Fragment,

    • private val _serviceState = MutableStateFlow<VoiceAnalysisServiceState?>(null) val serviceState: StateFlow<VoiceAnalysisServiceState?> = _serviceState
  • Fragment changes to update the UI and handle the service states

    • Refer the code in VerificationFragment.kt

    • Binding the service in onViewCreated

      • val intent = Intent(requireActivity(), VoiceAnalysisService::class.java) requireActivity().bindService( intent, voiceAnalysisServiceConnection, Context.BIND_AUTO_CREATE )
    • Create service connection in the fragment.

      • private var serviceBound = false private lateinit var voiceAnalysisService: VoiceAnalysisService
      • private var serviceBound = false private lateinit var voiceAnalysisService: VoiceAnalysisService private val voiceAnalysisServiceConnection = object : ServiceConnection { override fun onServiceConnected(p0: ComponentName?, service: IBinder?) { val binder = service as VoiceAnalysisService.VoiceAnalysisBinder voiceAnalysisService = binder.service serviceBound = true // Observe service state updates lifecycleScope.launch { voiceAnalysisService.serviceState.collectLatest { state -> // Update UI based on state } } } override fun onServiceDisconnected(p0: ComponentName?) { serviceBound = false } }
      • Once the service is connected, you can start communication with the service by sending the events.

      • Perform the required actions based on the state change of service which can be listened to using flow inside the onServiceConnected of the voiceAnalysisServiceConnection.

        • voiceAnalysisService.serviceState.collectLatest{state -> }

Destroy Service

override fun onDestroy() { stopContinuousVoiceAnalysis() super.onDestroy() } private fun stopContinuousVoiceAnalysis() { voiceAnalysisEngine.stopVoiceAnalysis() }

⏰ Reminder Scheduling

  • Refer the code in SettingFragment.kt

  • Take 4 parameter from user as maintained in following ReminderItem Data Class from UI/Fragment.

    • data class ReminderItem( val id: String, // Unique ID for each reminder val time: String, // e.g., "10:30" val hours: Int, val sessions: Int, val notificationMessage: String = "Your voice matters! Complete your check-in." )
  • Create one method as scheduleDailyNotifications to schedule notification on exact time. We are using AlarmManager here for exact time. You can use alternate for this is WorkManager for inexact time.

    • fun scheduleDailyNotifications( context: Context, reminderItem: ReminderItem ) { val timeParts = reminderItem.time.split(":") val hour = timeParts[0].toInt() val minute = timeParts[1].toInt() val calendar = Calendar.getInstance().apply { set(Calendar.HOUR_OF_DAY, hour) set(Calendar.MINUTE, minute) set(Calendar.SECOND, 0) set(Calendar.MILLISECOND, 0) if (before(Calendar.getInstance())) add(Calendar.DATE, 1) } val intent = Intent(context, DailyNotificationReceiver::class.java).apply { putExtra("title", "Sonde Mental Fitness:") putExtra("message", reminderItem.notificationMessage) putExtra("workId", reminderItem.id) putExtra("time", reminderItem.time) putExtra("hours", reminderItem.hours) putExtra("sessions", reminderItem.sessions) } val pendingIntent = PendingIntent.getBroadcast( context, reminderItem.id.hashCode(), intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE ) val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager try { alarmManager.setExactAndAllowWhileIdle( AlarmManager.RTC_WAKEUP, calendar.timeInMillis, pendingIntent ) } catch (exception: SecurityException) { Log.d("Notification", "Exception thrown: ${exception.message}") } Log.d("Notification", "${reminderItem.id}: AlarmManager task scheduled at ${calendar.time}") }
  • To receive notification on exact time create one BroadcastReceiver as DailyNotificationReceiver

    • class DailyNotificationReceiver : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { val title = intent.getStringExtra("title") ?: "Sonde Mental Fitness:" val message = intent.getStringExtra("message") ?: "Your voice matters! Complete your check-in." val workId = intent.getStringExtra("workId") ?: "daily_notification" val time = intent.getStringExtra("time") ?: "09:00" val hours = intent.getIntExtra("hours",4) val sessions = intent.getIntExtra("sessions",10) showNotification(context, title, message, workId) // 🔁 Reschedule for next day! val reminderItem=ReminderItem( id = workId, time = time, hours = hours, sessions = sessions, notificationMessage = message) scheduleDailyNotifications(context, reminderItem) } private fun showNotification(context: Context, title: String, message: String, workId: String) { val channelId = "multi_notification_channel" val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager val channel = NotificationChannel( channelId, "Multiple Daily Notifications", NotificationManager.IMPORTANCE_HIGH ) notificationManager.createNotificationChannel(channel) val args = Bundle().apply { putBoolean("fromNotification", true) putString("title", title) putString("message", message) putString("workId", workId) } val pendingIntent = NavDeepLinkBuilder(context) .setGraph(R.navigation.nav_graph) .setDestination(R.id.VerificationFragment) .setArguments(args) .createPendingIntent() val notification = NotificationCompat.Builder(context, channelId) .setContentTitle(title) .setContentText(message) .setSmallIcon(R.drawable.ic_mic_filled) .setContentIntent(pendingIntent) .setAutoCancel(true) .build() notificationManager.notify(System.currentTimeMillis().toInt(), notification) }
  • Declare this in your AndroidManifest.xml

    • <receiver android:name=".data.framework.DailyNotificationReceiver" android:exported="false" />
  • Once you have received notification on your device’s notification, you can handle notification click as per your requirement. Please refer VerificationFragment.kt fragment and onViewCreated method.

 

📝 Summary

Background Handling: Using a foreground service with notifications for privacy and persistent analysis.

Notification System: Enables users to track progress and receive reminders.

Reminder Scheduling: Uses AlarmManager for exact time, recurring daily notifications. You can use WorkManager for inexact notification timing.

 

Sonde Health