Firebase Android

Firebase FCM Deep Dive:
Background Push Commands

By Ankit Kumar, Founder of Kyvronix Technologies · 14 min read

Push notifications are easy when you just want to show a message to the user. But what if you want to use Firebase Cloud Messaging (FCM) to trigger background code on an Android device without the user ever seeing a notification? In PhoneGuard, when a user clicks "Lock Device" from the web dashboard, the device locks instantly via an FCM silent push.

Here is how to properly implement data-only FCM payloads to wake up an Android app in the background in 2026.

1. Notification vs Data Payloads

The biggest mistake developers make is sending a "Notification" payload. If your FCM JSON includes a "notification" key, the Android OS intercepts it. If your app is in the background or killed, the OS will display the notification in the system tray, and your app's code will not run until the user taps it.

To run background code, you must send a Data-only payload.

// Correct JSON payload from your backend
{
  "message": {
    "token": "device_fcm_token_here",
    "data": {
      "command": "LOCK_DEVICE",
      "timestamp": "1719230000"
    }
    // Notice: NO "notification" object here!
  }
}
  

2. Handling the Message in Kotlin

When a data-only message arrives, Firebase triggers your custom FirebaseMessagingService.

import com.google.firebase.messaging.FirebaseMessagingService
import com.google.firebase.messaging.RemoteMessage
import android.content.Intent

class PhoneGuardFcmService : FirebaseMessagingService() {

    override fun onMessageReceived(remoteMessage: RemoteMessage) {
        super.onMessageReceived(remoteMessage)

        // Check if message contains a data payload
        if (remoteMessage.data.isNotEmpty()) {
            val command = remoteMessage.data["command"]
            
            when (command) {
                "LOCK_DEVICE" -> executeLockCommand()
                "START_ALARM" -> triggerSiren()
                "GET_LOCATION" -> startLocationService()
            }
        }
    }

    override fun onNewToken(token: String) {
        // Send the new token to your backend
        sendRegistrationToServer(token)
    }
}
  

3. The Execution Window Constraint

When onMessageReceived is called while your app is in the background, Android grants your app a very short execution window (usually 10-20 seconds). If you need to do something that takes longer (like acquiring a GPS lock or downloading a file), you cannot do it directly inside this method.

For long-running tasks triggered by FCM, you must start a Foreground Service or schedule a WorkManager task immediately.

private fun startLocationService() {
    val intent = Intent(this, TrackingService::class.java)
    
    // On Android 8.0+, you must use startForegroundService
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        startForegroundService(intent)
    } else {
        startService(intent)
    }
}
  

Note: Starting a Foreground Service from the background is heavily restricted in Android 12+. However, FCM data messages designated as "High Priority" grant your app a temporary exemption to these rules.

4. The High Priority Exemption

To ensure your data payload wakes up the device from Doze mode and grants you the ability to start a Foreground Service, you must set the priority to high in your backend payload.

// Backend payload snippet
{
  "message": {
    "token": "...",
    "data": { ... },
    "android": {
      "priority": "high"
    }
  }
}
  

Conclusion

FCM Data payloads act as a remote control for your Android applications. By strictly avoiding the notification object, utilizing High Priority flags, and properly delegating long tasks to Foreground Services, you can build incredibly responsive remote-control applications.

— Ankit Kumar