Skip to content

Conversation

@MohitMaliFtechiz
Copy link
Collaborator

@MohitMaliFtechiz MohitMaliFtechiz commented Nov 18, 2025

Fixes #4486

  • 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. See https://developer.android.com/develop/background-work/services/fgs/timeout.
  • 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.
  • Added a DownloadTimeoutDismissReceiver which will listen for the "No" button event and dismiss the notification.
  • Introduced automatic resume functionality for downloads paused due to Android’s background execution limits.
  • Added a PausedReason field in DownloadRoomEntity so we can automatically resume downloads when the user returns to the application.
  • Fixed: In Custom apps, if a download was running and the user closed and reopened the app, the app incorrectly showed the Reader screen with an error stating that the current ZIM file is not valid, instead of showing the DownloadScreen.
Kiwix App Custom App
KiwixApp.mp4
CustomApp.mp4

@MohitMaliFtechiz MohitMaliFtechiz marked this pull request as draft November 18, 2025 11:15
@MohitMaliFtechiz
Copy link
Collaborator Author

@kelson42 We have fixed the crash. But the user does not know about this limitation, and they will think the download is stopped in the background, as we had some issues earlier where the download stopped in the background(They were actual issues, but this one is intentional). It can confuse users.

So we have to inform them about this so that there is no confusion for users about this limitation.

There could be 2 ways:

  • Add this to the "Help" screen to inform users.
  • When the download stops after reaching the timeout, we can post a notification about this, something like "Android's background downloading timeout. Please open Kiwix to resume downloading," or a better message.

@kelson42 What do you think?

@kelson42
Copy link
Collaborator

@MohitMaliFtechiz This is definitly the second approach I would should. Can we do it in the running Download notification (and reopen one if it has been disabled)? I would propose to use the following message "Android's background download has timed-out. Do you want to resume?" In a nutshell I would replace what is current displayed with the message and and a "Yes" and "no" buttons.

@MohitMaliFtechiz
Copy link
Collaborator Author

@MohitMaliFtechiz This is definitly the second approach I would should. Can we do it in the running Download notification (and reopen one if it has been disabled)?

@kelson42 Yes, we can modify the current running notification, but there could be multiple notifications for multiple downloads. So, this should be for each notification or only for one? Or for minimal and clear we should we post only a single notification with your given message and buttons?

@kelson42
Copy link
Collaborator

@MohitMaliFtechiz This is definitly the second approach I would should. Can we do it in the running Download notification (and reopen one if it has been disabled)?

@kelson42 Yes, we can modify the current running notification, but there could be multiple notifications for multiple downloads. So, this should be for each notification or only for one? Or for minimal and clear we should we post only a single notification with your given message and buttons?

Indeed, I have forgotten about this scenario. So you need:

  • to put all the notifications in pause if this "Android timeout" happens
  • Create only one notification for all of them, so cancel/resume all current downloads.
  • User will be able to resume one by one, using the usual download notification (just clicking on the play icon)

@MohitMaliFtechiz
Copy link
Collaborator Author

Here I have something to highlight first.

to put all the notifications in pause if this "Android timeout" happens

If we put them as a pause notification, the user could not resume the download by clicking on the "Resume" button in the download. Because the service has stopped, so clicking on it will do nothing.

Create only one notification for all of them, so cancel/resume all current downloads.

Yes, currently I am doing the same, only creating a single notification and removing all ongoing notifications along with the "Paused" notification(Since the user could not resume the download until they open the application). So, keeping those notifications will confuse users since they cannot restart the download from the notification.

When they click on the "Yes" button, the app will open, and this timer will reset. But before opening the app, the users can not start the download since the time limit is not reset.

…tion$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.
…fication. Users can tap the "Yes" button to open the app, which resets the background-execution timer. After that, they can easily resume their downloads.

* All ongoing downloads are paused before stopping the service, ensuring that no misleading notifications remain visible (users cannot resume downloads from notifications because the service is already stopped).
* When the user taps "Yes" in the notification, the app opens directly on the Downloads screen so they can resume their downloads. For custom apps, it simply opens the application, and the CustomReaderFragment handles navigating to the DownloadFragment.
@codecov
Copy link

codecov bot commented Nov 19, 2025

Codecov Report

❌ Patch coverage is 23.30827% with 102 lines in your changes missing coverage. Please review.
✅ Project coverage is 58.89%. Comparing base (7da9814) to head (5aee6e4).

Files with missing lines Patch % Lines
...wnloader/downloadManager/DownloadMonitorService.kt 0.00% 60 Missing ⚠️
...kiwixmobile/core/main/reader/CoreReaderFragment.kt 0.00% 10 Missing ⚠️
...va/org/kiwix/kiwixmobile/main/KiwixMainActivity.kt 27.27% 7 Missing and 1 partial ⚠️
...wnloader/downloadManager/DownloadManagerMonitor.kt 46.66% 6 Missing and 2 partials ⚠️
.../downloadManager/DownloadTimeoutDismissReceiver.kt 0.00% 8 Missing ⚠️
...wixmobile/webserver/wifi_hotspot/HotspotService.kt 0.00% 3 Missing ⚠️
...g/kiwix/kiwixmobile/core/data/KiwixRoomDatabase.kt 50.00% 3 Missing ⚠️
...bile/nav/destination/reader/KiwixReaderFragment.kt 0.00% 2 Missing ⚠️

