Skip to content

Commit ef1cac4

Browse files
authored
fix(messaging,android): fixing an issue with analytics being triggered twice (#18544)
* fix(messaging,android): fixing an issue with analytics being triggered twice * clean
1 parent 1345dbb commit ef1cac4

1 file changed

Lines changed: 94 additions & 4 deletions

File tree

packages/firebase_messaging/firebase_messaging/android/src/main/java/io/flutter/plugins/firebase/messaging/FlutterFirebaseMessagingPlugin.java

Lines changed: 94 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,22 @@
1111
import android.content.Intent;
1212
import android.content.pm.PackageManager;
1313
import android.os.Build;
14+
import android.os.Bundle;
15+
import android.util.Log;
1416
import androidx.annotation.NonNull;
1517
import androidx.annotation.Nullable;
1618
import androidx.annotation.RequiresApi;
19+
import androidx.annotation.VisibleForTesting;
1720
import androidx.core.app.NotificationManagerCompat;
1821
import androidx.lifecycle.LiveData;
1922
import androidx.lifecycle.Observer;
2023
import com.google.android.gms.tasks.Task;
2124
import com.google.android.gms.tasks.TaskCompletionSource;
2225
import com.google.android.gms.tasks.Tasks;
2326
import com.google.firebase.FirebaseApp;
27+
import com.google.firebase.messaging.Constants;
2428
import com.google.firebase.messaging.FirebaseMessaging;
29+
import com.google.firebase.messaging.MessagingAnalytics;
2530
import com.google.firebase.messaging.RemoteMessage;
2631
import io.flutter.embedding.engine.plugins.FlutterPlugin;
2732
import io.flutter.embedding.engine.plugins.activity.ActivityAware;
@@ -33,9 +38,11 @@
3338
import io.flutter.plugin.common.MethodChannel.Result;
3439
import io.flutter.plugin.common.PluginRegistry.NewIntentListener;
3540
import io.flutter.plugins.firebase.core.FlutterFirebasePlugin;
41+
import java.util.ArrayDeque;
3642
import java.util.HashMap;
3743
import java.util.Map;
3844
import java.util.Objects;
45+
import java.util.Queue;
3946

4047
/** FlutterFirebaseMessagingPlugin */
4148
public class FlutterFirebaseMessagingPlugin
@@ -45,7 +52,14 @@ public class FlutterFirebaseMessagingPlugin
4552
FlutterPlugin,
4653
ActivityAware {
4754

55+
private static final String TAG = "FLTFireMsgPlugin";
56+
57+
/** Mirrors the de-duplication window the Messaging SDK keeps in FcmLifecycleCallbacks. */
58+
private static final int RECENTLY_LOGGED_MESSAGE_IDS_MAX_SIZE = 10;
59+
4860
private final HashMap<String, Boolean> consumedInitialMessages = new HashMap<>();
61+
private final Queue<String> recentlyLoggedMessageIds =
62+
new ArrayDeque<>(RECENTLY_LOGGED_MESSAGE_IDS_MAX_SIZE);
4963
private MethodChannel channel;
5064
private Activity mainActivity;
5165

@@ -104,7 +118,10 @@ public void onAttachedToActivity(ActivityPluginBinding binding) {
104118
if (mainActivity.getIntent() != null && mainActivity.getIntent().getExtras() != null) {
105119
if ((mainActivity.getIntent().getFlags() & Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY)
106120
!= Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) {
107-
onNewIntent(mainActivity.getIntent());
121+
// The notification tap created this Activity, so the Messaging SDK has already logged
122+
// `notification_open` from FcmLifecycleCallbacks#onActivityCreated. Handle the intent
123+
// without logging it a second time.
124+
handleNotificationIntent(mainActivity.getIntent());
108125
}
109126
}
110127
}
@@ -533,13 +550,86 @@ private Map<String, Object> getExceptionDetails(@Nullable Exception exception) {
533550

534551
@Override
535552
public boolean onNewIntent(@NonNull Intent intent) {
553+
// The Activity already existed, so the Messaging SDK never ran
554+
// FcmLifecycleCallbacks#onActivityCreated for this intent and `notification_open` was not
555+
// logged. Log it here before handling the intent.
556+
logNotificationOpen(intent);
557+
return handleNotificationIntent(intent);
558+
}
559+
560+
/**
561+
* Logs the Analytics {@code notification_open} event for a notification tap that did not create
562+
* the Activity.
563+
*
564+
* <p>The Messaging SDK only logs this event from {@code FcmLifecycleCallbacks#onActivityCreated},
565+
* so it is missed whenever the app was merely backgrounded (for example with the Home button) and
566+
* the Activity is reused. See firebase/flutterfire#17072 and firebase/firebase-android-sdk#3799.
567+
*
568+
* <p>This mirrors the SDK's own implementation, including de-duplication by message id, so a
569+
* message is never counted twice.
570+
*/
571+
private void logNotificationOpen(@NonNull Intent intent) {
572+
Bundle analyticsData;
573+
try {
574+
Bundle extras = intent.getExtras();
575+
if (extras == null) {
576+
return;
577+
}
578+
579+
if (!markNotificationOpenAsLogged(getMessageId(extras))) {
580+
return;
581+
}
582+
583+
analyticsData = extras.getBundle(Constants.MessageNotificationKeys.ANALYTICS_DATA);
584+
} catch (RuntimeException e) {
585+
// The intent can come from anywhere and may be malformed, so never crash the host app while
586+
// reading analytics data out of it.
587+
Log.w(TAG, "Failed to get analytics data from notification intent extras.", e);
588+
return;
589+
}
590+
591+
if (MessagingAnalytics.shouldUploadScionMetrics(analyticsData)) {
592+
MessagingAnalytics.logNotificationOpen(analyticsData);
593+
}
594+
}
595+
596+
/**
597+
* Records {@code messageId} as having had its {@code notification_open} event logged, and returns
598+
* whether the caller should log it.
599+
*
600+
* <p>Returns {@code false} when this message id was logged recently, so that a message tapped
601+
* more than once (or delivered through both the create and the new-intent path) is only counted
602+
* once. A {@code null} id cannot be de-duplicated and is always logged, matching the SDK.
603+
*/
604+
@VisibleForTesting
605+
boolean markNotificationOpenAsLogged(@Nullable String messageId) {
606+
if (messageId == null) {
607+
return true;
608+
}
609+
if (recentlyLoggedMessageIds.contains(messageId)) {
610+
return false;
611+
}
612+
if (recentlyLoggedMessageIds.size() >= RECENTLY_LOGGED_MESSAGE_IDS_MAX_SIZE) {
613+
recentlyLoggedMessageIds.poll();
614+
}
615+
recentlyLoggedMessageIds.add(messageId);
616+
return true;
617+
}
618+
619+
/** Remote Message ID can be either one of the following... */
620+
@Nullable
621+
private static String getMessageId(@NonNull Bundle extras) {
622+
String messageId = extras.getString("google.message_id");
623+
if (messageId == null) messageId = extras.getString("message_id");
624+
return messageId;
625+
}
626+
627+
private boolean handleNotificationIntent(@NonNull Intent intent) {
536628
if (intent.getExtras() == null) {
537629
return false;
538630
}
539631

540-
// Remote Message ID can be either one of the following...
541-
String messageId = intent.getExtras().getString("google.message_id");
542-
if (messageId == null) messageId = intent.getExtras().getString("message_id");
632+
String messageId = getMessageId(intent.getExtras());
543633
if (messageId == null) {
544634
return false;
545635
}

0 commit comments

Comments
 (0)