Skip to content

Make Android data sync foreground work policy-compliant#11

Merged
Promises merged 1 commit into
Promises:mainfrom
kvmgithub:fix-android-fgs-compliance-length-sort
May 15, 2026
Merged

Make Android data sync foreground work policy-compliant#11
Promises merged 1 commit into
Promises:mainfrom
kvmgithub:fix-android-fgs-compliance-length-sort

Conversation

@kvmgithub
Copy link
Copy Markdown

@kvmgithub kvmgithub commented May 14, 2026

Summary

This change updates LibriSync's Android background transfer behavior for current foreground-service policy requirements and adds library sorting by audiobook length.

What changed

  • Removed the app-owned BootReceiver and its RECEIVE_BOOT_COMPLETED registration so the app no longer starts a dataSync foreground service from boot or package replacement broadcasts.
  • Removed BackgroundTaskService from the Android manifests and Expo config plugin as a registered dataSync foreground service.
  • Kept periodic token refresh and periodic library sync on WorkManager, which already provides the system-managed scheduling behavior needed after device restart.
  • Routed immediate, user-requested library sync through a one-time WorkManager request instead of starting a persistent foreground service.
  • Kept DownloadService as the only app-owned dataSync foreground service path for explicit download/conversion work.
  • Made DownloadService non-sticky so Android does not restart it later without an explicit user action intent.
  • Declared the runtime foreground service type with FOREGROUND_SERVICE_TYPE_DATA_SYNC when starting the download foreground service.
  • Added Service.onTimeout() handling so Android 15+ dataSync timeouts pause active downloads and stop the service cleanly.
  • Raised the local Expo Rust bridge Android module compile/target SDK to 36 to match the app target.
  • Added Length as a library sort option in the React Native UI, JNI mapping, Rust storage query layer, and Rust test coverage.

Why

Android 15 blocks several foreground service types from being started from BOOT_COMPLETED, including dataSync. If a boot receiver starts such a service, Android throws ForegroundServiceStartNotAllowedException.

Android 15 also introduces a time budget for dataSync foreground services while the app is in the background. Once that budget is exhausted, the system calls Service.onTimeout(), and the service must stop itself promptly.

Android 16 further tightens background job behavior, so long-running or restart-oriented background work should use system-managed scheduling where possible. In this codebase, periodic sync work fits WorkManager, while direct audiobook downloads remain an explicit user-visible transfer path.

Relevant Android documentation:

User impact

  • The app should no longer crash on Android 15/16 when the device boots or the package is replaced.
  • Background sync behavior remains scheduled through WorkManager instead of a persistent foreground service.
  • Explicit audiobook downloads still use an ongoing notification and foreground execution while active.
  • If Android reports that the dataSync foreground-service budget has been exhausted, active downloads are paused before the service exits, preserving resumability.
  • Users can now sort the Library view by audiobook length in addition to title, release date, date added, and series.

Validation

  • npm run typecheck
  • ./gradlew :expo-rust-bridge:compileDebugKotlin :app:processDebugMainManifest
  • ./gradlew :expo-rust-bridge:processDebugManifest :app:processDebugManifest --rerun-tasks
  • cargo test storage::queries::tests::test_list_books_with_filters_sorts_by_length
  • git diff --check
  • npm run build:rust:android
  • npx expo run:android
  • ./gradlew :app:assembleRelease
  • Verified the Library view can sort by Length.
  • Verified the Android foreground-service crash no longer occurs after restarting the phone.

After regenerating the merged debug manifests, the old app-owned BootReceiver and expo.modules.rustbridge.tasks.BackgroundTaskService are no longer present. The remaining BOOT_COMPLETED entry is from androidx.work, which is expected for WorkManager rescheduling.

Remove the boot-completed dataSync foreground service startup path and route immediate library syncs through WorkManager. Keep the download service as the only dataSync foreground service, make it non-sticky, declare the runtime service type, and pause work on Android 15 foreground-service timeout. Add library sorting by audiobook length across UI and Rust storage.
@Promises Promises merged commit b84e0c8 into Promises:main May 15, 2026
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.

2 participants