diff --git a/firebase-messaging/src/main/java/com/google/firebase/messaging/CommonNotificationBuilder.java b/firebase-messaging/src/main/java/com/google/firebase/messaging/CommonNotificationBuilder.java index b2bc2c76d5e..971cfde7551 100644 --- a/firebase-messaging/src/main/java/com/google/firebase/messaging/CommonNotificationBuilder.java +++ b/firebase-messaging/src/main/java/com/google/firebase/messaging/CommonNotificationBuilder.java @@ -95,6 +95,7 @@ public final class CommonNotificationBuilder { // Do not instantiate. private CommonNotificationBuilder() {} + /** Creates a DisplayNotificationInfo from NotificationParams for a single Context. */ static DisplayNotificationInfo createNotificationInfo( Context context, NotificationParams params) { Bundle manifestMetadata = @@ -102,14 +103,17 @@ static DisplayNotificationInfo createNotificationInfo( return createNotificationInfo( context, - context.getPackageName(), + context, params, getOrCreateChannel(context, params.getNotificationChannelId(), manifestMetadata), - context.getResources(), - context.getPackageManager(), manifestMetadata); } + /** + * Legacy method that creates a DisplayNotificationInfo from NotificationParams that allows + * specifying components so the calling Context can be different from the Context used for the + * notification (resources, etc.). + */ public static DisplayNotificationInfo createNotificationInfo( Context context, String pkgName, @@ -118,8 +122,53 @@ public static DisplayNotificationInfo createNotificationInfo( Resources appResources, PackageManager appPackageManager, Bundle manifestMetadata) { + return createNotificationInfo( + context, + context, + params, + channelId, + manifestMetadata, + pkgName, + appResources, + appPackageManager); + } - NotificationCompat.Builder builder = new NotificationCompat.Builder(context, channelId); + /** + * Creates a DisplayNotificationInfo from NotificationParams that allows specifying a calling + * Context to be used for creating PendingIntents and one Context that the notification is + * intended for (resources, package name, manifest data, etc.) + */ + public static DisplayNotificationInfo createNotificationInfo( + Context callingContext, + Context appContext, + NotificationParams params, + String channelId, + Bundle manifestMetadata) { + String pkgName = appContext.getPackageName(); + Resources appResources = appContext.getResources(); + PackageManager appPackageManager = appContext.getPackageManager(); + return createNotificationInfo( + callingContext, + appContext, + params, + channelId, + manifestMetadata, + pkgName, + appResources, + appPackageManager); + } + + public static DisplayNotificationInfo createNotificationInfo( + Context callingContext, + Context appContext, + NotificationParams params, + String channelId, + Bundle manifestMetadata, + String pkgName, + Resources appResources, + PackageManager appPackageManager) { + + NotificationCompat.Builder builder = new NotificationCompat.Builder(appContext, channelId); String title = params.getPossiblyLocalizedString( @@ -150,15 +199,16 @@ public static DisplayNotificationInfo createNotificationInfo( builder.setSound(sound); } - builder.setContentIntent(createContentIntent(context, params, pkgName, appPackageManager)); + builder.setContentIntent( + createContentIntent(callingContext, params, pkgName, appPackageManager)); - PendingIntent deleteIntent = createDeleteIntent(context, params); + PendingIntent deleteIntent = createDeleteIntent(callingContext, appContext, params); if (deleteIntent != null) { builder.setDeleteIntent(deleteIntent); } Integer color = - getColor(context, params.getString(MessageNotificationKeys.COLOR), manifestMetadata); + getColor(appContext, params.getString(MessageNotificationKeys.COLOR), manifestMetadata); if (color != null) { builder.setColor(color); } @@ -541,7 +591,8 @@ private static int getPendingIntentFlags(int baseFlags) { } @Nullable - private static PendingIntent createDeleteIntent(Context context, NotificationParams params) { + private static PendingIntent createDeleteIntent( + Context callingContext, Context appContext, NotificationParams params) { if (!shouldUploadMetrics(params)) { return null; } @@ -550,17 +601,18 @@ private static PendingIntent createDeleteIntent(Context context, NotificationPar new Intent(IntentActionKeys.NOTIFICATION_DISMISS) .putExtras(params.paramsForAnalyticsIntent()); - return createMessagingPendingIntent(context, dismissIntent); + return createMessagingPendingIntent(callingContext, appContext, dismissIntent); } /** Create a PendingIntent to start the app's messaging service via FirebaseInstanceIdReceiver */ - private static PendingIntent createMessagingPendingIntent(Context context, Intent intent) { + private static PendingIntent createMessagingPendingIntent( + Context callingContext, Context appContext, Intent intent) { return PendingIntent.getBroadcast( - context, + callingContext, generatePendingIntentRequestCode(), new Intent(ACTION_MESSAGING_EVENT) .setComponent( - new ComponentName(context, "com.google.firebase.iid.FirebaseInstanceIdReceiver")) + new ComponentName(appContext, "com.google.firebase.iid.FirebaseInstanceIdReceiver")) .putExtra(IntentKeys.WRAPPED_INTENT, intent), getPendingIntentFlags(PendingIntent.FLAG_ONE_SHOT)); } diff --git a/firebase-messaging/src/test/java/com/google/firebase/messaging/CommonNotificationBuilderRoboTest.java b/firebase-messaging/src/test/java/com/google/firebase/messaging/CommonNotificationBuilderRoboTest.java index a1b74b5dd47..596aab1f494 100644 --- a/firebase-messaging/src/test/java/com/google/firebase/messaging/CommonNotificationBuilderRoboTest.java +++ b/firebase-messaging/src/test/java/com/google/firebase/messaging/CommonNotificationBuilderRoboTest.java @@ -14,10 +14,13 @@ package com.google.firebase.messaging; import static com.google.common.truth.Truth.assertThat; +import static com.google.firebase.messaging.ServiceStarter.ACTION_MESSAGING_EVENT; import static org.robolectric.Shadows.shadowOf; import android.app.Notification; +import android.content.ComponentName; import android.content.Context; +import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; @@ -27,7 +30,10 @@ import android.os.Bundle; import androidx.core.app.NotificationCompat; import androidx.test.core.app.ApplicationProvider; +import com.google.android.gms.cloudmessaging.CloudMessagingReceiver.IntentActionKeys; +import com.google.android.gms.cloudmessaging.CloudMessagingReceiver.IntentKeys; import com.google.firebase.messaging.CommonNotificationBuilder.DisplayNotificationInfo; +import com.google.firebase.messaging.Constants.AnalyticsKeys; import com.google.firebase.messaging.Constants.MessageNotificationKeys; import com.google.firebase.messaging.testing.Bundles; import java.util.Arrays; @@ -38,6 +44,7 @@ import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; import org.robolectric.annotation.LooperMode; +import org.robolectric.shadows.ShadowPendingIntent; @LooperMode(LooperMode.Mode.LEGACY) @RunWith(RobolectricTestRunner.class) @@ -45,7 +52,6 @@ public class CommonNotificationBuilderRoboTest { public static final String FCM_FALLBACK_NOTIFICATION_CHANNEL = "fcm_fallback_notification_channel"; private static final String FCM_FALLBACK_NOTIFICATION_CHANNEL_NO_RESOURCE = "Misc"; - private static final String PACKAGE_NAME = "test_package"; private static final String KEY_PREFIX = "gcm.n."; private static final String KEY_TICKER = KEY_PREFIX + "ticker"; private static final String KEY_VIBRATE_TIMINGS = KEY_PREFIX + "vibrate_timings"; @@ -62,10 +68,16 @@ public class CommonNotificationBuilderRoboTest { private static final int DEFAULTS_ALL_OFF = 0; private Context appContext; + private Context callingContext; @Before - public void setUp() { + public void setUp() throws Exception { appContext = ApplicationProvider.getApplicationContext(); + + PackageInfo packageInfo = new PackageInfo(); + packageInfo.packageName = "test.calling.package"; + shadowOf(appContext.getPackageManager()).addPackage(packageInfo); + callingContext = appContext.createPackageContext("test.calling.package", 0); } @Test @@ -459,12 +471,10 @@ public void createNotificationInfo_withInvalidVisibility_outOfBoundVisibility() public void staticCreateNotificationInfo_respectsChannelId() throws Exception { DisplayNotificationInfo notificationInfo = CommonNotificationBuilder.createNotificationInfo( + callingContext, appContext, - appContext.getPackageName(), new NotificationParams(Bundle.EMPTY), "channelId", - appContext.getResources(), - appContext.getPackageManager(), Bundle.EMPTY); assertThat(notificationInfo.notificationBuilder.build().getChannelId()).isEqualTo("channelId"); @@ -497,15 +507,13 @@ static void setTargetSdkVersion(Context context, int version) throws Exception { public void staticCreateNotificationInfo_handlesNoArgLocalizedTitle() { DisplayNotificationInfo notificationInfo = CommonNotificationBuilder.createNotificationInfo( + callingContext, appContext, - appContext.getPackageName(), new NotificationParams( Bundles.of( MessageNotificationKeys.TITLE + MessageNotificationKeys.TEXT_RESOURCE_SUFFIX, "fcm_no_args")), "channelId", - appContext.getResources(), - appContext.getPackageManager(), Bundle.EMPTY); // http://google3/javatests/com/google/android/gmscore/integ/tests_res/res/values/strings.xml?l=25-28&rcl=127925113 @@ -518,17 +526,15 @@ public void staticCreateNotificationInfo_handlesNoArgLocalizedTitle() { public void staticCreateNotificationInfo_handlesLocalizedTitleWithArgs() { DisplayNotificationInfo notificationInfo = CommonNotificationBuilder.createNotificationInfo( + callingContext, appContext, - appContext.getPackageName(), new NotificationParams( Bundles.of( MessageNotificationKeys.TITLE + MessageNotificationKeys.TEXT_RESOURCE_SUFFIX, - "fcm_2_args", + "fcm_2_args", MessageNotificationKeys.TITLE + MessageNotificationKeys.TEXT_ARGS_SUFFIX, - "[\"one\", \"two\"]")), + "[\"one\", \"two\"]")), "channelId", - appContext.getResources(), - appContext.getPackageManager(), Bundle.EMPTY); // http://google3/javatests/com/google/android/gmscore/integ/tests_res/res/values/strings.xml?l=25-28&rcl=127925113 @@ -541,15 +547,13 @@ public void staticCreateNotificationInfo_handlesLocalizedTitleWithArgs() { public void staticCreateNotificationInfo_handlesNoArgLocalizedBody() { DisplayNotificationInfo notificationInfo = CommonNotificationBuilder.createNotificationInfo( + callingContext, appContext, - appContext.getPackageName(), new NotificationParams( Bundles.of( MessageNotificationKeys.BODY + MessageNotificationKeys.TEXT_RESOURCE_SUFFIX, "fcm_no_args")), "channelId", - appContext.getResources(), - appContext.getPackageManager(), Bundle.EMPTY); // http://google3/javatests/com/google/android/gmscore/integ/tests_res/res/values/strings.xml?l=25-28&rcl=127925113 @@ -562,17 +566,15 @@ public void staticCreateNotificationInfo_handlesNoArgLocalizedBody() { public void staticCreateNotificationInfo_handlesLocalizedBodyWithArgs() { DisplayNotificationInfo notificationInfo = CommonNotificationBuilder.createNotificationInfo( + callingContext, appContext, - appContext.getPackageName(), new NotificationParams( Bundles.of( MessageNotificationKeys.BODY + MessageNotificationKeys.TEXT_RESOURCE_SUFFIX, - "fcm_2_args", + "fcm_2_args", MessageNotificationKeys.BODY + MessageNotificationKeys.TEXT_ARGS_SUFFIX, - "[\"one\", \"two\"]")), + "[\"one\", \"two\"]")), "channelId", - appContext.getResources(), - appContext.getPackageManager(), Bundle.EMPTY); // http://google3/javatests/com/google/android/gmscore/integ/tests_res/res/values/strings.xml?l=25-28&rcl=127925113 @@ -585,12 +587,10 @@ public void staticCreateNotificationInfo_handlesLocalizedBodyWithArgs() { public void staticCreateNotificationInfo_smallIconSpecified() { DisplayNotificationInfo notificationInfo = CommonNotificationBuilder.createNotificationInfo( + callingContext, appContext, - appContext.getPackageName(), new NotificationParams(Bundles.of(MessageNotificationKeys.ICON, "gcm_icon")), "channelId", - appContext.getResources(), - appContext.getPackageManager(), Bundle.EMPTY); assertThat(notificationInfo.notificationBuilder.build().icon) @@ -601,12 +601,10 @@ public void staticCreateNotificationInfo_smallIconSpecified() { public void staticCreateNotificationInfo_smallIconSpecifiedInMetadata() { DisplayNotificationInfo notificationInfo = CommonNotificationBuilder.createNotificationInfo( + callingContext, appContext, - appContext.getPackageName(), new NotificationParams(Bundle.EMPTY), "channelId", - appContext.getResources(), - appContext.getPackageManager(), Bundles.of( CommonNotificationBuilder.METADATA_DEFAULT_ICON, com.google.firebase.messaging.test.R.drawable.gcm_icon)); @@ -618,21 +616,19 @@ public void staticCreateNotificationInfo_smallIconSpecifiedInMetadata() { @Test public void staticCreateNotificationInfo_noIconSpecifiedShouldUseAppIcon() { PackageInfo packageInfo = new PackageInfo(); - packageInfo.packageName = PACKAGE_NAME; + packageInfo.packageName = appContext.getPackageName(); packageInfo.applicationInfo = new ApplicationInfo(); - packageInfo.applicationInfo.packageName = PACKAGE_NAME; + packageInfo.applicationInfo.packageName = appContext.getPackageName(); packageInfo.applicationInfo.icon = com.google.firebase.messaging.test.R.drawable.gcm_icon2; shadowOf(appContext.getPackageManager()).installPackage(packageInfo); DisplayNotificationInfo notificationInfo = CommonNotificationBuilder.createNotificationInfo( + callingContext, appContext, - PACKAGE_NAME, new NotificationParams(Bundle.EMPTY), "channelId", - appContext.getResources(), - appContext.getPackageManager(), Bundle.EMPTY); assertThat(notificationInfo.notificationBuilder.build().icon) @@ -642,21 +638,19 @@ public void staticCreateNotificationInfo_noIconSpecifiedShouldUseAppIcon() { @Test public void staticCreateNotificationInfo_noAppIconShouldUseDefaultSystemIcon() { PackageInfo packageInfo = new PackageInfo(); - packageInfo.packageName = PACKAGE_NAME; + packageInfo.packageName = appContext.getPackageName(); packageInfo.applicationInfo = new ApplicationInfo(); - packageInfo.applicationInfo.packageName = PACKAGE_NAME; + packageInfo.applicationInfo.packageName = appContext.getPackageName(); packageInfo.applicationInfo.icon = 0; // Bad app icon! shadowOf(appContext.getPackageManager()).installPackage(packageInfo); DisplayNotificationInfo notificationInfo = CommonNotificationBuilder.createNotificationInfo( + callingContext, appContext, - PACKAGE_NAME, new NotificationParams(Bundle.EMPTY), "channelId", - appContext.getResources(), - appContext.getPackageManager(), Bundle.EMPTY); assertThat(notificationInfo.notificationBuilder.build().icon) @@ -667,12 +661,10 @@ public void staticCreateNotificationInfo_noAppIconShouldUseDefaultSystemIcon() { public void staticCreateNotificationInfo_defaultSoundSpecified_sound1Key() { DisplayNotificationInfo notificationInfo = CommonNotificationBuilder.createNotificationInfo( + callingContext, appContext, - appContext.getPackageName(), new NotificationParams(Bundles.of(MessageNotificationKeys.SOUND, "default")), "channelId", - appContext.getResources(), - appContext.getPackageManager(), Bundle.EMPTY); assertThat(notificationInfo.notificationBuilder.build().sound) @@ -683,12 +675,10 @@ public void staticCreateNotificationInfo_defaultSoundSpecified_sound1Key() { public void staticCreateNotificationInfo_defaultSoundSpecified_sound2Key() { DisplayNotificationInfo notificationInfo = CommonNotificationBuilder.createNotificationInfo( + callingContext, appContext, - appContext.getPackageName(), new NotificationParams(Bundles.of(MessageNotificationKeys.SOUND_2, "default")), "channelId", - appContext.getResources(), - appContext.getPackageManager(), Bundle.EMPTY); assertThat(notificationInfo.notificationBuilder.build().sound) @@ -699,14 +689,125 @@ public void staticCreateNotificationInfo_defaultSoundSpecified_sound2Key() { public void staticCreateNotificationInfo_noSoundSpecified() { DisplayNotificationInfo notificationInfo = CommonNotificationBuilder.createNotificationInfo( + callingContext, appContext, - appContext.getPackageName(), new NotificationParams(Bundle.EMPTY), "channelId", - appContext.getResources(), - appContext.getPackageManager(), Bundle.EMPTY); assertThat(notificationInfo.notificationBuilder.build().sound).isNull(); } + + @Test + public void createNotificationInfo_noContentIntentSpecified() { + DisplayNotificationInfo notificationInfo = + CommonNotificationBuilder.createNotificationInfo( + callingContext, + appContext, + new NotificationParams(Bundle.EMPTY), + "channelId", + Bundle.EMPTY); + + assertThat(notificationInfo.notificationBuilder.build().contentIntent).isNull(); + } + + @Test + public void createNotificationInfo_clickAction() { + DisplayNotificationInfo notificationInfo = + CommonNotificationBuilder.createNotificationInfo( + callingContext, + appContext, + new NotificationParams( + Bundles.of(MessageNotificationKeys.CLICK_ACTION, "click.action")), + "channelId", + Bundle.EMPTY); + + ShadowPendingIntent contentPendingIntent = + shadowOf(notificationInfo.notificationBuilder.build().contentIntent); + assertThat(contentPendingIntent.isActivityIntent()).isTrue(); + assertThat(contentPendingIntent.getSavedContext()).isEqualTo(callingContext); + Intent contentIntent = contentPendingIntent.getSavedIntent(); + assertThat(contentIntent.getPackage()).isEqualTo(appContext.getPackageName()); + assertThat(contentIntent.getAction()).isEqualTo("click.action"); + } + + @Test + public void createNotificationInfo_link() { + DisplayNotificationInfo notificationInfo = + CommonNotificationBuilder.createNotificationInfo( + callingContext, + appContext, + new NotificationParams(Bundles.of(MessageNotificationKeys.LINK, "link")), + "channelId", + Bundle.EMPTY); + + ShadowPendingIntent contentPendingIntent = + shadowOf(notificationInfo.notificationBuilder.build().contentIntent); + assertThat(contentPendingIntent.isActivityIntent()).isTrue(); + assertThat(contentPendingIntent.getSavedContext()).isEqualTo(callingContext); + Intent contentIntent = contentPendingIntent.getSavedIntent(); + assertThat(contentIntent.getPackage()).isEqualTo(appContext.getPackageName()); + assertThat(contentIntent.getDataString()).isEqualTo("link"); + } + + @Test + public void createNotificationInfo_androidLink() { + DisplayNotificationInfo notificationInfo = + CommonNotificationBuilder.createNotificationInfo( + callingContext, + appContext, + new NotificationParams( + Bundles.of( + MessageNotificationKeys.LINK, + "link", + MessageNotificationKeys.LINK_ANDROID, + "androidLink")), + "channelId", + Bundle.EMPTY); + + ShadowPendingIntent contentPendingIntent = + shadowOf(notificationInfo.notificationBuilder.build().contentIntent); + assertThat(contentPendingIntent.isActivityIntent()).isTrue(); + assertThat(contentPendingIntent.getSavedContext()).isEqualTo(callingContext); + Intent contentIntent = contentPendingIntent.getSavedIntent(); + assertThat(contentIntent.getPackage()).isEqualTo(appContext.getPackageName()); + assertThat(contentIntent.getDataString()).isEqualTo("androidLink"); + } + + @Test + public void createNotificationInfo_deleteIntentWithoutAnalytics() { + DisplayNotificationInfo notificationInfo = + CommonNotificationBuilder.createNotificationInfo( + callingContext, + appContext, + new NotificationParams(Bundle.EMPTY), + "channelId", + Bundle.EMPTY); + + assertThat(notificationInfo.notificationBuilder.build().deleteIntent).isNull(); + } + + @Test + public void createNotificationInfo_deleteIntentWithAnalytics() { + DisplayNotificationInfo notificationInfo = + CommonNotificationBuilder.createNotificationInfo( + callingContext, + appContext, + new NotificationParams(Bundles.of(AnalyticsKeys.ENABLED, "1")), + "channelId", + Bundle.EMPTY); + + ShadowPendingIntent deletePendingIntent = + shadowOf(notificationInfo.notificationBuilder.build().deleteIntent); + assertThat(deletePendingIntent.isBroadcastIntent()).isTrue(); + assertThat(deletePendingIntent.getSavedContext()).isEqualTo(callingContext); + Intent deleteIntent = deletePendingIntent.getSavedIntent(); + assertThat(deleteIntent.getComponent()) + .isEqualTo( + new ComponentName(appContext, "com.google.firebase.iid.FirebaseInstanceIdReceiver")); + assertThat(deleteIntent.getAction()).isEqualTo(ACTION_MESSAGING_EVENT); + Intent dismissIntent = deleteIntent.getParcelableExtra(IntentKeys.WRAPPED_INTENT); + assertThat(dismissIntent).isNotNull(); + assertThat(dismissIntent.getAction()).isEqualTo(IntentActionKeys.NOTIFICATION_DISMISS); + } }