mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-04-20 22:23:37 +00:00
feat(service): Introduce fallback worker to ensure service stays alive (#4295)
Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>
This commit is contained in:
parent
bd4cb80877
commit
576ae6757f
7 changed files with 144 additions and 26 deletions
|
|
@ -257,7 +257,8 @@ constructor(
|
|||
/** Start our configured interface (if it isn't already running) */
|
||||
private fun startInterface() {
|
||||
if (radioIf !is NopInterface) {
|
||||
Logger.w { "Can't start interface - $radioIf is already running" }
|
||||
// Already running
|
||||
return
|
||||
} else {
|
||||
val address = getBondedDeviceAddress()
|
||||
if (address == null) {
|
||||
|
|
|
|||
|
|
@ -98,12 +98,7 @@ class MeshService : Service() {
|
|||
|
||||
fun changeDeviceAddress(context: Context, service: IMeshService, address: String?) {
|
||||
service.setDeviceAddress(address)
|
||||
val intent = Intent(context, MeshService::class.java)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
context.startForegroundService(intent)
|
||||
} else {
|
||||
context.startService(intent)
|
||||
}
|
||||
startService(context)
|
||||
}
|
||||
|
||||
val minDeviceVersion = DeviceVersion(BuildConfig.MIN_FW_VERSION)
|
||||
|
|
@ -145,11 +140,13 @@ class MeshService : Service() {
|
|||
SERVICE_NOTIFY_ID,
|
||||
notification,
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
var types =
|
||||
ServiceInfo.FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE or
|
||||
ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC
|
||||
if (hasLocationPermission()) {
|
||||
ServiceInfo.FOREGROUND_SERVICE_TYPE_MANIFEST
|
||||
} else {
|
||||
ServiceInfo.FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE
|
||||
types = types or ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION
|
||||
}
|
||||
types
|
||||
} else {
|
||||
0
|
||||
},
|
||||
|
|
@ -300,7 +297,12 @@ class MeshService : Service() {
|
|||
}
|
||||
|
||||
override fun removeByNodenum(requestId: Int, nodeNum: Int) = toRemoteExceptions {
|
||||
router.actionHandler.handleRemoveByNodenum(nodeNum, requestId, myNodeNum)
|
||||
val myNodeNum = nodeManager.myNodeNum
|
||||
if (myNodeNum != null) {
|
||||
router.actionHandler.handleRemoveByNodenum(nodeNum, requestId, myNodeNum)
|
||||
} else {
|
||||
nodeManager.removeByNodenum(nodeNum)
|
||||
}
|
||||
}
|
||||
|
||||
override fun requestUserInfo(destNum: Int) = toRemoteExceptions {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2025 Meshtastic LLC
|
||||
* Copyright (c) 2025-2026 Meshtastic LLC
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
|
|
@ -14,14 +14,17 @@
|
|||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.geeksville.mesh.service
|
||||
|
||||
import android.app.ForegroundServiceStartNotAllowedException
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import androidx.work.OneTimeWorkRequestBuilder
|
||||
import androidx.work.OutOfQuotaPolicy
|
||||
import androidx.work.WorkManager
|
||||
import co.touchlab.kermit.Logger
|
||||
import com.geeksville.mesh.BuildConfig
|
||||
import com.geeksville.mesh.worker.ServiceKeepAliveWorker
|
||||
|
||||
// / Helper function to start running our service
|
||||
fun MeshService.Companion.startService(context: Context) {
|
||||
|
|
@ -40,9 +43,19 @@ fun MeshService.Companion.startService(context: Context) {
|
|||
try {
|
||||
context.startForegroundService(intent)
|
||||
} catch (ex: ForegroundServiceStartNotAllowedException) {
|
||||
Logger.e { "Unable to start service: ${ex.message}" }
|
||||
Logger.w { "Unable to start service foreground: ${ex.message}. Scheduling fallback worker." }
|
||||
scheduleKeepAliveWorker(context)
|
||||
}
|
||||
} else {
|
||||
context.startForegroundService(intent)
|
||||
}
|
||||
}
|
||||
|
||||
private fun scheduleKeepAliveWorker(context: Context) {
|
||||
val request =
|
||||
OneTimeWorkRequestBuilder<ServiceKeepAliveWorker>()
|
||||
.setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
|
||||
.build()
|
||||
|
||||
WorkManager.getInstance(context).enqueue(request)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,94 @@
|
|||
/*
|
||||
* Copyright (c) 2025-2026 Meshtastic LLC
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.geeksville.mesh.worker
|
||||
|
||||
import android.app.Notification
|
||||
import android.content.Context
|
||||
import android.content.pm.ServiceInfo
|
||||
import android.os.Build
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.hilt.work.HiltWorker
|
||||
import androidx.work.CoroutineWorker
|
||||
import androidx.work.ForegroundInfo
|
||||
import androidx.work.WorkerParameters
|
||||
import co.touchlab.kermit.Logger
|
||||
import com.geeksville.mesh.R
|
||||
import com.geeksville.mesh.service.MeshService
|
||||
import com.geeksville.mesh.service.startService
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedInject
|
||||
import org.meshtastic.core.service.MeshServiceNotifications
|
||||
import org.meshtastic.core.service.SERVICE_NOTIFY_ID
|
||||
|
||||
/**
|
||||
* A worker whose sole purpose is to start the MeshService from the background. This is used as a fallback when
|
||||
* `startForegroundService` is blocked by Android 14+ restrictions. It runs as an Expedited worker to gain temporary
|
||||
* foreground start privileges.
|
||||
*/
|
||||
@HiltWorker
|
||||
class ServiceKeepAliveWorker
|
||||
@AssistedInject
|
||||
constructor(
|
||||
@Assisted appContext: Context,
|
||||
@Assisted workerParams: WorkerParameters,
|
||||
private val serviceNotifications: MeshServiceNotifications,
|
||||
) : CoroutineWorker(appContext, workerParams) {
|
||||
|
||||
override suspend fun getForegroundInfo(): ForegroundInfo {
|
||||
// We use the same notification channel as the main service notification
|
||||
// to minimize user disruption.
|
||||
// On Android 12+, we need to provide a foreground info for expedited work.
|
||||
val notification = createNotification()
|
||||
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
ForegroundInfo(
|
||||
SERVICE_NOTIFY_ID,
|
||||
notification,
|
||||
ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC or ServiceInfo.FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE,
|
||||
)
|
||||
} else {
|
||||
ForegroundInfo(SERVICE_NOTIFY_ID, notification)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("TooGenericExceptionCaught")
|
||||
override suspend fun doWork(): Result {
|
||||
Logger.i { "ServiceKeepAliveWorker: Attempting to start MeshService" }
|
||||
return try {
|
||||
MeshService.startService(applicationContext)
|
||||
Result.success()
|
||||
} catch (e: Exception) {
|
||||
Logger.e(e) { "ServiceKeepAliveWorker failed to start service" }
|
||||
Result.failure()
|
||||
}
|
||||
}
|
||||
|
||||
private fun createNotification(): Notification {
|
||||
// We ensure channels are created
|
||||
serviceNotifications.initChannels()
|
||||
|
||||
// We create a generic "Resuming" notification.
|
||||
// We use "my_service" which matches NotificationType.ServiceState.channelId in MeshServiceNotificationsImpl
|
||||
|
||||
return NotificationCompat.Builder(applicationContext, "my_service")
|
||||
.setSmallIcon(R.drawable.ic_launcher_foreground)
|
||||
.setContentTitle("Resuming Mesh Service")
|
||||
.setPriority(NotificationCompat.PRIORITY_LOW)
|
||||
.setOngoing(true)
|
||||
.build()
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue