fix: start foreground service earlier to prevent call death on Android 14+
The foreground service was only started after onPeerConnected, leaving the Offering/Connecting phases unprotected. If the user backgrounded the app during connecting, Android 14+ could block the later startForegroundService() call, killing the call. Changes: - Start foreground service on Offering state (user just tapped call button, so app is guaranteed to be in foreground) - Update notification text on Connecting/Connected transitions - Add ACTION_UPDATE to CallForegroundService to change notification without restarting the service - onPeerConnected now uses ensureForegroundService() as a safety net https://claude.ai/code/session_01F5RF2yzngiMr1v2gr7f1GP
This commit is contained in:
@@ -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 =
|
||||
|
||||
+19
-3
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user