Service and foreground notifications

Alex Chen Jan 2026
1 tab
package com.example.myapp.services

import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.app.Service
import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.IBinder
import androidx.core.app.NotificationCompat
import com.example.myapp.R
import com.example.myapp.ui.MainActivity
import kotlinx.coroutines.*
import java.io.File

class UploadService : Service() {

    private val serviceScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
    private val notificationId = 2001

    companion object {
        private const val CHANNEL_ID = "upload_channel"
        const val ACTION_START_UPLOAD = "action_start_upload"
        const val ACTION_CANCEL_UPLOAD = "action_cancel_upload"
        const val EXTRA_FILE_PATH = "extra_file_path"

        fun startUpload(context: Context, filePath: String) {
            val intent = Intent(context, UploadService::class.java).apply {
                action = ACTION_START_UPLOAD
                putExtra(EXTRA_FILE_PATH, filePath)
            }
            context.startService(intent)
        }
    }

    override fun onCreate() {
        super.onCreate()
        createNotificationChannel()
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        when (intent?.action) {
            ACTION_START_UPLOAD -> {
                val filePath = intent.getStringExtra(EXTRA_FILE_PATH)
                filePath?.let { startUpload(it) }
            }
            ACTION_CANCEL_UPLOAD -> {
                stopSelf()
            }
        }
        return START_NOT_STICKY
    }

    private fun startUpload(filePath: String) {
        val notification = createNotification("Uploading...", 0)
        startForeground(notificationId, notification)

        serviceScope.launch {
            try {
                val file = File(filePath)
                uploadFile(file) { progress ->
                    updateNotification("Uploading...", progress)
                }

                updateNotification("Upload complete", 100)
                delay(2000)
                stopForeground(true)
                stopSelf()
            } catch (e: Exception) {
                updateNotification("Upload failed", 0)
                delay(2000)
                stopForeground(true)
                stopSelf()
            }
        }
    }

    private suspend fun uploadFile(file: File, onProgress: (Int) -> Unit) {
        // Simulate upload progress
        for (i in 0..100 step 10) {
            delay(500)
            onProgress(i)
        }
    }

    private fun createNotification(title: String, progress: Int): android.app.Notification {
        val intent = Intent(this, MainActivity::class.java)
        val pendingIntent = PendingIntent.getActivity(
            this,
            0,
            intent,
            PendingIntent.FLAG_IMMUTABLE
        )

        val cancelIntent = Intent(this, UploadService::class.java).apply {
            action = ACTION_CANCEL_UPLOAD
        }
        val cancelPendingIntent = PendingIntent.getService(
            this,
            0,
            cancelIntent,
            PendingIntent.FLAG_IMMUTABLE
        )

        return NotificationCompat.Builder(this, CHANNEL_ID)
            .setContentTitle(title)
            .setSmallIcon(R.drawable.ic_upload)
            .setProgress(100, progress, progress == 0)
            .setContentIntent(pendingIntent)
            .addAction(R.drawable.ic_cancel, "Cancel", cancelPendingIntent)
            .setOngoing(true)
            .build()
    }

    private fun updateNotification(title: String, progress: Int) {
        val notification = createNotification(title, progress)
        val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE)
            as NotificationManager
        notificationManager.notify(notificationId, notification)
    }

    private fun createNotificationChannel() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val channel = NotificationChannel(
                CHANNEL_ID,
                "File Uploads",
                NotificationManager.IMPORTANCE_LOW
            ).apply {
                description = "Shows progress of file uploads"
            }

            val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE)
                as NotificationManager
            notificationManager.createNotificationChannel(channel)
        }
    }

    override fun onBind(intent: Intent?): IBinder? = null

    override fun onDestroy() {
        super.onDestroy()
        serviceScope.cancel()
    }
}
1 file · kotlin Explain with highlit

Services run background operations without UI. I extend Service and override onStartCommand() for started services or onBind() for bound services. Foreground services use startForeground() with ongoing notification, required for long-running tasks visible to users. START_STICKY restarts service after system kills it. IntentService handles async work on worker threads automatically. Bound services enable client-server IPC with Binder. JobIntentService replaces IntentService for API 26+. Services require manifest declaration and runtime permissions. WorkManager or foreground services replaced many background service use cases. Foreground services ensure critical operations—music playback, navigation, file uploads—aren't killed, while keeping users informed.