From 451267688a347cbe3e807e3bb29861c581575026 Mon Sep 17 00:00:00 2001 From: OneWay Date: Mon, 21 Sep 2026 00:05:48 +0800 Subject: [PATCH] fix: clear import progress notification stuck at 100% The progress notification was detached from the foreground service on pipeline finish, so a process killed mid-import left an ongoing notification orphaned in the shade until the next app launch. Terminal notifications now use their own id, and finishing the pipeline removes the bound progress notification; swiping the app from recents cleans it up as well. --- .../common/progress/DictionaryProgressModel.kt | 2 +- .../research/common/util/NotificationHelper.kt | 15 ++++++++++++--- .../service/DictionaryForegroundService.kt | 8 +++++++- 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/com/example/research/common/progress/DictionaryProgressModel.kt b/app/src/main/java/com/example/research/common/progress/DictionaryProgressModel.kt index ee1c138..cdc14c0 100644 --- a/app/src/main/java/com/example/research/common/progress/DictionaryProgressModel.kt +++ b/app/src/main/java/com/example/research/common/progress/DictionaryProgressModel.kt @@ -120,7 +120,7 @@ object DictionaryProgressModel { } else { DOWNLOAD_WEIGHT + EXTRACT_WEIGHT + idx * INDEX_WEIGHT } - val pct = (p * 100f).toInt().coerceIn(0, 100) + val pct = (p * 100f).toInt().coerceIn(0, 99) return Snapshot( progress = p, diff --git a/app/src/main/java/com/example/research/common/util/NotificationHelper.kt b/app/src/main/java/com/example/research/common/util/NotificationHelper.kt index f730c82..433d03a 100644 --- a/app/src/main/java/com/example/research/common/util/NotificationHelper.kt +++ b/app/src/main/java/com/example/research/common/util/NotificationHelper.kt @@ -13,6 +13,7 @@ class NotificationHelper(private val context: Context) : NotificationPort { companion object { const val CHANNEL_ID = "download_progress_channel" const val NOTIFICATION_ID = 1 + const val TERMINAL_NOTIFICATION_ID = 2 } private val notificationManager = NotificationManagerCompat.from(context) private var lastNotificationKey: Int = 0 @@ -91,7 +92,7 @@ class NotificationHelper(private val context: Context) : NotificationPort { .setContentIntent(mainActivityPendingIntent) .setAutoCancel(true) .build() - safeNotify(notification) + safeNotifyTerminal(notification) } fun showErrorNotification() { if (!canShowNotification()) return @@ -103,7 +104,7 @@ class NotificationHelper(private val context: Context) : NotificationPort { .setContentIntent(mainActivityPendingIntent) .setAutoCancel(true) .build() - safeNotify(notification) + safeNotifyTerminal(notification) } fun cancelNotification() { lastNotificationKey = 0 @@ -161,6 +162,14 @@ class NotificationHelper(private val context: Context) : NotificationPort { // Notification permission denied or security policy blocked } } + + @SuppressLint("MissingPermission") + private fun safeNotifyTerminal(notification: Notification) { + try { + notificationManager.notify(TERMINAL_NOTIFICATION_ID, notification) + } catch (_: SecurityException) { + } + } fun showImportSuccessNotification() { if (!canShowNotification()) return lastNotificationKey = 0 @@ -171,7 +180,7 @@ class NotificationHelper(private val context: Context) : NotificationPort { .setContentIntent(mainActivityPendingIntent) .setAutoCancel(true) .build() - safeNotify(notification) + safeNotifyTerminal(notification) } fun createForegroundNotification(): Notification { val notification = NotificationCompat.Builder(context, CHANNEL_ID) diff --git a/app/src/main/java/com/example/research/feature/download/service/DictionaryForegroundService.kt b/app/src/main/java/com/example/research/feature/download/service/DictionaryForegroundService.kt index ed0dbc9..5f1517b 100644 --- a/app/src/main/java/com/example/research/feature/download/service/DictionaryForegroundService.kt +++ b/app/src/main/java/com/example/research/feature/download/service/DictionaryForegroundService.kt @@ -88,6 +88,12 @@ class DictionaryForegroundService : Service() { stopForTimeout(startId) } + override fun onTaskRemoved(rootIntent: Intent?) { + cancelAllWork() + ServiceCompat.stopForeground(this, ServiceCompat.STOP_FOREGROUND_REMOVE) + stopSelf() + } + override fun onTimeout(startId: Int, fgsType: Int) { stopForTimeout(startId) } @@ -125,7 +131,7 @@ class DictionaryForegroundService : Service() { startForegroundService() } ACTION_FINISH -> { - stopForegroundService(removeNotification = false) + stopForegroundService(removeNotification = true) } ACTION_STOP -> { try {