diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/call/CallController.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/call/CallController.kt index 17c2db79b..6132fed28 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/call/CallController.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/call/CallController.kt @@ -26,6 +26,7 @@ import android.net.ConnectivityManager import android.net.Network import android.net.NetworkCapabilities import android.net.NetworkRequest +import androidx.compose.runtime.Stable import com.vitorpamplona.amethyst.commons.call.AnswerRouteAction import com.vitorpamplona.amethyst.commons.call.CallManager import com.vitorpamplona.amethyst.commons.call.CallState @@ -59,6 +60,7 @@ import java.util.concurrent.atomic.AtomicBoolean private const val TAG = "CallController" private const val VIDEO_MAX_BITRATE_BPS_DEFAULT = 1_500_000 +@Stable class CallController( private val context: Context, val callManager: CallManager, @@ -656,11 +658,13 @@ class CallController( try { val peerName = callManager.currentPeerPubKey() ?: "" val isVideo = mediaManager.isVideoEnabled.value + val isRinging = callManager.state.value is CallState.IncomingCall || callManager.state.value is CallState.Offering val intent = Intent(context, CallForegroundService::class.java).apply { action = CallForegroundService.ACTION_START putExtra(CallForegroundService.EXTRA_PEER_NAME, peerName) putExtra(CallForegroundService.EXTRA_IS_VIDEO, isVideo) + putExtra(CallForegroundService.EXTRA_IS_RINGING, isRinging) } context.startForegroundService(intent) } catch (e: Exception) { @@ -672,10 +676,14 @@ class CallController( if (!foregroundServiceStarted) return try { val peerName = callManager.currentPeerPubKey() ?: "" + val isVideo = mediaManager.isVideoEnabled.value + val isRinging = callManager.state.value is CallState.IncomingCall || callManager.state.value is CallState.Offering val intent = Intent(context, CallForegroundService::class.java).apply { action = CallForegroundService.ACTION_UPDATE putExtra(CallForegroundService.EXTRA_PEER_NAME, peerName) + putExtra(CallForegroundService.EXTRA_IS_VIDEO, isVideo) + putExtra(CallForegroundService.EXTRA_IS_RINGING, isRinging) } context.startService(intent) } catch (e: Exception) { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/call/CallForegroundService.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/call/CallForegroundService.kt index 69338b593..a4b85c0c9 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/call/CallForegroundService.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/call/CallForegroundService.kt @@ -50,6 +50,7 @@ class CallForegroundService : Service() { const val EXTRA_PEER_NAME = "peer_name" const val EXTRA_IS_VIDEO = "is_video" const val EXTRA_STATUS_TEXT = "status_text" + const val EXTRA_IS_RINGING = "is_ringing" private const val HANGUP_REQUEST_CODE = 0x70001 } @@ -66,26 +67,31 @@ class CallForegroundService : Service() { startId: Int, ): Int { when (intent?.action) { - ACTION_START -> { + ACTION_START, ACTION_UPDATE -> { val peerName = intent.getStringExtra(EXTRA_PEER_NAME) ?: "Unknown" val isVideo = intent.getBooleanExtra(EXTRA_IS_VIDEO, false) + val isRinging = intent.getBooleanExtra(EXTRA_IS_RINGING, false) val statusText = intent.getStringExtra(EXTRA_STATUS_TEXT) val notification = buildNotification(peerName, statusText) - val hasAudioPermission = - ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) == - PackageManager.PERMISSION_GRANTED - val hasCameraPermission = - ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) == - PackageManager.PERMISSION_GRANTED + try { val fgsType = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { var type = ServiceInfo.FOREGROUND_SERVICE_TYPE_PHONE_CALL - if (hasAudioPermission) { - type = type or ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE - } - if (isVideo && hasCameraPermission) { - type = type or ServiceInfo.FOREGROUND_SERVICE_TYPE_CAMERA + if (!isRinging) { + val hasAudioPermission = + ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) == + PackageManager.PERMISSION_GRANTED + val hasCameraPermission = + ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) == + PackageManager.PERMISSION_GRANTED + + if (hasAudioPermission) { + type = type or ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE + } + if (isVideo && hasCameraPermission) { + type = type or ServiceInfo.FOREGROUND_SERVICE_TYPE_CAMERA + } } type } else { @@ -104,19 +110,13 @@ class CallForegroundService : Service() { ServiceCompat.startForeground(this, NOTIFICATION_ID, notification, fallbackType) } catch (e2: Exception) { Log.e(TAG, "Foreground service start failed entirely", e2) - stopSelf() + if (intent.action == ACTION_START) { + stopSelf() + } } } } - 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() diff --git a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/call/CallState.kt b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/call/CallState.kt index 001ebcd87..09746ceb6 100644 --- a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/call/CallState.kt +++ b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/call/CallState.kt @@ -21,19 +21,24 @@ package com.vitorpamplona.amethyst.commons.call import androidx.compose.runtime.Immutable +import androidx.compose.runtime.Stable import com.vitorpamplona.quartz.nip01Core.core.HexKey import com.vitorpamplona.quartz.nipACWebRtcCalls.tags.CallType +@Stable @Immutable sealed interface CallState { + @Stable data object Idle : CallState + @Stable data class Offering( val callId: String, val peerPubKeys: Set, val callType: CallType, ) : CallState + @Stable data class IncomingCall( val callId: String, val callerPubKey: HexKey, @@ -42,6 +47,7 @@ sealed interface CallState { val sdpOffer: String, ) : CallState + @Stable data class Connecting( val callId: String, val peerPubKeys: Set, @@ -49,6 +55,7 @@ sealed interface CallState { val pendingPeerPubKeys: Set = emptySet(), ) : CallState + @Stable data class Connected( val callId: String, val peerPubKeys: Set, @@ -60,6 +67,7 @@ sealed interface CallState { val allPeerPubKeys: Set get() = peerPubKeys + pendingPeerPubKeys } + @Stable data class Ended( val callId: String, val peerPubKeys: Set,