RizTech Academy logo
RizTech Academy
Work That Outlives the ScreenLesson 3 of 435 min

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

  1. 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.
  2. In a scratch copy with a different applicationIdSuffix, add DemoSyncService as above and start it from a button. Find the "Background started FGS" line in logcat and read why it was allowed.
  3. Start the same service ten seconds after pressing Home, using Handler(Looper.getMainLooper()).postDelayed { … } from the activity. Read the exception.
  4. Break it on purpose: remove android:foregroundServiceType from the manifest, then remove FOREGROUND_SERVICE_DATA_SYNC instead. Read each failure. Put both back.
  5. Shorten the dataSync limit with device_config, start the service, press Home and time onTimeout. Then make onTimeout do nothing and read the crash. Delete the setting afterwards.
  6. 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 a dataSync service. Say what happens when her signal drops for ten minutes in the middle, and what the notification says.

Official documentation

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