Skip to content

fix(messaging,android): ignore broadcasts that carry no message - #18907

Open
Lyokone wants to merge 2 commits into
mainfrom
fix/messaging-android-notification-dismiss
Open

Lyokone wants to merge 2 commits into
mainfrom
fix/messaging-android-notification-dismiss

Conversation

@Lyokone

@Lyokone Lyokone commented Nov 5, 2026

Copy link
Copy Markdown
Contributor

Description

On Android, two broadcasts that carry no message reach onMessage in the foreground, or onBackgroundMessage otherwise, as a RemoteMessage with no message ID:

  • Dismissing a notification that Firebase displayed (for messages with analytics enabled) delivers an empty message.
  • FCM's notice that it deleted messages pending for the device arrives as a message with messageType deleted_messages.

Both use the broadcast action messages use, which the plugin's receiver listens to. Firebase's own receiver and service treat neither as a message.

Fix: the receiver now makes Firebase's checks before reading a message. It ignores a broadcast that wraps a notification event, and one whose message type is anything other than a message's (deleted_messages, and the upstream send_event and send_error). Real messages are unaffected.

Behaviour change: apps that looked for messageType == 'deleted_messages' in onMessage or onBackgroundMessage no longer receive it, and the plugin has no other way to report it, as with onMessageReceived in native Android apps.

Tests

  • FlutterFirebaseMessagingReceiverTest (Android unit tests): ignoresABroadcastWrappingAnIntent, ignoresADeletedMessagesBroadcast and ignoresMessageTypesOtherThanGcm fail without the fix. The other four check that messages still get through.
  • E2E (Android): dismissing a notification displayed by Firebase does not reach onMessage and a deleted messages broadcast does not reach onMessage fail without the fix: onMessage receives the broadcast before the next message.

Related Issues

Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • My PR includes unit or integration tests for all changed/updated/fixed behaviors (See Contributor Guide).
  • All existing and new tests are passing.
  • I updated/added relevant documentation (doc comments with ///).
  • The analyzer (melos run analyze) does not report any problems on my PR.
  • I read and followed the Flutter Style Guide.
  • I signed the CLA.
  • I am willing to follow-up on review comments in a timely manner.

Breaking Change

  • Yes, this is a breaking change.
  • No, this is not a breaking change.

…st receiver

When firebase-messaging displays a notification for a message that has
analytics enabled, it gives the notification a delete intent. Dismissing the
notification sends that intent: a com.google.android.c2dm.intent.RECEIVE
broadcast to the app's package, with the NOTIFICATION_DISMISS intent in the
wrapped_intent extra. Since firebase-messaging 23.3.1 (firebase-android-sdk
#5410) it is addressed by action and package rather than to
FirebaseInstanceIdReceiver, so FlutterFirebaseMessagingReceiver gets it too and
built an empty RemoteMessage from it: onMessage in the foreground,
onBackgroundMessage otherwise.

The receiver now reads the message through remoteMessageFrom, which returns
null for a broadcast whose wrapped_intent extra is an Intent, the check
CloudMessagingReceiver (play-services-cloud-messaging) makes before treating a
broadcast as a message. Firebase's receiver still logs the dismissal.

The e2e test has the SDK build the notification (CommonNotificationBuilder,
through reflection since it is hidden API) and sends its delete intent as an
ordered broadcast. It then checks that Firebase's own receiver handled it
(RESULT_OK), and that onMessage receives no message without an ID before the
next message. Verified on an API 36 emulator with BoM 35.1.0: without the fix
onMessage receives [null, <message ID>].
FCM also sends com.google.android.c2dm.intent.RECEIVE broadcasts that are not
messages, marked by a message_type extra: deleted_messages when FCM has deleted
messages pending for the app, and send_event and send_error about upstream
messages. FirebaseMessagingService passes a broadcast to onMessageReceived
only if it has no message type or "gcm", but FlutterFirebaseMessagingReceiver
turned these into RemoteMessages for onMessage or onBackgroundMessage.

remoteMessageFrom now returns null for any type other than "gcm", as the
service drops them or passes them to their own callbacks.

Behaviour change: apps that checked messageType == 'deleted_messages' in
onMessage or onBackgroundMessage no longer get that broadcast, and the plugin
has no other way to report it, as with onMessageReceived in native apps. These
broadcasts no longer start a background isolate either.

The e2e test hands the receiver a deleted_messages broadcast, then a message,
and checks that onMessage receives only the message. Verified on an API 36
emulator: without the fix onMessage receives both.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request refactors FlutterFirebaseMessagingReceiver to extract message parsing into a helper method remoteMessageFrom, which filters out notification dismissals and non-gcm message types (such as deleted_messages). It also adds corresponding unit and integration tests. Feedback highlights a security and stability concern where parsing untrusted intents on this exported receiver could lead to a BadParcelableException and crash the app; wrapping these intent operations in try-catch blocks is recommended.

@Lyokone
Lyokone marked this pull request as ready for review November 5, 2026 13:35

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants