Android 14 (API 34) makes the foregroundServiceType mandatory for every
foreground service. Android 15 (API 35) introduces a new type
mediaProcessing specifically for image/video transcoding workloads. If your
worker declares the wrong type, the OS throws
ForegroundServiceStartNotAllowedException at runtime — no build-time warning.
This guide pins down the right type per camera-app workload, plus the manifest snippets you need to paste into the host app.
Note:
KmpWorkManageradheres to a strict "Opt-In Permissions" rule. We do not automatically declareFOREGROUND_SERVICE*permissions in the library manifest. You must manually declare these permissions and the<service>tag in your app'sAndroidManifest.xmlif you useKmpHeavyWorkerto avoid Play Store review rejections for apps that don't need them.
| Workload | Type constant | Min API | Manifest permission |
|---|---|---|---|
| HTTP upload / download / sync | FGS_DATA_SYNC (default) |
29 | FOREGROUND_SERVICE_DATA_SYNC (API 34+) |
| Image / video transcoding | FGS_MEDIA_PROCESSING |
35 | FOREGROUND_SERVICE_MEDIA_PROCESSING |
| Audio / video playback | FGS_MEDIA_PLAYBACK |
29 | FOREGROUND_SERVICE_MEDIA_PLAYBACK |
| Camera capture session | FGS_CAMERA |
29 | FOREGROUND_SERVICE_CAMERA |
| GPS / geofence | FGS_LOCATION |
29 | FOREGROUND_SERVICE_LOCATION |
| Bluetooth peripheral | FGS_CONNECTED_DEVICE |
29 | FOREGROUND_SERVICE_CONNECTED_DEVICE |
Wrong-type symptoms in production:
- Android 14+ device,
dataSyncdeclared butmediaProjectionactually used →SecurityException: Starting FGS with type mediaProjection ... requires permissions. - Android 15+ device, image compression with
dataSync→ still runs, but Play Store policy review flags the app for misusing the type. UsemediaProcessing. dataSyncon a 6+ hour upload — Android 15 limitsdataSyncto 6 hours of cumulative runtime per 24h window. Heavy nightly sync workloads hit this.
KmpHeavyWorker.foregroundServiceType defaults to FGS_DATA_SYNC. Override it:
// Image / video compression worker
class TranscodeWorker(
appContext: Context,
params: WorkerParameters,
factory: AndroidWorkerFactory,
) : KmpHeavyWorker(appContext, params, factory) {
override val foregroundServiceType: Int
get() = if (Build.VERSION.SDK_INT >= 35) {
FGS_MEDIA_PROCESSING
} else {
FGS_DATA_SYNC // pre-Android-15 fallback
}
}The guard against Build.VERSION.SDK_INT >= 35 is required —
FOREGROUND_SERVICE_TYPE_MEDIA_PROCESSING is API 35+; declaring it on older
devices is a no-op at best, a crash at worst.
<manifest ...>
<!-- Android 14+ -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<!-- Android 15+: required when FGS type is mediaProcessing -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PROCESSING" />
<application>
<!-- WorkManager's foreground service. Type MUST match what your worker passes
to ForegroundInfo. Manifest type can be multi-valued ("dataSync|mediaProcessing"). -->
<service
android:name="androidx.work.impl.foreground.SystemForegroundService"
android:foregroundServiceType="dataSync|mediaProcessing"
tools:node="merge" />
</application>
</manifest><uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_CAMERA" />
<uses-permission android:name="android.permission.CAMERA" /> <!-- runtime-requested -->
<service
android:name="androidx.work.impl.foreground.SystemForegroundService"
android:foregroundServiceType="dataSync|camera"
tools:node="merge" /><uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<service
android:name="androidx.work.impl.foreground.SystemForegroundService"
android:foregroundServiceType="dataSync|location"
tools:node="merge" />Some FGS types require a runtime permission grant on top of the manifest entry.
For example, FGS_CAMERA does nothing without CAMERA. Verify before scheduling:
if (Build.VERSION.SDK_INT >= 34 && ActivityCompat.checkSelfPermission(
context, Manifest.permission.CAMERA
) != PackageManager.PERMISSION_GRANTED) {
// Don't schedule — system would kill the service immediately.
return
}
scheduler.enqueueTask(/* ... */)Android 15 enforces a cumulative 6-hour-per-day cap on dataSync FGS runtime.
When the cap is hit:
setForeground()throwsForegroundServiceTypeExceptionon subsequent runs.- WorkManager marks the work as
FAILED(does NOT retry).
For long-running uploads (camera RAW backup), prefer chunked work + a constraint
on requiresUnmeteredNetwork so each worker instance is short-lived. The
6-hour clock resets at midnight device-local time.
android:foregroundServiceType accepts a |-separated list. Declare every type
that any of your workers might use:
android:foregroundServiceType="dataSync|mediaProcessing|camera|connectedDevice"The OS picks the intersection of the manifest declaration AND the type passed
to ForegroundInfo. Declaring extras in the manifest is harmless; missing one
is a crash.
There is no Robolectric stub for the type check — it's a system-level guard. Integration testing approach:
- Connect a real Android 14+ device.
adb shell cmd appops set <pkg> START_FOREGROUND denyto simulate a denied FGS.adb shell dumpsys activity processes <pkg>after scheduling — check theforegroundServiceTypefield on the running service.- For Android 15 6-hour cap testing:
adb shell device_config put activity_manager_native_boot fgs_data_sync_max_duration_ms 60000(drops the cap to 1 minute for the next boot).
- Android docs — Foreground service types
- Android 15 dataSync cap
KmpHeavyWorker.foregroundServiceTypeKDoc — overrideable in subclasses