Merge pull request #2206 from vitorpamplona/claude/review-webrtc-calls-0k3G4

fix: start foreground service earlier to prevent call death on Androi…
This commit is contained in:
Vitor Pamplona
2026-04-10 17:44:11 -04:00
committed by GitHub
2 changed files with 44 additions and 7 deletions
@@ -110,6 +110,7 @@ class CallController(
is CallState.Offering -> {
audioManager.startRingbackTone()
ensureForegroundService()
}
is CallState.Connecting -> {
@@ -118,6 +119,7 @@ class CallController(
withContext(Dispatchers.IO) { audioManager.switchToCallAudioMode() }
audioManager.acquireProximityWakeLock()
NotificationUtils.cancelCallNotification(context)
updateForegroundServiceNotification()
}
is CallState.Connected -> {
@@ -126,6 +128,7 @@ class CallController(
withContext(Dispatchers.IO) { audioManager.switchToCallAudioMode() }
audioManager.acquireProximityWakeLock()
NotificationUtils.cancelCallNotification(context)
updateForegroundServiceNotification()
}
is CallState.Ended -> {
@@ -444,10 +447,7 @@ class CallController(
Log.d(TAG) { "Peer ${peerPubKey.take(8)} connected!" }
scope.launch {
callManager.onPeerConnected()
if (!foregroundServiceStarted) {
foregroundServiceStarted = true
startForegroundService()
}
ensureForegroundService()
}
},
onRemoteVideoTrack = { track -> videoMonitor.onRemoteVideoTrack(peerPubKey, track) },
@@ -526,6 +526,12 @@ class CallController(
// ---- Foreground service ----
private fun ensureForegroundService() {
if (foregroundServiceStarted) return
foregroundServiceStarted = true
startForegroundService()
}
private fun startForegroundService() {
try {
val peerName = callManager.currentPeerPubKey() ?: ""
@@ -542,6 +548,21 @@ class CallController(
}
}
private fun updateForegroundServiceNotification() {
if (!foregroundServiceStarted) return
try {
val peerName = callManager.currentPeerPubKey() ?: ""
val intent =
Intent(context, CallForegroundService::class.java).apply {
action = CallForegroundService.ACTION_UPDATE
putExtra(CallForegroundService.EXTRA_PEER_NAME, peerName)
}
context.startService(intent)
} catch (e: Exception) {
Log.e(TAG, "Failed to update foreground service notification", e)
}
}
private fun stopForegroundService() {
try {
val intent =
@@ -46,8 +46,10 @@ class CallForegroundService : Service() {
const val NOTIFICATION_ID = 9001
const val ACTION_START = "com.vitorpamplona.amethyst.CALL_START"
const val ACTION_STOP = "com.vitorpamplona.amethyst.CALL_STOP"
const val ACTION_UPDATE = "com.vitorpamplona.amethyst.CALL_UPDATE"
const val EXTRA_PEER_NAME = "peer_name"
const val EXTRA_IS_VIDEO = "is_video"
const val EXTRA_STATUS_TEXT = "status_text"
private const val HANGUP_REQUEST_CODE = 0x70001
}
@@ -67,7 +69,8 @@ class CallForegroundService : Service() {
ACTION_START -> {
val peerName = intent.getStringExtra(EXTRA_PEER_NAME) ?: "Unknown"
val isVideo = intent.getBooleanExtra(EXTRA_IS_VIDEO, false)
val notification = buildNotification(peerName)
val statusText = intent.getStringExtra(EXTRA_STATUS_TEXT)
val notification = buildNotification(peerName, statusText)
val hasAudioPermission =
ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) ==
PackageManager.PERMISSION_GRANTED
@@ -100,6 +103,14 @@ class CallForegroundService : Service() {
}
}
ACTION_UPDATE -> {
val peerName = intent.getStringExtra(EXTRA_PEER_NAME) ?: "Unknown"
val statusText = intent.getStringExtra(EXTRA_STATUS_TEXT)
val notification = buildNotification(peerName, statusText)
val notificationManager = getSystemService(NotificationManager::class.java)
notificationManager.notify(NOTIFICATION_ID, notification)
}
ACTION_STOP -> {
stopForeground(STOP_FOREGROUND_REMOVE)
stopSelf()
@@ -121,7 +132,10 @@ class CallForegroundService : Service() {
notificationManager.createNotificationChannel(channel)
}
private fun buildNotification(peerName: String): Notification {
private fun buildNotification(
peerName: String,
statusText: String? = null,
): Notification {
val openCallIntent =
PendingIntent.getActivity(
this,
@@ -142,10 +156,12 @@ class CallForegroundService : Service() {
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT,
)
val contentText = statusText ?: getString(R.string.call_with, peerName)
return NotificationCompat
.Builder(this, CHANNEL_ID)
.setContentTitle(getString(R.string.app_name))
.setContentText(getString(R.string.call_with, peerName))
.setContentText(contentText)
.setSmallIcon(R.drawable.amethyst)
.setOngoing(true)
.setContentIntent(openCallIntent)