Fix foreground service start eligibility (#714)

* Fix foreground service start eligibility

* Defer foreground service start when backgrounded

* fix crash

---------

Co-authored-by: CC <cc@ggg.local>
This commit is contained in:
callebtc
2026-06-17 11:59:43 -05:00
committed by GitHub
co-authored by CC
parent 9e0a919cb8
commit b89644f5f7
2 changed files with 33 additions and 14 deletions
@@ -59,6 +59,7 @@ class MainActivity : OrientationAwareActivity() {
private lateinit var meshService: BluetoothMeshService private lateinit var meshService: BluetoothMeshService
private lateinit var unifiedMeshService: MeshService private lateinit var unifiedMeshService: MeshService
private val mainViewModel: MainViewModel by viewModels() private val mainViewModel: MainViewModel by viewModels()
private var pendingMeshForegroundServiceStart = false
private val chatViewModel: ChatViewModel by viewModels { private val chatViewModel: ChatViewModel by viewModels {
object : ViewModelProvider.Factory { object : ViewModelProvider.Factory {
override fun <T : androidx.lifecycle.ViewModel> create(modelClass: Class<T>): T { override fun <T : androidx.lifecycle.ViewModel> create(modelClass: Class<T>): T {
@@ -114,8 +115,8 @@ class MainActivity : OrientationAwareActivity() {
// Initialize permission management // Initialize permission management
permissionManager = PermissionManager(this) permissionManager = PermissionManager(this)
// Ensure foreground service is running and get mesh instance from holder // Start the foreground service when allowed, then get mesh instances from the holder.
try { com.bitchat.android.service.MeshForegroundService.start(applicationContext) } catch (_: Exception) { } startMeshForegroundServiceBestEffort()
meshService = com.bitchat.android.service.MeshServiceHolder.getOrCreate(applicationContext) meshService = com.bitchat.android.service.MeshServiceHolder.getOrCreate(applicationContext)
unifiedMeshService = com.bitchat.android.service.MeshServiceHolder.getUnifiedOrCreate(applicationContext) unifiedMeshService = com.bitchat.android.service.MeshServiceHolder.getUnifiedOrCreate(applicationContext)
// Expose BLE mesh to WiFi Aware controller for cross-transport relays - DEPRECATED // Expose BLE mesh to WiFi Aware controller for cross-transport relays - DEPRECATED
@@ -615,6 +616,22 @@ class MainActivity : OrientationAwareActivity() {
mainViewModel.updateOnboardingState(OnboardingState.ERROR) mainViewModel.updateOnboardingState(OnboardingState.ERROR)
} }
private fun startMeshForegroundServiceBestEffort() {
if (!lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) {
pendingMeshForegroundServiceStart = true
Log.i("MainActivity", "Deferring foreground mesh service start until activity is started")
return
}
try {
com.bitchat.android.service.MeshForegroundService.start(applicationContext)
pendingMeshForegroundServiceStart = false
} catch (e: Exception) {
pendingMeshForegroundServiceStart = true
Log.w("MainActivity", "Unable to start foreground mesh service; will retry when activity is started", e)
}
}
/** /**
* Check Battery Optimization status and proceed with onboarding flow * Check Battery Optimization status and proceed with onboarding flow
*/ */
@@ -715,6 +732,7 @@ class MainActivity : OrientationAwareActivity() {
// Set up unified mesh delegate and start enabled transports // Set up unified mesh delegate and start enabled transports
unifiedMeshService.delegate = chatViewModel unifiedMeshService.delegate = chatViewModel
unifiedMeshService.startServices() unifiedMeshService.startServices()
startMeshForegroundServiceBestEffort()
Log.d("MainActivity", "Mesh service started successfully") Log.d("MainActivity", "Mesh service started successfully")
@@ -753,6 +771,13 @@ class MainActivity : OrientationAwareActivity() {
} }
} }
override fun onStart() {
super.onStart()
if (pendingMeshForegroundServiceStart) {
startMeshForegroundServiceBestEffort()
}
}
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
// Check Bluetooth and Location status on resume and handle accordingly // Check Bluetooth and Location status on resume and handle accordingly
@@ -38,24 +38,20 @@ class MeshForegroundService : Service() {
fun start(context: Context) { fun start(context: Context) {
val intent = Intent(context, MeshForegroundService::class.java).apply { action = ACTION_START } val intent = Intent(context, MeshForegroundService::class.java).apply { action = ACTION_START }
// On API >= 26, avoid background-service start restrictions by using startForegroundService // Only launch as an FGS when onStartCommand can promote immediately.
// only when we can actually post a notification (Android 13+ requires runtime notif permission) val shouldStartForeground = shouldStartAsForeground(context)
val bgEnabled = MeshServicePreferences.isBackgroundEnabled(true)
val hasNotifPerm = hasNotificationPermissionStatic(context)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
if (bgEnabled && hasNotifPerm) { if (shouldStartForeground) {
context.startForegroundService(intent) context.startForegroundService(intent)
} else { } else {
// Do not attempt to start a background service from headless context without notif permission
// or when background is disabled, to avoid BackgroundServiceStartNotAllowedException.
android.util.Log.i( android.util.Log.i(
"MeshForegroundService", "MeshForegroundService",
"Not starting service on API>=26 (bgEnabled=$bgEnabled, hasNotifPerm=$hasNotifPerm)" "Not starting service on API>=26 (shouldStartForeground=$shouldStartForeground)"
) )
} }
} else { } else {
if (bgEnabled) { if (MeshServicePreferences.isBackgroundEnabled(true)) {
context.startService(intent) context.startService(intent)
} else { } else {
android.util.Log.i("MeshForegroundService", "Background disabled; not starting service (pre-O)") android.util.Log.i("MeshForegroundService", "Background disabled; not starting service (pre-O)")
@@ -69,12 +65,10 @@ class MeshForegroundService : Service() {
*/ */
fun onNotificationPermissionGranted(context: Context) { fun onNotificationPermissionGranted(context: Context) {
// If background is enabled and permission now granted, start/promo service // If background is enabled and permission now granted, start/promo service
val hasNotifPerm = hasNotificationPermissionStatic(context) if (!shouldStartAsForeground(context)) return
if (!MeshServicePreferences.isBackgroundEnabled(true) || !hasNotifPerm) return
val intent = Intent(context, MeshForegroundService::class.java).apply { action = ACTION_UPDATE_NOTIFICATION } val intent = Intent(context, MeshForegroundService::class.java).apply { action = ACTION_UPDATE_NOTIFICATION }
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// Safe now that we can show a notification
context.startForegroundService(intent) context.startForegroundService(intent)
} else { } else {
context.startService(intent) context.startService(intent)