diff --git a/app/src/main/kotlin/com/tutpro/baresip/BaresipService.kt b/app/src/main/kotlin/com/tutpro/baresip/BaresipService.kt index 135daf1d..00897ba1 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/BaresipService.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/BaresipService.kt @@ -167,8 +167,10 @@ class BaresipService: Service() { override fun onAvailable(network: Network) { super.onAvailable(network) Log.d(TAG, "Network $network is available") - if (network !in allNetworks) - allNetworks.add(network) + synchronized(allNetworks) { + if (network !in allNetworks) + allNetworks.add(network) + } } override fun onLosing(network: Network, maxMsToLive: Int) { @@ -179,16 +181,20 @@ class BaresipService: Service() { override fun onLost(network: Network) { super.onLost(network) Log.d(TAG, "Network $network is lost") - if (network in allNetworks) - allNetworks.remove(network) + synchronized(allNetworks) { + if (network in allNetworks) + allNetworks.remove(network) + } if (isServiceRunning) updateNetwork() } override fun onCapabilitiesChanged(network: Network, caps: NetworkCapabilities) { super.onCapabilitiesChanged(network, caps) - if (network !in allNetworks) - allNetworks.add(network) + synchronized(allNetworks) { + if (network !in allNetworks) + allNetworks.add(network) + } if (isServiceRunning) updateNetwork() } @@ -196,8 +202,10 @@ class BaresipService: Service() { override fun onLinkPropertiesChanged(network: Network, props: LinkProperties) { super.onLinkPropertiesChanged(network, props) Log.d(TAG, "Network $network link properties changed: $props") - if (network !in allNetworks) - allNetworks.add(network) + synchronized(allNetworks) { + if (network !in allNetworks) + allNetworks.add(network) + } if (isServiceRunning) updateNetwork() } @@ -740,6 +748,7 @@ class BaresipService: Service() { if (uas.value[accountIndex].account.aor == aor) { when (ev[0]) { "registering", "unregistering" -> { + updatePartialWakeLock(brief = true) ua.updateStatus(circleYellow.getValue(colorblind)) updateStatusNotification() if (isMainVisible) @@ -747,6 +756,7 @@ class BaresipService: Service() { return } "registered" -> { + updatePartialWakeLock(brief = true) ua.updateStatus( if (Api.account_regint(ua.account.accp) == 0) R.drawable.circle_white @@ -759,6 +769,7 @@ class BaresipService: Service() { return } "registering failed" -> { + updatePartialWakeLock(brief = true) ua.updateStatus(if (Api.account_regint(ua.account.accp) == 0) R.drawable.circle_white else @@ -1408,6 +1419,7 @@ class BaresipService: Service() { @Keep fun started() { Log.d(TAG, "Received 'started' from baresip") + isNativeReady = true Api.net_debug() postServiceEvent(ServiceEvent("started", arrayListOf(callActionUri), System.nanoTime())) callActionUri = "" @@ -1422,6 +1434,7 @@ class BaresipService: Service() { @Keep fun stopped(error: String) { Log.d(TAG, "Received 'stopped' from baresip with start error '$error'") + isNativeReady = false quitTimer.cancel() cleanService() isServiceRunning = false @@ -1629,18 +1642,23 @@ class BaresipService: Service() { } @SuppressLint("WakelockTimeout") - private fun updatePartialWakeLock() { - val isAnyUaActive = uasStatus.value.values.any { it != R.drawable.circle_white } - val needsToStayAwake = isAnyUaActive || calls.isNotEmpty() + private fun updatePartialWakeLock(brief: Boolean = false) { + // Only hold a permanent wake lock during active calls. + // For registration standby, rely on the foreground service and kernel socket wakes. + // Also hold it briefly for registration events or network transitions. + val needsToStayAwake = calls.isNotEmpty() try { if (needsToStayAwake) { if (!partialWakeLock.isHeld) { - Log.d(TAG, "Acquiring Partial Wake Lock (UA Active or Call in progress)") + Log.d(TAG, "Acquiring Partial Wake Lock (Call in progress)") partialWakeLock.acquire() } + } else if (brief) { + Log.d(TAG, "Acquiring Brief Partial Wake Lock (2s)") + partialWakeLock.acquire(2000L) } else { if (partialWakeLock.isHeld) { - Log.d(TAG, "Releasing Partial Wake Lock (All UAs idle and no calls)") + Log.d(TAG, "Releasing Partial Wake Lock (No calls in progress)") partialWakeLock.release() } } @@ -1943,6 +1961,9 @@ class BaresipService: Service() { } private fun updateNetwork() { + if (!isNativeReady) return + + updatePartialWakeLock(brief = true) updateDnsServers() val addresses = linkAddresses() @@ -1999,15 +2020,18 @@ class BaresipService: Service() { private fun linkAddresses(): MutableMap { val addresses = mutableMapOf() - for (n in allNetworks) { - val caps = cm.getNetworkCapabilities(n) ?: continue - if (caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_FOREGROUND)) { - val props = cm.getLinkProperties(n) ?: continue - for (la in props.linkAddresses) - if (la.scope == OsConstants.RT_SCOPE_UNIVERSE && + synchronized(allNetworks) { + for (n in allNetworks) { + val caps = cm.getNetworkCapabilities(n) ?: continue + if (caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_FOREGROUND)) { + val props = cm.getLinkProperties(n) ?: continue + for (la in props.linkAddresses) + if (la.scope == OsConstants.RT_SCOPE_UNIVERSE && props.interfaceName != null && la.address.hostAddress != null && - afMatch(la.address.hostAddress!!)) - addresses[la.address.hostAddress!!] = props.interfaceName!! + afMatch(la.address.hostAddress!!) + ) + addresses[la.address.hostAddress!!] = props.interfaceName!! + } } } if (hotSpotIsEnabled) { @@ -2138,6 +2162,7 @@ class BaresipService: Service() { var instance: BaresipService? = null var isServiceRunning = false + var isNativeReady = false var isStartReceived = false var isConfigInitialized = false var libraryLoaded = false diff --git a/app/src/main/kotlin/com/tutpro/baresip/BootCompletedReceiver.kt b/app/src/main/kotlin/com/tutpro/baresip/BootCompletedReceiver.kt index e2d3ea9d..0b8a77a0 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/BootCompletedReceiver.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/BootCompletedReceiver.kt @@ -3,7 +3,7 @@ package com.tutpro.baresip import android.content.BroadcastReceiver import android.content.Context import android.content.Intent -import android.os.Bundle +import androidx.core.content.ContextCompat import java.nio.charset.StandardCharsets @@ -17,14 +17,12 @@ class BootCompletedReceiver : BroadcastReceiver() { val config = Utils.getFileContents(configPath) ?: return val asCv = Utils.getNameValue(String(config, StandardCharsets.ISO_8859_1), "auto_start") if ((asCv.isNotEmpty()) && (asCv[0] == "yes")) { - Log.i(TAG, "Start baresip upon boot completed") - val i = Intent(context, MainActivity::class.java).apply { - addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - val b = Bundle() - b.putBoolean("onStartup", true) - putExtras(b) + Log.i(TAG, "Start baresip service upon boot completed") + val baresipService = Intent(context, BaresipService::class.java).apply { + action = "Start" + putExtra("onStartup", true) } - context.startActivity(i) + ContextCompat.startForegroundService(context, baresipService) } } diff --git a/app/src/main/kotlin/com/tutpro/baresip/MainActivity.kt b/app/src/main/kotlin/com/tutpro/baresip/MainActivity.kt index f1b92aa9..14ba9b4e 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/MainActivity.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/MainActivity.kt @@ -25,7 +25,6 @@ import android.view.WindowManager import android.view.inputmethod.InputMethodManager import androidx.activity.ComponentActivity import androidx.activity.compose.setContent -import androidx.activity.enableEdgeToEdge import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.viewModels @@ -35,6 +34,7 @@ import androidx.lifecycle.Observer import androidx.navigation.NavHostController import androidx.navigation.compose.NavHost import androidx.navigation.compose.rememberNavController +import androidx.core.content.ContextCompat import kotlin.system.exitProcess class MainActivity : ComponentActivity() { @@ -150,7 +150,7 @@ class MainActivity : ComponentActivity() { if (BaresipService.isServiceRunning) { restart = true baresipService.action = "Stop" - startService(baresipService) + ContextCompat.startForegroundService(this, baresipService) } else { finishAndRemoveTask() val pm = applicationContext.packageManager @@ -171,7 +171,7 @@ class MainActivity : ComponentActivity() { if (BaresipService.isServiceRunning) { restart = false baresipService.action = "Stop" - startService(baresipService) + ContextCompat.startForegroundService(this, baresipService) } else { finishAndRemoveTask() exitProcess(0) @@ -236,7 +236,7 @@ class MainActivity : ComponentActivity() { else { if (!BaresipService.isStartReceived) { baresipService.action = "Start" - startService(baresipService) + ContextCompat.startForegroundService(this, baresipService) if (atStartup) moveTaskToBack(true) } @@ -389,7 +389,7 @@ class MainActivity : ComponentActivity() { if (BaresipService.isServiceRunning) { restart = reStart baresipService.action = "Stop" - startService(baresipService) + ContextCompat.startForegroundService(this, baresipService) } else { finishAndRemoveTask() if (reStart) diff --git a/app/src/main/kotlin/com/tutpro/baresip/MainScreen.kt b/app/src/main/kotlin/com/tutpro/baresip/MainScreen.kt index 58410030..60c9624e 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/MainScreen.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/MainScreen.kt @@ -2087,7 +2087,7 @@ private fun answer(ctx: Context, call: Call) { intent.action = "Call Answer" intent.putExtra("uap", call.ua.uap) intent.putExtra("callp", call.callp) - ctx.startService(intent) + ContextCompat.startForegroundService(ctx, intent) } private fun reject(call: Call) { diff --git a/app/src/main/kotlin/com/tutpro/baresip/SettingsScreen.kt b/app/src/main/kotlin/com/tutpro/baresip/SettingsScreen.kt index 181fdef0..62d7476d 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/SettingsScreen.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/SettingsScreen.kt @@ -21,6 +21,7 @@ import androidx.activity.result.ActivityResult import androidx.activity.result.contract.ActivityResultContracts import androidx.annotation.RequiresApi import androidx.appcompat.app.AppCompatDelegate +import androidx.core.content.ContextCompat import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement @@ -74,7 +75,6 @@ import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.core.app.ActivityCompat.shouldShowRequestPermissionRationale -import androidx.core.content.ContextCompat import androidx.core.net.toUri import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.NavController @@ -1499,7 +1499,7 @@ private fun checkOnClick(ctx: Context, viewModel: SettingsViewModel): Boolean { } } Contact.contactsUpdate() - ctx.startService(baresipService) + ContextCompat.startForegroundService(ctx, baresipService) save = true } @@ -1535,7 +1535,7 @@ private fun checkOnClick(ctx: Context, viewModel: SettingsViewModel): Boolean { UserAgent.updateColorblindStatus() val baresipService = Intent(ctx, BaresipService::class.java) baresipService.action = "Update Notification" - ctx.startService(baresipService) + ContextCompat.startForegroundService(ctx, baresipService) save = true } diff --git a/app/src/main/kotlin/com/tutpro/baresip/TaskReceiver.kt b/app/src/main/kotlin/com/tutpro/baresip/TaskReceiver.kt index 0d6bd2a6..f0871c5f 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/TaskReceiver.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/TaskReceiver.kt @@ -3,6 +3,7 @@ package com.tutpro.baresip import android.content.BroadcastReceiver import android.content.Context import android.content.Intent +import androidx.core.content.ContextCompat class TaskReceiver : BroadcastReceiver() { @@ -46,7 +47,7 @@ class TaskReceiver : BroadcastReceiver() { val baresipService = Intent(context, BaresipService::class.java) if (BaresipService.isServiceRunning) { baresipService.action = "Stop" - context.startService(baresipService) + ContextCompat.startForegroundService(context, baresipService) } }