Skip to content

Commit 954f53b

Browse files
Fixed: Playstore reported a crash for android.app.RemoteServiceException$ForegroundServiceDidNotStopInTimeException.
* Android 15 introduced a limitation where an app can run a foreground service in the background for only `6 hours per day`, unless the user explicitly opens the app again, which resets this timer. * To prevent this crash, we have overridden the `onTimeout` method in both `DownloadMonitorService` and `HotspotService`, and we now stop the service when the timeout limit is reached.
1 parent 55a599a commit 954f53b

File tree

2 files changed

+43
-0
lines changed

2 files changed

+43
-0
lines changed

app/src/main/java/org/kiwix/kiwixmobile/webserver/wifi_hotspot/HotspotService.kt

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,25 @@ class HotspotService :
7070
hotspotStateReceiver?.let(::registerReceiver)
7171
}
7272

73+
/**
74+
* Called when the foreground service is about to reach its timeout limit.
75+
*
76+
* Starting from Android 15, foreground services can run for only 6 hours per day
77+
* in the background unless the user explicitly opens the application again,
78+
* which resets this timer.
79+
*
80+
* To avoid the system killing our service and throwing a
81+
* `ForegroundServiceDidNotStopInTimeException`, we proactively stop the
82+
* download service here. When the user opens the app again, the download
83+
* process will resume normally.
84+
*
85+
* More details: https://developer.android.com/develop/background-work/services/fgs/timeout
86+
*/
87+
override fun onTimeout(startId: Int, fgsType: Int) {
88+
stopHotspotAndDismissNotification()
89+
super.onTimeout(startId, fgsType)
90+
}
91+
7392
override fun onDestroy() {
7493
hotspotStateReceiver?.let(::unregisterReceiver)
7594
super.onDestroy()

core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadMonitorService.kt

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,10 @@ import com.tonyodev.fetch2.util.DEFAULT_NOTIFICATION_TIMEOUT_AFTER_RESET
3838
import com.tonyodev.fetch2core.DownloadBlock
3939
import kotlinx.coroutines.CoroutineScope
4040
import kotlinx.coroutines.Dispatchers
41+
import kotlinx.coroutines.ExperimentalCoroutinesApi
4142
import kotlinx.coroutines.Job
4243
import kotlinx.coroutines.SupervisorJob
44+
import kotlinx.coroutines.cancelChildren
4345
import kotlinx.coroutines.flow.MutableSharedFlow
4446
import kotlinx.coroutines.flow.first
4547
import kotlinx.coroutines.launch
@@ -110,6 +112,25 @@ class DownloadMonitorService : Service() {
110112
return START_STICKY
111113
}
112114

115+
/**
116+
* Called when the foreground service is about to reach its timeout limit.
117+
*
118+
* Starting from Android 15, foreground services can run for only 6 hours per day
119+
* in the background unless the user explicitly opens the application again,
120+
* which resets this timer.
121+
*
122+
* To avoid the system killing our service and throwing a
123+
* `ForegroundServiceDidNotStopInTimeException`, we proactively stop the
124+
* download service here. When the user opens the app again, the download
125+
* process will resume normally.
126+
*
127+
* More details: https://developer.android.com/develop/background-work/services/fgs/timeout
128+
*/
129+
override fun onTimeout(startId: Int, fgsType: Int) {
130+
stopForegroundServiceForDownloads()
131+
super.onTimeout(startId, fgsType)
132+
}
133+
113134
/**
114135
* Shows a persistent foreground notification while at least one download is active.
115136
* The notification remains visible until all downloads are complete or stopped.
@@ -358,7 +379,10 @@ class DownloadMonitorService : Service() {
358379
/**
359380
* Stops the foreground service, disposes of resources, and removes the Fetch listener.
360381
*/
382+
@OptIn(ExperimentalCoroutinesApi::class)
361383
private fun stopForegroundServiceForDownloads() {
384+
taskFlow.resetReplayCache()
385+
scope.coroutineContext.cancelChildren()
362386
updaterJob?.cancel()
363387
fetch.removeListener(fetchListener)
364388
stopForeground(STOP_FOREGROUND_REMOVE)

0 commit comments

Comments
 (0)