I have an Android app configured to receive push notifications through Firebase and I'm having issues making it work while the phone is in Doze mode.
The application correctly receives push notifications, regardless if it is in foreground or in background. To do this, I am using only the data
field in the push notification to be able to handle anything that comes in regardless of the status of the app.
I have implemented my service to receive notifications as follows:
class MyFirebaseMessagingService : FirebaseMessagingService() {
override fun onMessageReceived(p0: RemoteMessage?) {
Timber.d("Push notification received")
super.onMessageReceived(p0)
when (p0!!.data["ch"]) {
NotificationType.VoIP.channelType() -> handleVoIPNotification(p0.data)
NotificationType.Push.channelType() -> handlePushNotification(p0.data)
}
}
}
The ch
property defines the type of notification and is sent from my backend: since my app has a video-calling function, when someone is calling the backend sends out a notification with ch = voip
and sets the message priority to high
, as documented in the Firebase guide.
The handleVoIPNotification
function contains this:
private fun handleVoIPNotification(data: Map<String, String>) {
val gson = Gson()
val jsonElement = gson.toJsonTree(data)
try {
val voIPNotification = gson.fromJson(jsonElement, VoIPNotification::class.java)
Timber.i("VoIP Notification received: %s", voIPNotification.action.name)
// pass the incoming call data to the Call Manager.
CallManager.getInstance().handleNotification(voIPNotification)
} catch (exc: Exception) {
Timber.e(exc)
Timber.i("Invalid VoIP notification received: %s", data)
}
}
The Call Manager then updates a property named currentCall
and terminates:
this.currentCall.apply {
token = notification.token
roomName = notification.room
username = notification.nickname
status.value = Call.CallStatus.Ringing
}
The status
property is a BehaviorSubject
implementation that is observed by another object that reacts to changes of the status of the call:
currentCall.status.observable
.distinctUntilChanged()
.subscribe {
Timber.d("Call status changed: $it")
when (it) {
Call.CallStatus.Ringing -> {
this.showIncomingCallNotification()
}
Call.CallStatus.Declined, Call.CallStatus.Ended -> {
this.dismissIncomingCallNotification()
this.refreshIncomingCallActivity()
}
Call.CallStatus.Connecting -> {
this.dismissIncomingCallNotification()
this.presentOngoingCallActivity()
}
else -> { /* ignored */ }
}
}.disposedBy(this.disposeBag)
The showIncomingCallNotification
is the following:
fun showIncomingCallNotification() {
val intent = Intent(Intent.ACTION_MAIN, null).apply {
flags = Intent.FLAG_ACTIVITY_NO_USER_ACTION or Intent.FLAG_ACTIVITY_NEW_TASK
setClass(configuration.context, configuration.incomingCallActivityType.java)
}
val pendingIntent = PendingIntent.getActivity(configuration.context, configuration.requestCode, intent, 0)
val builder = NotificationCompat.Builder(configuration.context, configuration.notificationChannel)
.setOngoing(true)
.setContentIntent(pendingIntent)
.setFullScreenIntent(pendingIntent, true)
.setSmallIcon(configuration.notificationIcon)
.setContentTitle(currentCall.username)
.setContentText(configuration.context.getString(configuration.notificationText))
val notification = builder.build()
notification.flags = notification.flags or Notification.FLAG_INSISTENT
configuration.notificationsManager.getSystemNotificationManager().notify(0, notification)
}
This code shows a notification which, when pressed, opens the IncomingCallActivity and lets the user accept or reject the call. This notification is also responsible of making the phone ring and vibrate.
All of this works perfectly when the app is opened in foreground or in the background, as long as the screen is on or it has just been turned off. If I wait a while (from 5 minutes to hours, it depends), everything stops working: my Firebase service is not called when a push notification is sent from my backend (and I can see that the push notification has been correctly sent). Turning on the screen, makes the incoming call notification appear correctly.
I have logs in place that clearly show that the Firebase service onMessageReceived
function is not even called until I turn on the screen.
I have read a thousand times the Android Developers - Optimize for Doze and App Standby and they clearly state that FCM with High priority messages is the correct approach for waking up an app when the phone is in idle.
FCM is optimized to work with Doze and App Standby idle modes by means of high-priority FCM messages. FCM high-priority messages let you reliably wake your app to access the network, even if the user’s device is in Doze or the app is in App Standby mode. In Doze or App Standby mode, the system delivers the message and gives the app temporary access to network services and partial wakelocks, then returns the device or app to the idle state.
Regardless, this doesn't work. I have tried on different phones and I can say that the behavior is way worse on Android 9, whereas from Android 7 and lower it is not so common.
I have seen and tried the solutions proposed in this other question, but none of them seems to work and, even if in one of the answers they say that this happens only if the user has swiped away the app from multi-tasking, I can say that it happens to me even if the app hasn't been force stopped.
There's also another question, more similar to my issue, here but all the proposed solutions don't work.
I have no idea how to solve this and it is a very bad issue and affects the usability of my app, since the user is unable to answer to video-calls when they're not using their phone. I also found other people online reporting this issue, but all of those conversations seem to die at some point without an actual solution…
Can anyone help me?
Handle notification messages in a backgrounded appA user tap on the notification opens the app launcher by default. This includes messages that contain both notification and data payload (and all messages sent from the Notifications console).
Firebase Cloud Messaging (FCM) provides a reliable and battery-efficient connection between your server and devices that allows you to deliver and receive messages and notifications on iOS, Android, and the web at no cost.
Without FCM, you'd need to build more than three notification systems. You'd need one each for iOS and Android, but then you'd also need to accommodate all the different types of browsers for the web application to deliver push notifications without a hitch.
Turns out that this is not a FCM issue, but it's actually a problem with Azure Notification Hub (which is the system our backend is using to send out notifications).
If you're having issues with High Priority messages, Azure Notification Hub and Doze Mode, refer to the chosen answer of this question: Setting Fcm Notification Priority - Azure Notification Hub
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With