Repository: firebase/firebase-admin-java
Pull request #1094 (2025-05-23 22:19:15)

feat(fcm): Add `liveActivityToken` to `ApnsConfig`
Adds a new `liveActivityToken` field to the `ApnsConfig` class, allowing you to specify a Live Activity token for APNS messages.

The changes include:
- Addition of the `liveActivityToken` field in `ApnsConfig.java` and its associated builder.
- Updates to unit tests in `MessageTest.java` to verify the correct serialization of the new field and to include it in existing test cases. A new test method `testApnsMessageWithOnlyLiveActivityToken` was added for specific testing.
- Update to the integration test in `FirebaseMessagingIT.java` to include the `liveActivityToken` in a test message.


Patch:
diff --git a/src/main/java/com/google/firebase/messaging/ApnsConfig.java b/src/main/java/com/google/firebase/messaging/ApnsConfig.java
index 0e5de86..39092ac 100644
--- a/src/main/java/com/google/firebase/messaging/ApnsConfig.java
+++ b/src/main/java/com/google/firebase/messaging/ApnsConfig.java
@@ -41,6 +41,9 @@ public class ApnsConfig {
   @Key("fcm_options")
   private final ApnsFcmOptions fcmOptions;
 
+  @Key("live_activity_token")
+  private final String liveActivityToken;
+
   private ApnsConfig(Builder builder) {
     checkArgument(builder.aps != null, "aps must be specified");
     checkArgument(!builder.customData.containsKey("aps"),
@@ -51,6 +54,7 @@ public class ApnsConfig {
         .put("aps", builder.aps.getFields())
         .build();
     this.fcmOptions = builder.fcmOptions;
+    this.liveActivityToken = builder.liveActivityToken;
   }
 
   /**
@@ -68,6 +72,7 @@ public class ApnsConfig {
     private final Map<String, Object> customData = new HashMap<>();
     private Aps aps;
     private ApnsFcmOptions fcmOptions;
+    private String liveActivityToken;
 
     private Builder() {}
 
@@ -137,6 +142,17 @@ public class ApnsConfig {
       return this;
     }
 
+    /**
+     * Sets the Live Activity token.
+     *
+     * @param liveActivityToken Live Activity token.
+     * @return This builder.
+     */
+    public Builder setLiveActivityToken(String liveActivityToken) {
+      this.liveActivityToken = liveActivityToken;
+      return this;
+    }
+
     /**
      * Creates a new {@link ApnsConfig} instance from the parameters set on this builder.
      *


Test patch:
diff --git a/src/test/java/com/google/firebase/messaging/FirebaseMessagingIT.java b/src/test/java/com/google/firebase/messaging/FirebaseMessagingIT.java
index d937578..91bfc5b 100644
--- a/src/test/java/com/google/firebase/messaging/FirebaseMessagingIT.java
+++ b/src/test/java/com/google/firebase/messaging/FirebaseMessagingIT.java
@@ -63,6 +63,7 @@ public class FirebaseMessagingIT {
                     .setBody("Body")
                     .build())
                 .build())
+            .setLiveActivityToken("integration-test-live-activity-token")
             .build())
         .setWebpushConfig(WebpushConfig.builder()
             .putHeader("X-Custom-Val", "Foo")
diff --git a/src/test/java/com/google/firebase/messaging/MessageTest.java b/src/test/java/com/google/firebase/messaging/MessageTest.java
index cea440c..4ac1782 100644
--- a/src/test/java/com/google/firebase/messaging/MessageTest.java
+++ b/src/test/java/com/google/firebase/messaging/MessageTest.java
@@ -411,6 +411,7 @@ public class MessageTest {
             .putCustomData("cd1", "cd-v1")
             .putAllCustomData(ImmutableMap.<String, Object>of("cd2", "cd-v2", "cd3", true))
             .setAps(Aps.builder().build())
+            .setLiveActivityToken("test-live-activity-token")
             .build())
         .setTopic("test-topic")
         .build();
@@ -421,10 +422,11 @@ public class MessageTest {
         .put("cd3", true)
         .put("aps", ImmutableMap.of())
         .build();
-    Map<String, Object> data = ImmutableMap.<String, Object>of(
-        "headers", ImmutableMap.of("k1", "v1", "k2", "v2", "k3", "v3"),
-        "payload", payload
-    );
+    Map<String, Object> data = ImmutableMap.<String, Object>builder()
+        .put("headers", ImmutableMap.of("k1", "v1", "k2", "v2", "k3", "v3"))
+        .put("payload", payload)
+        .put("live_activity_token", "test-live-activity-token")
+        .build();
     assertJsonEquals(ImmutableMap.of("topic", "test-topic", "apns", data), message);
   }
 
@@ -442,6 +444,7 @@ public class MessageTest {
                 .setSound("test-sound")
                 .setThreadId("test-thread-id")
                 .build())
+            .setLiveActivityToken("test-live-activity-token-aps")
             .build())
         .setTopic("test-topic")
         .build();
@@ -459,7 +462,10 @@ public class MessageTest {
     assertJsonEquals(
         ImmutableMap.of(
             "topic", "test-topic",
-            "apns", ImmutableMap.<String, Object>of("payload", payload)),
+            "apns", ImmutableMap.<String, Object>builder()
+                .put("payload", payload)
+                .put("live_activity_token", "test-live-activity-token-aps")
+                .build()),
         message);
 
     message = Message.builder()
@@ -825,6 +831,7 @@ public class MessageTest {
         .setApnsConfig(
             ApnsConfig.builder().setAps(Aps.builder().build())
                 .setFcmOptions(ApnsFcmOptions.builder().setImage(TEST_IMAGE_URL_APNS).build())
+                .setLiveActivityToken("test-live-activity-token-image")
                 .build()).build();
 
     ImmutableMap<String, Object> notification =
@@ -837,6 +844,7 @@ public class MessageTest {
         ImmutableMap.<String, Object>builder()
             .put("fcm_options", ImmutableMap.of("image", TEST_IMAGE_URL_APNS))
             .put("payload", ImmutableMap.of("aps", ImmutableMap.of()))
+            .put("live_activity_token", "test-live-activity-token-image")
             .build();
     ImmutableMap<String, Object> expected =
         ImmutableMap.<String, Object>builder()
@@ -847,6 +855,34 @@ public class MessageTest {
     assertJsonEquals(expected, message);
   }
 
+  @Test
+  public void testApnsMessageWithOnlyLiveActivityToken() throws IOException {
+    Message message = Message.builder()
+        .setApnsConfig(ApnsConfig.builder()
+            .setAps(Aps.builder().build())
+            .setLiveActivityToken("only-live-activity")
+            .build())
+        .setTopic("test-topic")
+        .build();
+    Map<String, Object> expectedApns = ImmutableMap.<String, Object>builder()
+        .put("live_activity_token", "only-live-activity")
+        .put("payload", ImmutableMap.of("aps", ImmutableMap.of()))
+        .build();
+    assertJsonEquals(ImmutableMap.of("topic", "test-topic", "apns", expectedApns), message);
+
+    // Test without live activity token
+    message = Message.builder()
+        .setApnsConfig(ApnsConfig.builder()
+            .setAps(Aps.builder().build())
+            .build())
+        .setTopic("test-topic")
+        .build();
+    expectedApns = ImmutableMap.<String, Object>builder()
+        .put("payload", ImmutableMap.of("aps", ImmutableMap.of()))
+        .build();
+    assertJsonEquals(ImmutableMap.of("topic", "test-topic", "apns", expectedApns), message);
+  }
+
   @Test
   public void testInvalidColorInAndroidNotificationLightSettings() {
     try {