Foreground services, and when they are justified
There is one way for an app to keep running when the user is not looking at it and have Android leave it alone: say so, in the notification shade, for as long as it runs. That is a foreground service. Music players use one, and so do navigation apps, step counters and voice calls. The tempting conclusion is that Mandi Bhav should use one too, so its refresh can never be stopped. This lesson explains why that would be wrong, what a foreground service costs, the rules Android and Google Play now attach to one, and what each of those rules does when you break it, measured in a scratch app rather than shipped in Mandi Bhav.
Why Mandi Bhav does not get one
Go back to the last lesson's table. A foreground service is for work that is long, started by the user, and must not be interrupted, where the user would notice at once if it stopped. Mandi Bhav's refresh is none of those:
| Question | Mandi Bhav's refresh | A foreground service needs |
|---|---|---|
| Who starts it? | A schedule, at seven | Usually the user, with a tap |
| How long does it run? | A 3.5 KB download; 12 ms on Wi-Fi, a few seconds on weak data | Minutes or hours |
| Would anyone notice a two-hour delay? | No; the prices are for tomorrow morning | Yes: the music stops, the route is lost |
| Is a notification for its whole life honest? | "Downloading 3.5 KB" for a second, every day, is noise | It tells the user something they care about is running |
And WorkManager already delivers what a foreground service would add. The work survives the swipe (lesson 2 measured it), runs when Doze allows, and costs the farmer nothing to look at. A foreground service here would buy one thing, starting a few minutes sooner, and cost a permanent-looking notification, a Play Console declaration and a review. The right number of foreground services for Mandi Bhav is zero. Saying so is part of the job: a senior reviewer will ask why yours exists.
Where one is justified
The test is whether the user would say "stop that, I was using it" if it stopped. Since Android 14 (API 34), every foreground service must declare one of these types:
| Type | Permission to declare | For | Look at first |
|---|---|---|---|
mediaPlayback |
FOREGROUND_SERVICE_MEDIA_PLAYBACK |
Audio or video that keeps playing in the background | Media3's session service |
location |
FOREGROUND_SERVICE_LOCATION |
Navigation, sharing a trip | Geofencing, if you only need to know arrival |
phoneCall |
FOREGROUND_SERVICE_PHONE_CALL |
An ongoing call | android.telecom |
microphone, camera |
FOREGROUND_SERVICE_MICROPHONE, …_CAMERA |
Recording, video chat while multitasking | |
health |
FOREGROUND_SERVICE_HEALTH |
Workout and step tracking | |
connectedDevice |
FOREGROUND_SERVICE_CONNECTED_DEVICE |
A Bluetooth scale, a USB device | Companion device manager |
dataSync |
FOREGROUND_SERVICE_DATA_SYNC |
A large transfer the user asked for: a backup, an export | User-initiated data transfer jobs, WorkManager |
mediaProcessing |
FOREGROUND_SERVICE_MEDIA_PROCESSING |
Converting a video | |
shortService |
None | Finishing something critical that cannot be interrupted, in about three minutes | |
specialUse |
FOREGROUND_SERVICE_SPECIAL_USE |
Genuinely none of the above; reviewed by hand |
Plus the general FOREGROUND_SERVICE permission for all of them. Several types also need a runtime permission granted first: location needs location access, camera the camera permission, and so on. So Kavita, a trader, recording a voice note about tomorrow's orders while she checks prices: microphone. Arjun's cousin exporting a year of ledger entries to a 40 MB file he asked for: a candidate for dataSync, though the alternatives column comes first.
Google Play's part
If your app targets Android 14 or later, you also declare each foreground service type in the Play Console, under App content, with a description of the feature, what the user loses if the system delays or interrupts it, and a link to a video of a user starting it. Google reviews them all, and its help page says the dataSync video cannot show a fully automatic backup: the type is for work the user started. A foreground service that exists to keep an app alive is the kind of thing that review exists to find.
What one looks like
The scratch app, installed under the suffix .m10 so it never touches the real Mandi Bhav, has a dataSync service. In its manifest:
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
<service
android:name=".fgsdemo.DemoSyncService"
android:exported="false"
android:foregroundServiceType="dataSync" />
and the service:
class DemoSyncService : Service() {
override fun onBind(intent: Intent?): IBinder? = null
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
val notification = NotificationCompat.Builder(this, PRICE_ALERTS_CHANNEL_ID)
.setSmallIcon(R.drawable.ic_alerts)
.setContentTitle("Uploading the ledger")
.setOngoing(true)
.build()
ServiceCompat.startForeground(this, 1001, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC)
// … start the upload, and call stopSelf() when it is done …
return START_NOT_STICKY
}
override fun onTimeout(startId: Int, fgsType: Int) {
// Android 15 and later: the time limit for this type is up.
stopSelf()
}
}
started from the activity, while it is on screen, with ContextCompat.startForegroundService(context, Intent(context, DemoSyncService::class.java)). The service then has a few seconds to call startForeground, with a notification and a type. ServiceCompat.startForeground passes the type on Android 10 and later and leaves it out on older versions, which do not take one. The notification channel here is borrowed from Mandi Bhav; a real app would give the service its own channel, and lesson 4 covers channels. The system logged the start:
I/ActivityManager: Background started FGS: Allowed [callingPackage: com.riztechacademy.mandibhav.m10; … uidState: TOP ; … code:PROC_STATE_TOP; …]
TOP: the app was on screen, which is what made the start allowed.
Three rules, broken on purpose
No type. A second service, declared without android:foregroundServiceType, calling startForeground(1002, notification):
E/AndroidRuntime: FATAL EXCEPTION: main
E/AndroidRuntime: java.lang.RuntimeException: Unable to start service com.riztechacademy.mandibhav.fgsdemo.DemoNoTypeService@dbcb144 …: android.app.MissingForegroundServiceTypeException: Starting FGS without a type callerApp=ProcessRecord{13d80a1 1842:com.riztechacademy.mandibhav.m10/u0a239} targetSDK=36
A crash, at the moment the user tapped the button. Older code you inherit, written before Android 14, is often exactly this.
Starting it from the background. The same dataSync service, started from a broadcast receiver while the app was not on screen:
W/ActivityManager: Background started FGS: Disallowed [callingPackage: com.riztechacademy.mandibhav.m10; … uidState: RCVR; … code:DENIED; …]
I/M10Fgs: startForegroundService from the background threw android.app.ForegroundServiceStartNotAllowedException: startForegroundService() not allowed due to mAllowStartForeground false: service com.riztechacademy.mandibhav.m10/com.riztechacademy.mandibhav.fgsdemo.DemoSyncService
Since Android 12, an app may start a foreground service only while it is visible, or in a short list of exempt situations. This is why a seven o'clock refresh could not use one even if it wanted to: at seven, nothing is on the screen.
Running past the time limit. For apps targeting Android 15 or later, dataSync and mediaProcessing services may run for six hours in any 24 while the app is in the background, each type counted separately, and the clock resets when the user brings the app to the front. shortService gets about three minutes. Six hours is a long time to wait on an emulator, so Android lets you shorten it for testing:
adb shell device_config put activity_manager data_sync_fgs_timeout_duration 30000
Start the service, press Home:
00:11:29.181 I/M10Fgs: started as dataSync, ignoreTimeout=false
00:12:03.624 E/ActivityManager: FGS (dataSync) timed out: ServiceRecord{f4420b0 u0 com.riztechacademy.mandibhav.m10/…DemoSyncService …}
00:12:03.627 I/M10Fgs: onTimeout(startId=1, fgsType=1) after 34446 ms
00:12:03.633 I/M10Fgs: stopped
Thirty seconds configured, 34.4 measured, and onTimeout stopped the service cleanly. Then the same service told to ignore the callback:
00:40:44.362 I/M10Fgs: onTimeout(startId=1, fgsType=1) after 33954 ms
00:40:54.384 E/AndroidRuntime: android.app.RemoteServiceException$ForegroundServiceDidNotStopInTimeException: A foreground service of type dataSync did not stop within its timeout: ComponentInfo{com.riztechacademy.mandibhav.m10/com.riztechacademy.mandibhav.fgsdemo.DemoSyncService}
Ten seconds of grace, then the app is killed with a crash that counts in Android vitals. Put the setting back afterwards:
adb shell device_config delete activity_manager data_sync_fgs_timeout_duration
| Rule | Since | Broken, you get |
|---|---|---|
Declare a type in the manifest and pass it to startForeground |
Android 14 (API 34) target | MissingForegroundServiceTypeException |
| Declare the type's permission, and hold its runtime permission | Android 14 target | SecurityException at startForeground |
| Start only while visible, or exempt | Android 12 (API 31) target | ForegroundServiceStartNotAllowedException |
Call startForeground within a few seconds of startForegroundService |
Android 8 | The app is killed |
dataSync, mediaProcessing: 6 hours in 24 in the background; shortService: about 3 minutes |
Android 15 (API 35) target; shortService from 14 |
onTimeout, then ForegroundServiceDidNotStopInTimeException |
| Declare each type in the Play Console | Android 14 target | Rejection or removal from Play |
The first, third and fifth rows were each broken on the emulator above; the second and fourth come from Google's documentation and were not tested here.
Before you write one
For the transfers people most often reach for dataSync to do, Android now offers something better. User-initiated data transfer jobs, from Android 14, are JobScheduler jobs the user explicitly started: they show a notification, can run long, and the system tracks network constraints and wake locks for you; Google's Play help page recommends them over dataSync for user-requested transfers. WorkManager can also run a long worker as a foreground service by calling setForeground() from inside doWork, with the same type rules, and retries it when it is stopped. Use a raw foreground service when neither fits, and you will find that is rarer than tutorials suggest.
Check your work
Why Mandi Bhav has no foreground service. Its refresh is scheduled, tiny, and fine to delay; WorkManager already survives the swipe, and a foreground service would add a notification and a Play declaration for nothing.
What is required since Android 14. A type in the manifest and in startForeground, the type's permission, any runtime permission it needs, and a Play Console declaration.
What happened without a type. MissingForegroundServiceTypeException, a crash when the service started.
Why a seven o'clock refresh could not start one. Starting from the background was refused: ForegroundServiceStartNotAllowedException, state RCVR.
What the time limit does. With 30 seconds configured, onTimeout came at 34.4 seconds; ignoring it crashed the app ten seconds later.
Practice 3. The start from an activity on screen logged uidState: TOP and code:PROC_STATE_TOP, allowed. The same call ten seconds after Home, from the same activity's postDelayed, logged Background started FGS: Disallowed [… uidState: LAST; … code:DENIED; …] and threw ForegroundServiceStartNotAllowedException. The code was identical; only whether the user could see the app had changed.
Practice
- List five apps on your phone that show a persistent notification. For each, guess the foreground service type and decide whether you, as a user, would want it to keep running.
- In a scratch copy with a different
applicationIdSuffix, addDemoSyncServiceas above and start it from a button. Find the "Background started FGS" line in logcat and read why it was allowed. - Start the same service ten seconds after pressing Home, using
Handler(Looper.getMainLooper()).postDelayed { … }from the activity. Read the exception. - Break it on purpose: remove
android:foregroundServiceTypefrom the manifest, then removeFOREGROUND_SERVICE_DATA_SYNCinstead. Read each failure. Put both back. - Shorten the
dataSynclimit withdevice_config, start the service, press Home and timeonTimeout. Then makeonTimeoutdo nothing and read the crash. Delete the setting afterwards. - Harder: Kavita wants to send her day's purchase receipts, 30 photos at about 2 MB each, to her accountant when she taps "Send all". Write a one-page design: WorkManager with
setForeground, a user-initiated data transfer job, or adataSyncservice. Say what happens when her signal drops for ten minutes in the middle, and what the notification says.
Official documentation
- Foreground service types — Every type, its permission, its runtime prerequisites and its recommended alternatives.
- Foreground service types are required — The Android 14 change, and how to migrate an older service.
- Foreground service timeouts — The six-hour limit,
onTimeout, and the adb commands for testing it. - Restrictions on starting a foreground service from the background — When a start is allowed, and the exemptions.
- Understanding foreground service requirements on Google Play — What the Play Console declaration asks for, type by type.
Next: notifications, channels and the permission to send them, for Mandi Bhav's price alerts.
Stuck on this lesson?
Being stuck is part of it — but being stuck alone for three days is not. Selected students who finish a course can join our internship, with real-world projects and code review from a working engineer.
About the internship