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
, orphoneCall
- 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 backgroundconnectedDevice
- interactions with Bluetooth, NFC, IR, USB, or network connectiondataSync
- data transfer operationshealth
- exercise trackerslocation
- navigation and location sharingmediaPlayback
- audio or video playbackmediaProcessing
- time-consuming operations on media assetsmediaProjection
- project content to non-primary displaymicrophone
- microphone capture from the backgroundphoneCall
- continue an ongoing callremoteMessaging
- transfer text messages from one device to another.shortService
- quickly finish critical work (ca 3 minutes max)specialUse
- any other reasonsystemExempted
- 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