diff --git a/src/main/java/com/google/firebase/messaging/FirebaseMessaging.java b/src/main/java/com/google/firebase/messaging/FirebaseMessaging.java
index bb7b8208f..0e9831588 100644
--- a/src/main/java/com/google/firebase/messaging/FirebaseMessaging.java
+++ b/src/main/java/com/google/firebase/messaging/FirebaseMessaging.java
@@ -265,12 +265,14 @@ protected SendResponse execute() {
}
/**
- * Sends the given multicast message to all the FCM registration tokens specified in it.
+ * Sends the given multicast message to all the FCM registration tokens and/or FIDs
+ * specified in it.
*
*
This method uses the {@link #sendEach(List)} API under the hood to send the given
* message to all the target recipients. The list of responses obtained by calling
* {@link BatchResponse#getResponses()} on the return value is in the same order as the
- * tokens in the {@link MulticastMessage}.
+ * tokens and/or FIDs in the {@link MulticastMessage}. If both tokens and FIDs are
+ * provided, tokens are processed first, followed by FIDs.
*
* @param message A non-null {@link MulticastMessage}
* @return A {@link BatchResponse} indicating the result of the operation.
@@ -285,17 +287,19 @@ public BatchResponse sendEachForMulticast(
}
/**
- * Sends the given multicast message to all the FCM registration tokens specified in it.
+ * Sends the given multicast message to all the FCM registration tokens and/or FIDs
+ * specified in it.
*
- *
If the {@code dryRun} option is set to true, the message will not be actually sent. Instead
- * FCM performs all the necessary validations, and emulates the send operation. The {@code dryRun}
- * option is useful for determining whether an FCM registration has been deleted. But it cannot be
- * used to validate APNs tokens.
+ *
If the {@code dryRun} option is set to true, the message will not be actually sent.
+ * Instead FCM performs all the necessary validations, and emulates the send operation.
+ * The {@code dryRun} option is useful for determining whether an FCM registration has
+ * been deleted. But it cannot be used to validate APNs tokens.
*
*
This method uses the {@link #sendEach(List)} API under the hood to send the given
* message to all the target recipients. The list of responses obtained by calling
* {@link BatchResponse#getResponses()} on the return value is in the same order as the
- * tokens in the {@link MulticastMessage}.
+ * tokens and/or FIDs in the {@link MulticastMessage}. If both tokens and FIDs are
+ * provided, tokens are processed first, followed by FIDs.
*
* @param message A non-null {@link MulticastMessage}.
* @param dryRun A boolean indicating whether to perform a dry run (validation only) of the send.
@@ -417,12 +421,14 @@ public ApiFuture sendAllAsync(
}
/**
- * Sends the given multicast message to all the FCM registration tokens specified in it.
+ * Sends the given multicast message to all the FCM registration tokens and/or FIDs
+ * specified in it.
*
* This method uses the {@link #sendAll(List)} API under the hood to send the given
* message to all the target recipients. The responses list obtained by calling
- * {@link BatchResponse#getResponses()} on the return value corresponds to the order of tokens
- * in the {@link MulticastMessage}.
+ * {@link BatchResponse#getResponses()} on the return value corresponds to the order of
+ * tokens and/or FIDs in the {@link MulticastMessage}. If both tokens and FIDs are
+ * provided, tokens are processed first, followed by FIDs.
*
* @param message A non-null {@link MulticastMessage}
* @return A {@link BatchResponse} indicating the result of the operation.
@@ -439,17 +445,19 @@ public BatchResponse sendMulticast(
}
/**
- * Sends the given multicast message to all the FCM registration tokens specified in it.
+ * Sends the given multicast message to all the FCM registration tokens and/or FIDs
+ * specified in it.
*
- *
If the {@code dryRun} option is set to true, the message will not be actually sent. Instead
- * FCM performs all the necessary validations, and emulates the send operation. The {@code dryRun}
- * option is useful for determining whether an FCM registration has been deleted. But it cannot be
- * used to validate APNs tokens.
+ *
If the {@code dryRun} option is set to true, the message will not be actually sent.
+ * Instead FCM performs all the necessary validations, and emulates the send operation.
+ * The {@code dryRun} option is useful for determining whether an FCM registration has
+ * been deleted. But it cannot be used to validate APNs tokens.
*
*
This method uses the {@link #sendAll(List)} API under the hood to send the given
* message to all the target recipients. The responses list obtained by calling
- * {@link BatchResponse#getResponses()} on the return value corresponds to the order of tokens
- * in the {@link MulticastMessage}.
+ * {@link BatchResponse#getResponses()} on the return value corresponds to the order of
+ * tokens and/or FIDs in the {@link MulticastMessage}. If both tokens and FIDs are
+ * provided, tokens are processed first, followed by FIDs.
*
* @param message A non-null {@link MulticastMessage}.
* @param dryRun A boolean indicating whether to perform a dry run (validation only) of the send.
diff --git a/src/main/java/com/google/firebase/messaging/Message.java b/src/main/java/com/google/firebase/messaging/Message.java
index 1514fce3d..da790fa7f 100644
--- a/src/main/java/com/google/firebase/messaging/Message.java
+++ b/src/main/java/com/google/firebase/messaging/Message.java
@@ -54,9 +54,16 @@ public class Message {
@Key("apns")
private final ApnsConfig apnsConfig;
+ /**
+ * @deprecated Use {@link #fid} instead.
+ */
+ @Deprecated
@Key("token")
private final String token;
+ @Key("fid")
+ private final String fid;
+
@Key("topic")
private final String topic;
@@ -74,11 +81,14 @@ private Message(Builder builder) {
this.apnsConfig = builder.apnsConfig;
int count = Booleans.countTrue(
!Strings.isNullOrEmpty(builder.token),
+ !Strings.isNullOrEmpty(builder.fid),
!Strings.isNullOrEmpty(builder.topic),
!Strings.isNullOrEmpty(builder.condition)
);
- checkArgument(count == 1, "Exactly one of token, topic or condition must be specified");
+ checkArgument(count == 1,
+ "Exactly one of token, fid, topic or condition must be specified");
this.token = builder.token;
+ this.fid = builder.fid;
this.topic = stripPrefix(builder.topic);
this.condition = builder.condition;
this.fcmOptions = builder.fcmOptions;
@@ -109,11 +119,20 @@ ApnsConfig getApnsConfig() {
return apnsConfig;
}
+ /**
+ * @deprecated Use {@link #getFid()} instead.
+ */
+ @Deprecated
@VisibleForTesting
String getToken() {
return token;
}
+ @VisibleForTesting
+ String getFid() {
+ return fid;
+ }
+
@VisibleForTesting
String getTopic() {
return topic;
@@ -166,7 +185,9 @@ public static class Builder {
private AndroidConfig androidConfig;
private WebpushConfig webpushConfig;
private ApnsConfig apnsConfig;
+ @Deprecated
private String token;
+ private String fid;
private String topic;
private String condition;
private FcmOptions fcmOptions;
@@ -222,12 +243,26 @@ public Builder setApnsConfig(ApnsConfig apnsConfig) {
*
* @param token A valid device registration token.
* @return This builder.
+ * @deprecated Use {@link #setFid(String)} instead.
*/
+ @Deprecated
public Builder setToken(String token) {
this.token = token;
return this;
}
+ /**
+ * Sets the Firebase Installation ID (FID) of the app instance to which the message
+ * should be sent.
+ *
+ * @param fid A valid Firebase Installation ID.
+ * @return This builder.
+ */
+ public Builder setFid(String fid) {
+ this.fid = fid;
+ return this;
+ }
+
/**
* Sets the name of the FCM topic to which the message should be sent. Topic names may
* contain the {@code /topics/} prefix.
diff --git a/src/main/java/com/google/firebase/messaging/MulticastMessage.java b/src/main/java/com/google/firebase/messaging/MulticastMessage.java
index cc43b187b..a880a88f2 100644
--- a/src/main/java/com/google/firebase/messaging/MulticastMessage.java
+++ b/src/main/java/com/google/firebase/messaging/MulticastMessage.java
@@ -30,22 +30,26 @@
/**
* Represents a message that can be sent to multiple devices via Firebase Cloud Messaging (FCM).
- * Contains payload information as well as the list of device registration tokens to which the
- * message should be sent. A single {@code MulticastMessage} may contain up to 500 registration
- * tokens.
+ * Contains payload information as well as the list of device registration tokens and/or
+ * Firebase Installation IDs (FIDs) to which the message should be sent. A single
+ * {@code MulticastMessage} may contain up to 500 registration tokens and FIDs combined.
*
*
Instances of this class are thread-safe and immutable. Use {@link MulticastMessage.Builder}
* to create new instances. See {@link FirebaseMessaging#sendMulticast(MulticastMessage)} for
* details on how to send the message to FCM for multicast delivery.
*
- *
This class and the associated Builder retain the order of tokens. Therefore the order of
- * the responses list obtained by calling {@link BatchResponse#getResponses()} on the return value
- * of {@link FirebaseMessaging#sendMulticast(MulticastMessage)} corresponds to the order in which
- * tokens were added to the {@link MulticastMessage.Builder}.
+ *
This class and the associated Builder retain the order of tokens and FIDs. Therefore
+ * the order of the responses list obtained by calling {@link BatchResponse#getResponses()}
+ * on the return value of {@link FirebaseMessaging#sendMulticast(MulticastMessage)}
+ * corresponds to the order in which targets were added to the
+ * {@link MulticastMessage.Builder}. If both tokens and FIDs are provided, tokens are
+ * processed first, followed by FIDs.
*/
public class MulticastMessage {
+ @Deprecated
private final List tokens;
+ private final List fids;
private final Map data;
private final Notification notification;
private final AndroidConfig androidConfig;
@@ -55,11 +59,18 @@ public class MulticastMessage {
private MulticastMessage(Builder builder) {
this.tokens = builder.tokens.build();
- checkArgument(!this.tokens.isEmpty(), "at least one token must be specified");
- checkArgument(this.tokens.size() <= 500, "no more than 500 tokens can be specified");
+ this.fids = builder.fids.build();
+ int tokensSize = this.tokens.size();
+ int fidsSize = this.fids.size();
+ checkArgument(tokensSize + fidsSize > 0, "at least one token or fid must be specified");
+ checkArgument(tokensSize + fidsSize <= 500,
+ "no more than 500 tokens and fids combined can be specified");
for (String token : this.tokens) {
checkArgument(!Strings.isNullOrEmpty(token), "none of the tokens can be null or empty");
}
+ for (String fid : this.fids) {
+ checkArgument(!Strings.isNullOrEmpty(fid), "none of the fids can be null or empty");
+ }
this.data = builder.data.isEmpty() ? null : ImmutableMap.copyOf(builder.data);
this.notification = builder.notification;
this.androidConfig = builder.androidConfig;
@@ -69,6 +80,26 @@ private MulticastMessage(Builder builder) {
}
List getMessageList() {
+ ImmutableList.Builder messages = ImmutableList.builder();
+
+ if (!this.tokens.isEmpty()) {
+ Message.Builder tokenBuilder = getMetadataBuilder();
+ for (String token : this.tokens) {
+ messages.add(tokenBuilder.setToken(token).build());
+ }
+ }
+
+ if (!this.fids.isEmpty()) {
+ Message.Builder fidBuilder = getMetadataBuilder();
+ for (String fid : this.fids) {
+ messages.add(fidBuilder.setFid(fid).build());
+ }
+ }
+
+ return messages.build();
+ }
+
+ private Message.Builder getMetadataBuilder() {
Message.Builder builder = Message.builder()
.setNotification(this.notification)
.setAndroidConfig(this.androidConfig)
@@ -78,11 +109,7 @@ List getMessageList() {
if (this.data != null) {
builder.putAllData(this.data);
}
- ImmutableList.Builder messages = ImmutableList.builder();
- for (String token : this.tokens) {
- messages.add(builder.setToken(token).build());
- }
- return messages.build();
+ return builder;
}
/**
@@ -96,7 +123,9 @@ public static Builder builder() {
public static class Builder {
+ @Deprecated
private final ImmutableList.Builder tokens = ImmutableList.builder();
+ private final ImmutableList.Builder fids = ImmutableList.builder();
private final Map data = new HashMap<>();
private Notification notification;
private AndroidConfig androidConfig;
@@ -107,29 +136,61 @@ public static class Builder {
private Builder() {}
/**
- * Adds a token to which the message should be sent. Up to 500 tokens can be specified on
- * a single instance of {@link MulticastMessage}.
+ * Adds a token to which the message should be sent. Up to 500 tokens
+ * and FIDs combined can be specified on a single instance of
+ * {@link MulticastMessage}.
*
* @param token A non-null, non-empty Firebase device registration token.
* @return This builder.
+ * @deprecated Use {@link #addFid(String)} instead.
*/
+ @Deprecated
public Builder addToken(@NonNull String token) {
this.tokens.add(token);
return this;
}
/**
- * Adds a collection of tokens to which the message should be sent. Up to 500 tokens can be
- * specified on a single instance of {@link MulticastMessage}.
+ * Adds a Firebase Installation ID (FID) to which the message should be sent.
+ * Up to 500 tokens and FIDs combined can be specified on a single instance
+ * of {@link MulticastMessage}.
+ *
+ * @param fid A non-null, non-empty Firebase Installation ID.
+ * @return This builder.
+ */
+ public Builder addFid(@NonNull String fid) {
+ this.fids.add(fid);
+ return this;
+ }
+
+ /**
+ * Adds a collection of tokens to which the message should be sent. Up to 500
+ * tokens and FIDs combined can be specified on a single instance of
+ * {@link MulticastMessage}.
*
* @param tokens Collection of Firebase device registration tokens.
* @return This builder.
+ * @deprecated Use {@link #addAllFids(Collection)} instead.
*/
+ @Deprecated
public Builder addAllTokens(@NonNull Collection tokens) {
this.tokens.addAll(tokens);
return this;
}
+ /**
+ * Adds a collection of Firebase Installation IDs (FIDs) to which the message
+ * should be sent. Up to 500 tokens and FIDs combined can be specified on a
+ * single instance of {@link MulticastMessage}.
+ *
+ * @param fids Collection of Firebase Installation IDs.
+ * @return This builder.
+ */
+ public Builder addAllFids(@NonNull Collection fids) {
+ this.fids.addAll(fids);
+ return this;
+ }
+
/**
* Sets the notification information to be included in the message.
*
diff --git a/src/test/java/com/google/firebase/messaging/FirebaseMessagingIT.java b/src/test/java/com/google/firebase/messaging/FirebaseMessagingIT.java
index e084b8c29..9bdbf6a04 100644
--- a/src/test/java/com/google/firebase/messaging/FirebaseMessagingIT.java
+++ b/src/test/java/com/google/firebase/messaging/FirebaseMessagingIT.java
@@ -21,6 +21,7 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
import com.google.api.client.http.HttpResponseException;
import com.google.common.collect.ImmutableList;
@@ -97,6 +98,28 @@ public void testSendError() throws InterruptedException {
}
}
+ @Test
+ public void testSendFidError() throws InterruptedException {
+ FirebaseMessaging messaging = FirebaseMessaging.getInstance();
+ Message message = Message.builder()
+ .setNotification(Notification.builder()
+ .setTitle("Title")
+ .setBody("Body")
+ .build())
+ .setFid("not-a-fid")
+ .build();
+ try {
+ messaging.sendAsync(message, true).get();
+ fail("No exception thrown for invalid FID");
+ } catch (ExecutionException e) {
+ FirebaseMessagingException cause = (FirebaseMessagingException) e.getCause();
+ assertEquals(ErrorCode.INVALID_ARGUMENT, cause.getErrorCode());
+ assertEquals(MessagingErrorCode.INVALID_ARGUMENT, cause.getMessagingErrorCode());
+ assertNotNull(cause.getHttpResponse());
+ assertTrue(cause.getCause() instanceof HttpResponseException);
+ }
+ }
+
@Test
public void testSendEach() throws Exception {
List messages = new ArrayList<>();
@@ -194,6 +217,34 @@ public void testSendEachForMulticast() throws Exception {
}
}
+ @Test
+ public void testSendEachForMulticastFidsError() throws Exception {
+ MulticastMessage multicastMessage = MulticastMessage.builder()
+ .setNotification(Notification.builder()
+ .setTitle("Title")
+ .setBody("Body")
+ .build())
+ .addFid("not-a-fid")
+ .addFid("also-not-a-fid")
+ .build();
+
+ BatchResponse response = FirebaseMessaging.getInstance().sendEachForMulticast(
+ multicastMessage, true);
+
+ assertEquals(0, response.getSuccessCount());
+ assertEquals(2, response.getFailureCount());
+ assertEquals(2, response.getResponses().size());
+ for (SendResponse sendResponse : response.getResponses()) {
+ assertFalse(sendResponse.isSuccessful());
+ assertNull(sendResponse.getMessageId());
+ assertNotNull(sendResponse.getException());
+ assertEquals(ErrorCode.INVALID_ARGUMENT,
+ sendResponse.getException().getErrorCode());
+ assertEquals(MessagingErrorCode.INVALID_ARGUMENT,
+ sendResponse.getException().getMessagingErrorCode());
+ }
+ }
+
@Test
public void testSubscribe() throws Exception {
FirebaseMessaging messaging = FirebaseMessaging.getInstance();
diff --git a/src/test/java/com/google/firebase/messaging/MessageTest.java b/src/test/java/com/google/firebase/messaging/MessageTest.java
index 94ca4fa4a..0bca10df1 100644
--- a/src/test/java/com/google/firebase/messaging/MessageTest.java
+++ b/src/test/java/com/google/firebase/messaging/MessageTest.java
@@ -54,6 +54,28 @@ public void testEmptyMessage() throws IOException {
Message.builder().setCondition("'foo' in topics").build());
assertJsonEquals(ImmutableMap.of("token", "test-token"),
Message.builder().setToken("test-token").build());
+ assertJsonEquals(ImmutableMap.of("fid", "test-fid"),
+ Message.builder().setFid("test-fid").build());
+ }
+
+ @Test
+ public void testMultipleTargets() {
+ List builders = ImmutableList.of(
+ Message.builder().setToken("token").setFid("fid"),
+ Message.builder().setToken("token").setTopic("topic"),
+ Message.builder().setFid("fid").setCondition("cond"),
+ Message.builder().setTopic("topic").setCondition("cond"),
+ Message.builder().setToken("token").setFid("fid").setTopic("topic").setCondition("cond")
+ );
+ for (int i = 0; i < builders.size(); i++) {
+ try {
+ builders.get(i).build();
+ fail("No error thrown for multiple targets: " + i);
+ } catch (IllegalArgumentException expected) {
+ assertEquals("Exactly one of token, fid, topic or condition must be specified",
+ expected.getMessage());
+ }
+ }
}
@Test
diff --git a/src/test/java/com/google/firebase/messaging/MulticastMessageTest.java b/src/test/java/com/google/firebase/messaging/MulticastMessageTest.java
index aa0dae10c..481ccccc9 100644
--- a/src/test/java/com/google/firebase/messaging/MulticastMessageTest.java
+++ b/src/test/java/com/google/firebase/messaging/MulticastMessageTest.java
@@ -86,6 +86,21 @@ public void testTooManyTokens() {
}
}
+ @Test
+ public void testTooManyFids() {
+ MulticastMessage.Builder builder = MulticastMessage.builder();
+ for (int i = 0; i < 501; i++) {
+ builder.addFid("fid" + i);
+ }
+ try {
+ builder.build();
+ fail("No error thrown for more than 500 fids");
+ } catch (IllegalArgumentException expected) {
+ assertEquals("no more than 500 tokens and fids combined can be specified",
+ expected.getMessage());
+ }
+ }
+
@Test(expected = NullPointerException.class)
public void testNullToken() {
MulticastMessage.builder().addToken(null).build();
@@ -96,6 +111,92 @@ public void testEmptyToken() {
MulticastMessage.builder().addToken("").build();
}
+ @Test
+ public void testMulticastMessageFids() {
+ MulticastMessage multicastMessage = MulticastMessage.builder()
+ .setAndroidConfig(ANDROID)
+ .setApnsConfig(APNS)
+ .setWebpushConfig(WEBPUSH)
+ .setNotification(NOTIFICATION)
+ .setFcmOptions(FCM_OPTIONS)
+ .putData("key1", "value1")
+ .putAllData(ImmutableMap.of("key2", "value2"))
+ .addFid("fid1")
+ .addAllFids(ImmutableList.of("fid2", "fid3"))
+ .build();
+
+ List messages = multicastMessage.getMessageList();
+
+ assertEquals(3, messages.size());
+ for (int i = 0; i < 3; i++) {
+ Message message = messages.get(i);
+ assertMessageFid(message, "fid" + (i + 1));
+ }
+ }
+
+ @Test
+ public void testMulticastMessageMixed() {
+ MulticastMessage multicastMessage = MulticastMessage.builder()
+ .setAndroidConfig(ANDROID)
+ .setApnsConfig(APNS)
+ .setWebpushConfig(WEBPUSH)
+ .setNotification(NOTIFICATION)
+ .setFcmOptions(FCM_OPTIONS)
+ .putData("key1", "value1")
+ .putAllData(ImmutableMap.of("key2", "value2"))
+ .addToken("token1")
+ .addFid("fid1")
+ .addToken("token2")
+ .addFid("fid2")
+ .build();
+
+ List messages = multicastMessage.getMessageList();
+
+ assertEquals(4, messages.size());
+ assertMessage(messages.get(0), "token1");
+ assertMessage(messages.get(1), "token2");
+ assertMessageFid(messages.get(2), "fid1");
+ assertMessageFid(messages.get(3), "fid2");
+ }
+
+ @Test
+ public void testTooManyTargetsCombined() {
+ MulticastMessage.Builder builder = MulticastMessage.builder();
+ for (int i = 0; i < 250; i++) {
+ builder.addToken("token" + i);
+ }
+ for (int i = 0; i < 251; i++) {
+ builder.addFid("fid" + i);
+ }
+ try {
+ builder.build();
+ fail("No error thrown for more than 500 combined targets");
+ } catch (IllegalArgumentException expected) {
+ assertEquals("no more than 500 tokens and fids combined can be specified",
+ expected.getMessage());
+ }
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void testNullFid() {
+ MulticastMessage.builder().addFid(null).build();
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testEmptyFid() {
+ MulticastMessage.builder().addFid("").build();
+ }
+
+ private void assertMessageFid(Message message, String expectedFid) {
+ assertSame(ANDROID, message.getAndroidConfig());
+ assertSame(APNS, message.getApnsConfig());
+ assertSame(WEBPUSH, message.getWebpushConfig());
+ assertSame(NOTIFICATION, message.getNotification());
+ assertSame(FCM_OPTIONS, message.getFcmOptions());
+ assertEquals(ImmutableMap.of("key1", "value1", "key2", "value2"), message.getData());
+ assertEquals(expectedFid, message.getFid());
+ }
+
private void assertMessage(Message message, String expectedToken) {
assertSame(ANDROID, message.getAndroidConfig());
assertSame(APNS, message.getApnsConfig());