Skip to content
Open
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -41,17 +41,20 @@ class MeshForegroundService : Service() {
// 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 hasBtPerms = hasBluetoothPermissionsStatic(context)
val hasNotifPerm = hasNotificationPermissionStatic(context)
val canStartAsForeground = bgEnabled && hasBtPerms && hasNotifPerm

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
if (bgEnabled && hasNotifPerm) {
if (canStartAsForeground) {
context.startForegroundService(intent)
Comment on lines +46 to 50

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Re-trigger foreground start after runtime Bluetooth grant

This new guard skips startForegroundService whenever Bluetooth/location permissions are not already granted, but there is no automatic retry path once those permissions are granted later in the same app session (the existing call sites invoke MeshForegroundService.start(...) only during startup/toggle flows). On first-launch permission onboarding for API 26+ devices, users can complete grants and still never get the foreground service/notification until they relaunch or manually toggle background mode, which leaves background mesh behavior unexpectedly disabled.

Useful? React with 👍 / 👎.

} else {
// Do not attempt to start a background service from headless context without notif permission
// or when background is disabled, to avoid BackgroundServiceStartNotAllowedException.
// or when background is disabled / bluetooth permission is missing,
// to avoid BackgroundServiceStartNotAllowedException and FGS start timeout crashes.
android.util.Log.i(
"MeshForegroundService",
"Not starting service on API>=26 (bgEnabled=$bgEnabled, hasNotifPerm=$hasNotifPerm)"
"Not starting service on API>=26 (bgEnabled=$bgEnabled, hasBtPerms=$hasBtPerms, hasNotifPerm=$hasNotifPerm)"
)
}
} else {
Expand All @@ -68,9 +71,8 @@ class MeshForegroundService : Service() {
* promoting/starting the foreground service immediately without polling.
*/
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 we can now fully run as foreground, start/promote immediately.
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) {
Expand Down Expand Up @@ -124,16 +126,6 @@ class MeshForegroundService : Service() {
super.onCreate()
notificationManager = NotificationManagerCompat.from(this)
createChannel()

// Ensure mesh service exists in holder (create if needed)
val existing = MeshServiceHolder.meshService
if (existing != null) {
Log.d("MeshForegroundService", "Using existing BluetoothMeshService from holder")
} else {
val created = MeshServiceHolder.getOrCreate(applicationContext)
Log.i("MeshForegroundService", "Created new BluetoothMeshService via holder")
MeshServiceHolder.attach(created)
}
}

override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
Expand Down Expand Up @@ -176,28 +168,21 @@ class MeshForegroundService : Service() {
return START_NOT_STICKY
}
ACTION_UPDATE_NOTIFICATION -> {
// If we became eligible and are not in foreground yet, promote once
if (MeshServicePreferences.isBackgroundEnabled(true) && hasAllRequiredPermissions() && !isInForeground) {
val n = buildNotification(meshService?.getActivePeerCount() ?: 0)
startForegroundCompat(n)
isInForeground = true
} else {
updateNotification(force = true)
}
updateNotification(force = true)
}
else -> { /* ACTION_START or null */ }
}

// Ensure mesh is running (only after permissions are granted)
ensureMeshStarted()

// Promote exactly once when eligible, otherwise stay background (or stop)
// Promote as early as possible after startForegroundService() to avoid timeout.
if (MeshServicePreferences.isBackgroundEnabled(true) && hasAllRequiredPermissions() && !isInForeground) {
val notification = buildNotification(meshService?.getActivePeerCount() ?: 0)
startForegroundCompat(notification)
isInForeground = true
}

// Ensure mesh is running (only after permissions are granted)
ensureMeshStarted()

// Periodically refresh the notification with live network size
if (updateJob == null) {
updateJob = scope.launch {
Expand Down
Loading