Improves stability and permission checks for voice calls when the app is in the background.
This commit is contained in:
@@ -26,6 +26,7 @@ import android.net.ConnectivityManager
|
|||||||
import android.net.Network
|
import android.net.Network
|
||||||
import android.net.NetworkCapabilities
|
import android.net.NetworkCapabilities
|
||||||
import android.net.NetworkRequest
|
import android.net.NetworkRequest
|
||||||
|
import androidx.compose.runtime.Stable
|
||||||
import com.vitorpamplona.amethyst.commons.call.AnswerRouteAction
|
import com.vitorpamplona.amethyst.commons.call.AnswerRouteAction
|
||||||
import com.vitorpamplona.amethyst.commons.call.CallManager
|
import com.vitorpamplona.amethyst.commons.call.CallManager
|
||||||
import com.vitorpamplona.amethyst.commons.call.CallState
|
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 TAG = "CallController"
|
||||||
private const val VIDEO_MAX_BITRATE_BPS_DEFAULT = 1_500_000
|
private const val VIDEO_MAX_BITRATE_BPS_DEFAULT = 1_500_000
|
||||||
|
|
||||||
|
@Stable
|
||||||
class CallController(
|
class CallController(
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
val callManager: CallManager,
|
val callManager: CallManager,
|
||||||
@@ -656,11 +658,13 @@ class CallController(
|
|||||||
try {
|
try {
|
||||||
val peerName = callManager.currentPeerPubKey() ?: ""
|
val peerName = callManager.currentPeerPubKey() ?: ""
|
||||||
val isVideo = mediaManager.isVideoEnabled.value
|
val isVideo = mediaManager.isVideoEnabled.value
|
||||||
|
val isRinging = callManager.state.value is CallState.IncomingCall || callManager.state.value is CallState.Offering
|
||||||
val intent =
|
val intent =
|
||||||
Intent(context, CallForegroundService::class.java).apply {
|
Intent(context, CallForegroundService::class.java).apply {
|
||||||
action = CallForegroundService.ACTION_START
|
action = CallForegroundService.ACTION_START
|
||||||
putExtra(CallForegroundService.EXTRA_PEER_NAME, peerName)
|
putExtra(CallForegroundService.EXTRA_PEER_NAME, peerName)
|
||||||
putExtra(CallForegroundService.EXTRA_IS_VIDEO, isVideo)
|
putExtra(CallForegroundService.EXTRA_IS_VIDEO, isVideo)
|
||||||
|
putExtra(CallForegroundService.EXTRA_IS_RINGING, isRinging)
|
||||||
}
|
}
|
||||||
context.startForegroundService(intent)
|
context.startForegroundService(intent)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
@@ -672,10 +676,14 @@ class CallController(
|
|||||||
if (!foregroundServiceStarted) return
|
if (!foregroundServiceStarted) return
|
||||||
try {
|
try {
|
||||||
val peerName = callManager.currentPeerPubKey() ?: ""
|
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 =
|
val intent =
|
||||||
Intent(context, CallForegroundService::class.java).apply {
|
Intent(context, CallForegroundService::class.java).apply {
|
||||||
action = CallForegroundService.ACTION_UPDATE
|
action = CallForegroundService.ACTION_UPDATE
|
||||||
putExtra(CallForegroundService.EXTRA_PEER_NAME, peerName)
|
putExtra(CallForegroundService.EXTRA_PEER_NAME, peerName)
|
||||||
|
putExtra(CallForegroundService.EXTRA_IS_VIDEO, isVideo)
|
||||||
|
putExtra(CallForegroundService.EXTRA_IS_RINGING, isRinging)
|
||||||
}
|
}
|
||||||
context.startService(intent)
|
context.startService(intent)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
|||||||
+21
-21
@@ -50,6 +50,7 @@ class CallForegroundService : Service() {
|
|||||||
const val EXTRA_PEER_NAME = "peer_name"
|
const val EXTRA_PEER_NAME = "peer_name"
|
||||||
const val EXTRA_IS_VIDEO = "is_video"
|
const val EXTRA_IS_VIDEO = "is_video"
|
||||||
const val EXTRA_STATUS_TEXT = "status_text"
|
const val EXTRA_STATUS_TEXT = "status_text"
|
||||||
|
const val EXTRA_IS_RINGING = "is_ringing"
|
||||||
private const val HANGUP_REQUEST_CODE = 0x70001
|
private const val HANGUP_REQUEST_CODE = 0x70001
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,26 +67,31 @@ class CallForegroundService : Service() {
|
|||||||
startId: Int,
|
startId: Int,
|
||||||
): Int {
|
): Int {
|
||||||
when (intent?.action) {
|
when (intent?.action) {
|
||||||
ACTION_START -> {
|
ACTION_START, ACTION_UPDATE -> {
|
||||||
val peerName = intent.getStringExtra(EXTRA_PEER_NAME) ?: "Unknown"
|
val peerName = intent.getStringExtra(EXTRA_PEER_NAME) ?: "Unknown"
|
||||||
val isVideo = intent.getBooleanExtra(EXTRA_IS_VIDEO, false)
|
val isVideo = intent.getBooleanExtra(EXTRA_IS_VIDEO, false)
|
||||||
|
val isRinging = intent.getBooleanExtra(EXTRA_IS_RINGING, false)
|
||||||
val statusText = intent.getStringExtra(EXTRA_STATUS_TEXT)
|
val statusText = intent.getStringExtra(EXTRA_STATUS_TEXT)
|
||||||
val notification = buildNotification(peerName, statusText)
|
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 {
|
try {
|
||||||
val fgsType =
|
val fgsType =
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
||||||
var type = ServiceInfo.FOREGROUND_SERVICE_TYPE_PHONE_CALL
|
var type = ServiceInfo.FOREGROUND_SERVICE_TYPE_PHONE_CALL
|
||||||
if (hasAudioPermission) {
|
if (!isRinging) {
|
||||||
type = type or ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE
|
val hasAudioPermission =
|
||||||
}
|
ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) ==
|
||||||
if (isVideo && hasCameraPermission) {
|
PackageManager.PERMISSION_GRANTED
|
||||||
type = type or ServiceInfo.FOREGROUND_SERVICE_TYPE_CAMERA
|
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
|
type
|
||||||
} else {
|
} else {
|
||||||
@@ -104,19 +110,13 @@ class CallForegroundService : Service() {
|
|||||||
ServiceCompat.startForeground(this, NOTIFICATION_ID, notification, fallbackType)
|
ServiceCompat.startForeground(this, NOTIFICATION_ID, notification, fallbackType)
|
||||||
} catch (e2: Exception) {
|
} catch (e2: Exception) {
|
||||||
Log.e(TAG, "Foreground service start failed entirely", e2)
|
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 -> {
|
ACTION_STOP -> {
|
||||||
stopForeground(STOP_FOREGROUND_REMOVE)
|
stopForeground(STOP_FOREGROUND_REMOVE)
|
||||||
stopSelf()
|
stopSelf()
|
||||||
|
|||||||
@@ -21,19 +21,24 @@
|
|||||||
package com.vitorpamplona.amethyst.commons.call
|
package com.vitorpamplona.amethyst.commons.call
|
||||||
|
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
|
import androidx.compose.runtime.Stable
|
||||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||||
import com.vitorpamplona.quartz.nipACWebRtcCalls.tags.CallType
|
import com.vitorpamplona.quartz.nipACWebRtcCalls.tags.CallType
|
||||||
|
|
||||||
|
@Stable
|
||||||
@Immutable
|
@Immutable
|
||||||
sealed interface CallState {
|
sealed interface CallState {
|
||||||
|
@Stable
|
||||||
data object Idle : CallState
|
data object Idle : CallState
|
||||||
|
|
||||||
|
@Stable
|
||||||
data class Offering(
|
data class Offering(
|
||||||
val callId: String,
|
val callId: String,
|
||||||
val peerPubKeys: Set<HexKey>,
|
val peerPubKeys: Set<HexKey>,
|
||||||
val callType: CallType,
|
val callType: CallType,
|
||||||
) : CallState
|
) : CallState
|
||||||
|
|
||||||
|
@Stable
|
||||||
data class IncomingCall(
|
data class IncomingCall(
|
||||||
val callId: String,
|
val callId: String,
|
||||||
val callerPubKey: HexKey,
|
val callerPubKey: HexKey,
|
||||||
@@ -42,6 +47,7 @@ sealed interface CallState {
|
|||||||
val sdpOffer: String,
|
val sdpOffer: String,
|
||||||
) : CallState
|
) : CallState
|
||||||
|
|
||||||
|
@Stable
|
||||||
data class Connecting(
|
data class Connecting(
|
||||||
val callId: String,
|
val callId: String,
|
||||||
val peerPubKeys: Set<HexKey>,
|
val peerPubKeys: Set<HexKey>,
|
||||||
@@ -49,6 +55,7 @@ sealed interface CallState {
|
|||||||
val pendingPeerPubKeys: Set<HexKey> = emptySet(),
|
val pendingPeerPubKeys: Set<HexKey> = emptySet(),
|
||||||
) : CallState
|
) : CallState
|
||||||
|
|
||||||
|
@Stable
|
||||||
data class Connected(
|
data class Connected(
|
||||||
val callId: String,
|
val callId: String,
|
||||||
val peerPubKeys: Set<HexKey>,
|
val peerPubKeys: Set<HexKey>,
|
||||||
@@ -60,6 +67,7 @@ sealed interface CallState {
|
|||||||
val allPeerPubKeys: Set<HexKey> get() = peerPubKeys + pendingPeerPubKeys
|
val allPeerPubKeys: Set<HexKey> get() = peerPubKeys + pendingPeerPubKeys
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Stable
|
||||||
data class Ended(
|
data class Ended(
|
||||||
val callId: String,
|
val callId: String,
|
||||||
val peerPubKeys: Set<HexKey>,
|
val peerPubKeys: Set<HexKey>,
|
||||||
|
|||||||
Reference in New Issue
Block a user