Skip to main content

11 - Foreground Service

Foreground Service

Foreground services perform operations that are noticeable to the user - music player, tracking location, etc. Showing a status bar notification is mandatory to notify user of the ongoing activity.

Only use a foreground service when your app needs to perform a task that is noticeable by the user, even when they're not directly interacting with the app.

From api 33 foreground notification is dismissable by user. Use ongoing(true) if you wan non-dismissable notification.

Api 31 waits 10 seconds before showin notification in some cases (short lived service).

Exceptions are

  • notification has action buttons
  • service is of type mediaPlayback, mediaProjection, or phoneCall
  • notification category is connected to phone calls, navigation, or media playback
  • passing FOREGROUND_SERVICE_IMMEDIATE to setForegroundServiceBehavior()

On Android 13 (API level 33) or higher, if the user denies the notification permission, they still see notices related to foreground services in the Task Manager but don't see them in the notification drawer.

Declare foreground service in manifest

<service
android:name=".MyService"
android:foregroundServiceType="mediaPlayback"
android:exported="false">
</service>

If you have mutiple types in the same service - use |. Declaring foregroundServiceType is mandatory starting from Api 34.

android:foregroundServiceType="camera|microphone"

From api 28 - permission FOREGROUND_SERVICE is needed. From api 34 also every service type needs permission.

<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_CAMERA"/>

These are normal permissions - granted during install time. Missing these declarations - SecurityException is thrown.

Start a foreground service

From activity create the notification channel, check the permissions and start the foreground service.

companion object {
private val TAG = this::class.java.declaringClass!!.simpleName
const val NOTIFICATION_CHANNEL_ID = "default"
}

private fun createNotificationChannel() {
val notificationManager =
getSystemService(Service.NOTIFICATION_SERVICE) as NotificationManager

// create the notification channel
val channel = NotificationChannel(
NOTIFICATION_CHANNEL_ID,
NOTIFICATION_CHANNEL_ID,
// no sound
NotificationManager.IMPORTANCE_DEFAULT
)
notificationManager.createNotificationChannel(channel)
}

private fun launchForegroundService() {
if (ContextCompat.checkSelfPermission(
applicationContext,
Manifest.permission.FOREGROUND_SERVICE
) != PackageManager.PERMISSION_GRANTED
) {
textViewInfo.text = "Missing permissions!"
return
}

val serviceIntent = Intent(this, ForegroundService::class.java)
startForegroundService(serviceIntent)
}

From the service request the foreground, usually in onStartCommand()

ServiceCompat.startForeground(...) 

Check versions in lib.versions.toml or build.gradle.kts. ServiceCompat.startForeground was missing in androidx.core.app version generated app initially had.

[versions]
agp = "8.7.1"
kotlin = "2.0.21"
coreKtx = "1.13.1"
junit = "4.13.2"
junitVersion = "1.1.5"
espressoCore = "3.5.1"
appcompat = "1.7.0"
material = "1.12.0"
activity = "1.9.3"
constraintlayout = "2.1.4"

startForeground should be called within 5 seconds!
If not, then exception is thrown...

ForegroundServiceDidNotStartInTimeException: 
Context.startForegroundService() did not then call Service.startForeground()

Parameters are

  • The service
  • A positive integer that uniquely identifies the notification in the status bar
  • The Notification object itself
  • The foreground service types identifying the work done by the service
class ForegroundService : Service() {
companion object {
private val TAG = this::class.java.declaringClass!!.simpleName
const val NOTIFICATION_ID = 100
}

override fun onBind(intent: Intent?): IBinder? {
return null
}

override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
startFgService()
startTimerService()
return START_STICKY
}

private fun startFgService() {
ServiceCompat.startForeground(
this,
NOTIFICATION_ID,
buildNotification(this, "Foreground Service", "Foreground Service running"),
ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE
)
}

private fun buildNotification(context: Context, title: String, content: String): Notification {
val notificationIntent = Intent(
context,
MainActivity::class.java
)
val pendingIntent = PendingIntent.getActivity(
context,
123,
notificationIntent,
PendingIntent.FLAG_IMMUTABLE
)

return NotificationCompat.Builder(context, MainActivity.NOTIFICATION_CHANNEL_ID)
.setContentTitle(title)
.setContentText(content)
.setSmallIcon(R.drawable.ic_launcher_foreground)
.setForegroundServiceBehavior(NotificationCompat.FOREGROUND_SERVICE_IMMEDIATE)
.setContentIntent(pendingIntent)
.setOngoing(true)
.setSound(null)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.build()
}

private val scheduledExecutorService = Executors.newScheduledThreadPool(1)

private fun startTimerService() {
scheduledExecutorService.scheduleWithFixedDelay(
{
if (ActivityCompat.checkSelfPermission(
this,
Manifest.permission.POST_NOTIFICATIONS
) == PackageManager.PERMISSION_GRANTED
) {
val notification =
buildNotification(this, "Foreground Service", Date().toString())
NotificationManagerCompat.from(this).notify(NOTIFICATION_ID, notification)
}
},
0, // initialDelay
5, // period
TimeUnit.SECONDS
)
}
}

Background app issues

If your app is in background (ie no visible UI) - you mostly cannot start foreground service. While-in-use permissions will fail (location, microphone, camera, etc). There is special permission ACCESS_BACKGROUND_LOCATION for location that gives exempt for location services.

Foreground service types

android:foregroundServiceType

  • camera - access the camera from the background
  • connectedDevice - interactions with Bluetooth, NFC, IR, USB, or network connection
  • dataSync - data transfer operations
  • health - exercise trackers
  • location - navigation and location sharing
  • mediaPlayback - audio or video playback
  • mediaProcessing - time-consuming operations on media assets
  • mediaProjection - project content to non-primary display
  • microphone - microphone capture from the background
  • phoneCall - continue an ongoing call
  • remoteMessaging - transfer text messages from one device to another.
  • shortService - quickly finish critical work (ca 3 minutes max)
  • specialUse - any other reason
  • systemExempted - not for mortals

Permission in manifest

  • FOREGROUND_SERVICE_CAMERA
  • FOREGROUND_SERVICE_CONNECTED_DEVICE
  • FOREGROUND_SERVICE_DATA_SYNC
  • FOREGROUND_SERVICE_HEALTH
  • FOREGROUND_SERVICE_LOCATION
  • FOREGROUND_SERVICE_MEDIA_PLAYBACK
  • FOREGROUND_SERVICE_MEDIA_PROCESSING
  • FOREGROUND_SERVICE_MEDIA_PROJECTION
  • FOREGROUND_SERVICE_MICROPHONE
  • FOREGROUND_SERVICE_PHONE_CALL
  • FOREGROUND_SERVICE_REMOTE_MESSAGING
  • FOREGROUND_SERVICE_SPECIAL_USE

For startForeground() use the similar constant FOREGROUND_SERVICE_TYPE_<>

specialUse - any valid foreground service use cases that aren't covered by the other foreground service types. Also add explanation:

<service android:name="fooService" android:foregroundServiceType="specialUse">
<property android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
android:value="explanation_for_special_use"/>
</service>

If a use case in your app isn't associated with any of these predefined types, migrate your logic to use WorkManager or user-initiated data transfer jobs.

NB! Google play store might need explanations and special policy agreements for certain service types.

System also checks from needed runtime permissions connected to specific services - a'la location also needs ACCESS_FINE_LOCATION and ACCESS_COARSE_LOCATION. Microphone needs RECORD_AUDIO, etc.

https://developer.android.com/develop/background-work/services/fg-service-types