❌ Your patch check has failed because the patch coverage (23.30%) is below the target coverage (70.00%). You can increase the patch coverage or adjust the target coverage.

Additional details and impacted files
@@             Coverage Diff              @@
##               main    #4502      +/-   ##
============================================
- Coverage     58.94%   58.89%   -0.05%     
- Complexity     1494     1505      +11     
============================================
  Files           319      320       +1     
  Lines         17115    17237     +122     
  Branches       2136     2143       +7     
============================================
+ Hits          10088    10152      +64     
- Misses         5624     5679      +55     
- Partials       1403     1406       +3     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@kelson42
Copy link
Collaborator

Here I have something to highlight first.

to put all the notifications in pause if this "Android timeout" happens

If we put them as a pause notification, the user could not resume the download by clicking on the "Resume" button in the download. Because the service has stopped, so clicking on it will do nothing.

I don't get it, we don't really do it, this is the android timeout triggering this.

Create only one notification for all of them, so cancel/resume all current downloads.

Yes, currently I am doing the same, only creating a single notification and removing all ongoing notifications along with the "Paused" notification(Since the user could not resume the download until they open the application). So, keeping those notifications will confuse users since they cannot restart the download from the notification.

When they click on the "Yes" button, the app will open, and this timer will reset. But before opening the app, the users can not start the download since the time limit is not reset.

So, you will recreate all the download notifications once the he has clicked on "resume"?

* Added a `DownloadTimeoutDismissReceiver` which will listen for the "No" button event and dismiss the notification.
@MohitMaliFtechiz
Copy link
Collaborator Author

MohitMaliFtechiz commented Nov 19, 2025

I don't get it, we don't really do it, this is the android timeout triggering this.

Let me clarify. When the Android background timeout is reached, the system will forcibly kill our foreground service. If we don’t pause the downloads ourselves before this happens, the downloads will be paused anyway, but in a problematic way:
Fetch will throw a timeout error, and the service will be killed immediately, leaving no chance for our DownloadRoomDB to update the actual download states. This results in inconsistent data and unexpected UI behavior when the user returns to the app.
To avoid this situation, we proactively pause all ongoing downloads before the service is terminated, so both Fetch and our database remain in a clean and consistent state.

So, you will recreate all the download notifications once the he has clicked on "resume"?

Yes. When the user taps the “Yes” button, the app launches again. At that moment, we recreate all download notifications in their paused state. Since we navigate the user directly to the “Download” screen, they immediately see that all downloads are paused and can resume them easily.

Edited:

I was thinking that when the user clicks on the "Yes" button, then automatically resume all the paused downloads(When app comes in foreground). But if a user intentionally paused a download, it will also be resumed(Which is not a good idea).

@MohitMaliFtechiz
Copy link
Collaborator Author

@kelson42 I have fixed the "No" button issue. But here, i am seeing some behaviour in our application.

Due to this new limitation, there is a new scenario that comes in Custom apps. Currently, there is no "Pause/Resume" functionality in the application. So if this time limit is reached, it will "Pause" the download, and the user can only have the option to resume, which is in the notification. Our app posts a notification(With paused status) when the app reopens. But in our application, there is no option to "resume" the download. We should give an option for "Resume" the download in our application.

A one issue i am seeing if the download is in progress and we closes the application, and reopens it it keeps us on Reader screen instead of taking to Download screen. Because the ZIM file is exists in storage. however, that ZIM file is not valid. Reader gives a error for that, that is current file is not a valid file. So in this scenario, we should also take user to Download screen.

@kelson42
Copy link
Collaborator

@MohitMaliFtechiz Remember me: why we don't resume automatically?

@MohitMaliFtechiz
Copy link
Collaborator Author

MohitMaliFtechiz commented Nov 20, 2025

@MohitMaliFtechiz, Remember me: why don't we resume automatically?

@kelson42 For custom apps, it can be easily done because there is only a single file to download. But for the Kiwix app, there could be multiple downloads running at a time. And some of them user explicitly paused. So resuming them all automatically is not good IMO. Instead, we should add a param to our DB to track whether the download is paused by the system or user. If paused by the system, after reopening the application, we should resume those downloads automatically.

… Android’s background execution limits.

* Added a `PausedReason` field in `DownloadRoomEntity` so we can automatically resume downloads when the user returns to the application.
* Fixed: In Custom apps, if a download was running and the user closed and reopened the app, the app incorrectly showed the Reader screen with an error stating that the current ZIM file is not valid, instead of showing the `DownloadScreen`.
* Fixed: The “Android background limit” notification remained visible even after the user reopened the application manually.
…showing for not a valid ZIM file which should not show at this time, because download is in progress.

* Updated the methods comment for better understanding.
@MohitMaliFtechiz
Copy link
Collaborator Author

@kelson42, I have updated the videos on the PR description. How automatically starting the downloads works, and how our "Limit" reach notification will look like, and how it's working for both Kiwix and custom apps.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Playstore reported a crash for android.app.RemoteServiceException$ForegroundServiceDidNotStopInTimeException.

4 participants