diff --git a/app/src/main/java/com/bitchat/android/MainActivity.kt b/app/src/main/java/com/bitchat/android/MainActivity.kt index 3e6b482e..2345549e 100644 --- a/app/src/main/java/com/bitchat/android/MainActivity.kt +++ b/app/src/main/java/com/bitchat/android/MainActivity.kt @@ -59,6 +59,7 @@ class MainActivity : OrientationAwareActivity() { private lateinit var meshService: BluetoothMeshService private lateinit var unifiedMeshService: MeshService private val mainViewModel: MainViewModel by viewModels() + private var pendingMeshForegroundServiceStart = false private val chatViewModel: ChatViewModel by viewModels { object : ViewModelProvider.Factory { override fun create(modelClass: Class): T { @@ -114,8 +115,8 @@ class MainActivity : OrientationAwareActivity() { // Initialize permission management permissionManager = PermissionManager(this) - // Ensure foreground service is running and get mesh instance from holder - try { com.bitchat.android.service.MeshForegroundService.start(applicationContext) } catch (_: Exception) { } + // Start the foreground service when allowed, then get mesh instances from the holder. + startMeshForegroundServiceBestEffort() meshService = com.bitchat.android.service.MeshServiceHolder.getOrCreate(applicationContext) unifiedMeshService = com.bitchat.android.service.MeshServiceHolder.getUnifiedOrCreate(applicationContext) // Expose BLE mesh to Wi‑Fi Aware controller for cross-transport relays - DEPRECATED @@ -614,6 +615,22 @@ class MainActivity : OrientationAwareActivity() { mainViewModel.updateErrorMessage(message) 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 @@ -715,6 +732,7 @@ class MainActivity : OrientationAwareActivity() { // Set up unified mesh delegate and start enabled transports unifiedMeshService.delegate = chatViewModel unifiedMeshService.startServices() + startMeshForegroundServiceBestEffort() Log.d("MainActivity", "Mesh service started successfully") @@ -752,6 +770,13 @@ class MainActivity : OrientationAwareActivity() { handleVerificationIntent(intent) } } + + override fun onStart() { + super.onStart() + if (pendingMeshForegroundServiceStart) { + startMeshForegroundServiceBestEffort() + } + } override fun onResume() { super.onResume() diff --git a/app/src/main/java/com/bitchat/android/service/MeshForegroundService.kt b/app/src/main/java/com/bitchat/android/service/MeshForegroundService.kt index 218631ea..d9ad2d7f 100644 --- a/app/src/main/java/com/bitchat/android/service/MeshForegroundService.kt +++ b/app/src/main/java/com/bitchat/android/service/MeshForegroundService.kt @@ -38,24 +38,20 @@ class MeshForegroundService : Service() { fun start(context: Context) { val intent = Intent(context, MeshForegroundService::class.java).apply { action = ACTION_START } - // On API >= 26, avoid background-service start restrictions by using startForegroundService - // only when we can actually post a notification (Android 13+ requires runtime notif permission) - val bgEnabled = MeshServicePreferences.isBackgroundEnabled(true) - val hasNotifPerm = hasNotificationPermissionStatic(context) + // Only launch as an FGS when onStartCommand can promote immediately. + val shouldStartForeground = shouldStartAsForeground(context) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - if (bgEnabled && hasNotifPerm) { + if (shouldStartForeground) { context.startForegroundService(intent) } 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( "MeshForegroundService", - "Not starting service on API>=26 (bgEnabled=$bgEnabled, hasNotifPerm=$hasNotifPerm)" + "Not starting service on API>=26 (shouldStartForeground=$shouldStartForeground)" ) } } else { - if (bgEnabled) { + if (MeshServicePreferences.isBackgroundEnabled(true)) { context.startService(intent) } else { android.util.Log.i("MeshForegroundService", "Background disabled; not starting service (pre-O)") @@ -69,12 +65,10 @@ class MeshForegroundService : Service() { */ fun onNotificationPermissionGranted(context: Context) { // If background is enabled and permission now granted, start/promo service - val hasNotifPerm = hasNotificationPermissionStatic(context) - if (!MeshServicePreferences.isBackgroundEnabled(true) || !hasNotifPerm) return + if (!shouldStartAsForeground(context)) return val intent = Intent(context, MeshForegroundService::class.java).apply { action = ACTION_UPDATE_NOTIFICATION } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - // Safe now that we can show a notification context.startForegroundService(intent) } else { context.startService(intent)