Search code examples
androidfirebase-cloud-messagingandroid-pendingintent

How to send a request when push notification is cancelled in Android


When an application receives push notification from FCM, it calls onMessageReceived. (See 1, 2 or 3.)

When a user taps the notification, it launches the applications, then it sends a request to a server that the user has read the notification.

I want to know when a device received a push notification, but the user swiped it (or cleared all notifications). I want to send a request to the server that the user simply cancelled the notification.

I tried to send BroadcastReceiver and show logs (see 4 or 5), but it works when the application was opened when the notification delivered. I suppose, that

MyFirebaseMessagingService:

override fun onMessageReceived(remoteMessage: RemoteMessage) {
    ...
    // An event when a user swipes a notification.
    val intent = Intent(this, NotificationBroadcastReceiver::class.java)
    intent.action = "notification_cancelled"
    val deleteIntent = PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT)
    notificationBuilder.setDeleteIntent(deleteIntent)

    // Navigation to an activity when a user taps the notification.
    // It doesn't matter to this question.
    val intent2 = Intent(this, MainActivity::class.java)
    val navigateIntent = PendingIntent.getActivity(this, notificationId, intent2,
        PendingIntent.FLAG_UPDATE_CURRENT)
    notificationBuilder.setContentIntent(navigateIntent)
    ...
}

NotificationBroadcastReceiver:

class NotificationBroadcastReceiver : BroadcastReceiver() {

    override fun onReceive(context: Context, intent: Intent) {
        Timber.d("NotificationBroadcastReceiver onReceive")
        Toast.makeText(context, "Notification dismissed", Toast.LENGTH_LONG).show()
        // Send a request to the server.
    }
}

AndroidManifest:

<uses-permission android:name="com.uremont.NOTIFICATION_PERMISSION" />

    <receiver
        android:name=".receiver.NotificationBroadcastReceiver"
        android:exported="true"
        android:permission="NOTIFICATION_PERMISSION"
        >
        <intent-filter>
            <action android:name="notification_cancelled" />
        </intent-filter>
    </receiver>

works only when the application is opened. But when the application is in background or killed, it doesn't react to swipe. Probably we shouldn't use BroadcastReceiver, for instance, use PendingIntent.getService or PendingIntent.getForegroundService.

Can we send a request to the server?


Solution

  • After a short time it worked right (though I hardly changed much). After a lot of research I made this solution. Tested on several Android emulators and devices from API 19 to API 30.

    Because using BroadcastReceiver is not safe, add in AndroidManifest:

    <receiver
        android:name=".NotificationBroadcastReceiver"
        android:exported="false"
        />
    

    NotificationBroadcastReceiver:

    class NotificationBroadcastReceiver : BroadcastReceiver() {
    
        override fun onReceive(context: Context, intent: Intent) {
            Timber.d("NotificationBroadcastReceiver onReceive")
    
            if (intent.extras != null) {
                // Receive parameters of a cancelled notification.
                val authToken = intent.getStringExtra(EXTRA_TOKEN)
                val code = intent.getStringExtra(EXTRA_CODE)
                Timber.d("token = $authToken, code = $code")
                // We can access context even if the application was removed from the recent list.
                Toast.makeText(context, "Notification $code was cancelled", Toast.LENGTH_SHORT).show()
                // Send data to a server.
            }
        }
    
        companion object {
            const val EXTRA_TOKEN = "EXTRA_TOKEN"
            const val EXTRA_CODE = "EXTRA_CODE"
        }
    }
    

    MyFirebaseMessagingService:

    override fun onMessageReceived(remoteMessage: RemoteMessage) {
        ...
        // Get notification code from data.
        val code = remoteMessage.data["code"]
    
        val notificationBuilder = NotificationCompat.Builder(this,
        ...
    
        val notificationId = Random.nextInt()
        val intent = Intent(this, NotificationBroadcastReceiver::class.java).apply {
            putExtra(EXTRA_TOKEN, authToken)
            putExtra(EXTRA_CODE, code)
        }
        val deleteIntent = PendingIntent.getBroadcast(this, notificationId, intent,
            PendingIntent.FLAG_CANCEL_CURRENT)
        notificationBuilder.setDeleteIntent(deleteIntent)
    
        val notification = notificationBuilder.build()
        notificationManager.notify(notificationId, notification)
    }
    

    Send a push message to Android device with it's push token, for instance:

    {
      "to": "ddSOGiz4QzmY.....:APA91bHgoincFw.......",
      "data": {
        "title": "Test",
        "message": "Test",
        "code": "ABCDEF"
      }
    }
    

    You can see different schemes of delivering push messages here. If a user presses "Force stop" at the application, it won't receive push messages (except "AliExpress", ha-ha).

    When the user dismisses push notification, NotificationBroadcastReceiver::onReceive() is called. An application gets parameters of the push message. Then we can see a toast message and send these parameters to the server.

    When the user presses "Clear all" notifications, all dismiss events are fired. So, you will see a sequence of toasts. The server will receive several requests simultaneously (check that it can handle, for instance, 10 requests in 0.01 second).