Contain headless foreground, notification and teardown failures
This commit is contained in:
1 parent
d7fb8568b2
commit
6916575380
3 files changed
+41
-13
No files matched your search
@@ -75,7 +75,8 @@ internal class CoreStartup(
|
||||
closed = true
|
||||
ready.completeExceptionally(error)
|
||||
log("ERROR", "core startup failed: ${error.javaClass.simpleName}: ${error.message}")
|
||||
onFailure()
|
||||
try { onFailure() }
|
||||
catch (e: Exception) { log("ERROR", "startup failure cleanup callback failed: ${e.javaClass.simpleName}: ${e.message}") }
|
||||
}
|
||||
|
||||
/** Сначала закрывает готовность; поздний callback и готовый конфиг уже не могут завершить запуск. */
|
||||
|
||||
@@ -25,6 +25,7 @@ import kotlinx.coroutines.withContext
|
||||
|
||||
/** Владеет запуском ядра/сетевых наблюдателей; готовность подтверждается событием native SERVICE_STARTED. */
|
||||
class HeadlessService : Service() {
|
||||
private var foregroundReady = false
|
||||
private val startupScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
|
||||
private val startup = CoreStartup(startupScope, NativeLib::isReady, onReady = {
|
||||
LogManager.addLog("INFO", "SRV", "uTun core started")
|
||||
@@ -51,13 +52,18 @@ class HeadlessService : Service() {
|
||||
|
||||
fun refreshLanguage() {
|
||||
currentService?.let { service ->
|
||||
service.createNotificationChannel()
|
||||
service.getSystemService(NotificationManager::class.java).notify(1, service.notification())
|
||||
try {
|
||||
if (!service.foregroundReady) return
|
||||
service.createNotificationChannel()
|
||||
service.getSystemService(NotificationManager::class.java).notify(1, service.notification())
|
||||
} catch (e: Exception) { LogManager.addLog("ERROR", "SRV", "Service notification language update failed: $e") }
|
||||
}
|
||||
}
|
||||
fun refreshFiles() {
|
||||
currentService?.let { service ->
|
||||
service.getSystemService(NotificationManager::class.java).notify(1, service.notification())
|
||||
try {
|
||||
if (service.foregroundReady) service.getSystemService(NotificationManager::class.java).notify(1, service.notification())
|
||||
} catch (e: Exception) { LogManager.addLog("ERROR", "SRV", "Service file notification update failed: $e") }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -65,8 +71,16 @@ class HeadlessService : Service() {
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
currentService = this
|
||||
createNotificationChannel()
|
||||
startForeground(1, notification())
|
||||
try {
|
||||
createNotificationChannel()
|
||||
startForeground(1, notification())
|
||||
foregroundReady = true
|
||||
LogManager.addLog("INFO", "SRV", "Core service foreground confirmed")
|
||||
} catch (e: Exception) {
|
||||
LogManager.addLog("ERROR", "SRV", "Core service foreground failed: $e")
|
||||
startup.fail(e)
|
||||
return
|
||||
}
|
||||
|
||||
startup.start(prepare = {
|
||||
shutdown.await()
|
||||
@@ -82,19 +96,23 @@ class HeadlessService : Service() {
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
startup.stop()
|
||||
startupScope.cancel()
|
||||
foregroundReady = false
|
||||
if (currentService === this) currentService = null
|
||||
LogManager.addLog("INFO", "SRV", "uTun service stopping")
|
||||
NetworkChangeMonitor.stop()
|
||||
NetworkActivityMonitor.stop()
|
||||
shutdown.request()
|
||||
super.onDestroy()
|
||||
try { startup.stop() } catch (e: Exception) { LogManager.addLog("ERROR", "SRV", "Startup cancellation failed: $e") }
|
||||
try { startupScope.cancel() } catch (e: Exception) { LogManager.addLog("ERROR", "SRV", "Startup scope cancellation failed: $e") }
|
||||
try { NetworkChangeMonitor.stop() } catch (e: Exception) { LogManager.addLog("ERROR", "SRV", "Network observer stop failed: $e") }
|
||||
try { NetworkActivityMonitor.stop() } catch (e: Exception) { LogManager.addLog("ERROR", "SRV", "Network activity stop failed: $e") }
|
||||
try { shutdown.request() } finally { super.onDestroy() }
|
||||
}
|
||||
|
||||
override fun onBind(intent: Intent?): IBinder? = null
|
||||
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
if (!foregroundReady) {
|
||||
LogManager.addLog("ERROR", "SRV", "Service command rejected before foreground confirmation startId=$startId")
|
||||
return START_NOT_STICKY
|
||||
}
|
||||
if (intent?.action == "com.utun.chat.CANCEL_FILES") {
|
||||
ChatApplication.instance.memberFiles.cancel()
|
||||
return START_STICKY
|
||||
@@ -124,7 +142,7 @@ class HeadlessService : Service() {
|
||||
val detail = operation.status?.let { AppLanguage.text(fileStatusResource(it)) } ?: AppLanguage.text(operation.stage)
|
||||
builder.setContentText("${operation.name}: $detail")
|
||||
if (operation.active) {
|
||||
val percent = if (operation.total == 0L) 0 else (operation.done.toDouble() * 100 / operation.total).toInt()
|
||||
val percent = if (operation.total <= 0L) 0 else (operation.done.toDouble() * 100 / operation.total).toInt().coerceIn(0, 100)
|
||||
builder.setProgress(100, percent, operation.total == 0L)
|
||||
val cancel = PendingIntent.getService(this, 21, Intent(this, HeadlessService::class.java)
|
||||
.setAction("com.utun.chat.CANCEL_FILES"), PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT)
|
||||
|
||||
@@ -189,4 +189,13 @@ class CoreStartupTest {
|
||||
config.complete("config")
|
||||
assertEquals(1, waiter().await())
|
||||
}
|
||||
@Test fun failingStopCallbackCannotMaskTheStartupFailureOrEscapeToMain() = runBlocking {
|
||||
val logs = mutableListOf<String>()
|
||||
val owner = CoreStartup(scope, { false }, {}, { error("stopSelf rejected") }, { _, message -> logs.add(message) })
|
||||
val failure = IllegalStateException("foreground denied")
|
||||
owner.fail(failure)
|
||||
val result = runCatching { owner.awaitReady() }.exceptionOrNull()
|
||||
assertEquals("foreground denied", result?.message)
|
||||
assertTrue(logs.any { "stopSelf rejected" in it })
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user