Repository: jchambers/pushy (java)

Issue:
add attributes-type and attributes to the payloadbuilder
In the [documentation for remote starting a live activity](https://developer.apple.com/documentation/activitykit/starting-and-updating-live-activities-with-activitykit-push-notifications#Construct-the-payload-that-starts-a-Live-Activity) it is mentioned that the attributes and attributes-type keys should be present in the payload when starting a live activity. These are not available in the payload builder yet.

Pull request description:
Live activity attributes type
This adds support for the `attributes-type` and `attributes` payload fields for Live Activities.

This fixes #1050.

Patch:
diff --git a/pushy/src/main/java/com/eatthepath/pushy/apns/util/ApnsPayloadBuilder.java b/pushy/src/main/java/com/eatthepath/pushy/apns/util/ApnsPayloadBuilder.java
index b2534daf..7b4083e5 100644
--- a/pushy/src/main/java/com/eatthepath/pushy/apns/util/ApnsPayloadBuilder.java
+++ b/pushy/src/main/java/com/eatthepath/pushy/apns/util/ApnsPayloadBuilder.java
@@ -89,6 +89,9 @@ public abstract class ApnsPayloadBuilder {
 
     private Instant staleDate = null;
 
+    private String attributesType = null;
+    private Map<String, Object> attributes = null;
+
     private Map<String, Object> contentState = null;
 
     private static final String APS_KEY = "aps";
@@ -125,6 +128,8 @@ public abstract class ApnsPayloadBuilder {
     private static final String DISMISSAL_DATE_KEY = "dismissal-date";
     private static final String STALE_DATE_KEY = "stale-date";
     private static final String EVENT_KEY = "event";
+    private static final String ATTRIBUTES_TYPE_KEY = "attributes-type";
+    private static final String ATTRIBUTES_KEY = "attributes";
     private static final String CONTENT_STATE_KEY = "content-state";
 
     public static final String[] EMPTY_STRING_ARRAY = new String[0];
@@ -756,6 +761,46 @@ public abstract class ApnsPayloadBuilder {
         return this;
     }
 
+    /**
+     * Sets the type of iOS {@code ActivityAttributes} used for a Live Activity. Must be set for a notification that
+     * starts a live activity.
+     *
+     * @param attributesType the type of {@code ActivityAttributes} used for a Live Activity
+     *
+     * @return a reference to this payload builder
+     *
+     * @see #setAttributes(Map) 
+     * @see <a href="https://developer.apple.com/documentation/activitykit/starting-and-updating-live-activities-with-activitykit-push-notifications#Construct-the-payload-that-starts-a-Live-Activity">
+     *      Construct the payload that starts a Live Activity</a>
+     * @see <a href="https://developer.apple.com/documentation/activitykit/activityattributes">ActivityKit - ActivityAttributes</a>
+     *
+     * @since 0.15.4
+     */
+    public ApnsPayloadBuilder setAttributesType(final String attributesType) {
+        this.attributesType = attributesType;
+        return this;
+    }
+
+    /**
+     * Sets the values of iOS {@code ContentState} attributes when starting a Live Activity. Must be set for a
+     * notification that starts a live activity.
+     *
+     * @param attributes the attributes used to populate the {@code ContentState} for the newly-started live activity
+     *
+     * @return a reference to this payload builder
+     *
+     * @see #setAttributesType(String) 
+     * @see <a href="https://developer.apple.com/documentation/activitykit/starting-and-updating-live-activities-with-activitykit-push-notifications#Construct-the-payload-that-starts-a-Live-Activity">
+     *      Construct the payload that starts a Live Activity</a>
+     * @see <a href="https://developer.apple.com/documentation/activitykit/activityattributes/contentstate">ActivityKit - ContentState</a>
+     *
+     * @since 0.15.4
+     */
+    public ApnsPayloadBuilder setAttributes(final Map<String, Object> attributes) {
+        this.attributes = attributes;
+        return this;
+    }
+
     /**
      * <p>Sets the content state object inside content-state payload for Live Activities.</p>
      *
@@ -768,6 +813,8 @@ public abstract class ApnsPayloadBuilder {
      *
      * @see <a href="https://developer.apple.com/documentation/activitykit/update-and-end-your-live-activity-with-remote-push-notifications">
      *      Updating and ending your Live Activity with remote push notifications</a>
+     *
+     * @since 0.15.2
      */
     public ApnsPayloadBuilder setContentState(final Map<String, Object> contentState) {
         this.contentState = contentState;
@@ -784,6 +831,8 @@ public abstract class ApnsPayloadBuilder {
      *
      * @see <a href="https://developer.apple.com/documentation/activitykit/update-and-end-your-live-activity-with-remote-push-notifications">
      *      Updating and ending your Live Activity with remote push notifications</a>
+     *
+     * @since 0.15.2
      */
     public ApnsPayloadBuilder setEvent(final LiveActivityEvent event) {
         this.event = event;
@@ -799,6 +848,8 @@ public abstract class ApnsPayloadBuilder {
      *
      * @see <a href="https://developer.apple.com/documentation/activitykit/update-and-end-your-live-activity-with-remote-push-notifications">
      *      Updating and ending your Live Activity with remote push notifications</a>
+     *
+     * @since 0.15.2
      */
     public ApnsPayloadBuilder setTimestamp(final Instant timestamp) {
         this.timestamp = timestamp;
@@ -819,6 +870,8 @@ public abstract class ApnsPayloadBuilder {
      *
      * @see <a href="https://developer.apple.com/documentation/activitykit/update-and-end-your-live-activity-with-remote-push-notifications">
      *      Updating and ending your Live Activity with remote push notifications</a>
+     *
+     * @since 0.15.2
      */
     public ApnsPayloadBuilder setDismissalDate(final Instant dismissalDate) {
         this.dismissalDate = dismissalDate;
@@ -891,6 +944,14 @@ public abstract class ApnsPayloadBuilder {
                 aps.put(EVENT_KEY, this.event.getValue());
             }
 
+            if (this.attributesType != null) {
+                aps.put(ATTRIBUTES_TYPE_KEY, this.attributesType);
+            }
+
+            if (this.attributes != null) {
+                aps.put(ATTRIBUTES_KEY, this.attributes);
+            }
+
             if (this.contentState != null) {
                 aps.put(CONTENT_STATE_KEY, this.contentState);
             }
diff --git a/pushy/src/main/java/com/eatthepath/pushy/apns/util/LiveActivityEvent.java b/pushy/src/main/java/com/eatthepath/pushy/apns/util/LiveActivityEvent.java
index ccd1460b..78ff9a17 100644
--- a/pushy/src/main/java/com/eatthepath/pushy/apns/util/LiveActivityEvent.java
+++ b/pushy/src/main/java/com/eatthepath/pushy/apns/util/LiveActivityEvent.java
@@ -25,11 +25,16 @@ package com.eatthepath.pushy.apns.util;
 /**
  * An enumeration of accepted actions for Live Activity notification payloads.
  *
- * @see <a href="https://developer.apple.com/documentation/activitykit/update-and-end-your-live-activity-with-remote-push-notifications">Updating and ending your Live Activity with remote push notifications</a>
+ * @see <a href="https://developer.apple.com/documentation/activitykit/starting-and-updating-live-activities-with-activitykit-push-notifications#Construct-the-payload-that-starts-a-Live-Activity">Starting and updating Live Activities with ActivityKit push notifications</a>
  *
  * @since 0.15.2
 */
 public enum LiveActivityEvent {
+    /**
+     * Used to start a Live Activity.
+     */
+    START("start"),
+
     /**
      * Used to update a Live Activity.
      */


Test patch:
diff --git a/pushy/src/test/java/com/eatthepath/pushy/apns/util/ApnsPayloadBuilderTest.java b/pushy/src/test/java/com/eatthepath/pushy/apns/util/ApnsPayloadBuilderTest.java
index 21c06dec..ff9a4815 100644
--- a/pushy/src/test/java/com/eatthepath/pushy/apns/util/ApnsPayloadBuilderTest.java
+++ b/pushy/src/test/java/com/eatthepath/pushy/apns/util/ApnsPayloadBuilderTest.java
@@ -29,7 +29,6 @@ import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.Arguments;
 import org.junit.jupiter.params.provider.EnumSource;
 import org.junit.jupiter.params.provider.MethodSource;
-import org.junit.jupiter.params.provider.ValueSource;
 
 import java.text.ParseException;
 import java.time.Instant;
@@ -665,6 +664,42 @@ public abstract class ApnsPayloadBuilderTest {
         assertEquals(event.getValue(), aps.get("event"));
     }
 
+    @Test
+    void testSetAttributesType() {
+        final String attributesType = "TestAttributes";
+        this.builder.setAttributesType(attributesType);
+
+        final Map<String, Object> aps = this.extractApsObjectFromPayloadString(this.builder.build());
+
+        assertEquals(attributesType, aps.get("attributes-type"));
+    }
+
+    @Test
+    void testSetAttributes() {
+        final String keyForStringValue = "string";
+        final String stringValue = "Hello";
+
+        final String keyForLongValue = "integer";
+        final long longValue = 12;
+
+        final String keyForMapValue = "map";
+        final Map<String, Object> attributes = new HashMap<>();
+        final Map<String, Object> subMap = new HashMap<>();
+        subMap.put("boolean", true);
+        attributes.put(keyForLongValue, longValue);
+        attributes.put(keyForStringValue, stringValue);
+        attributes.put(keyForMapValue, subMap);
+
+        this.builder.setAttributes(attributes);
+
+        final Map<String, Object> aps = this.extractApsObjectFromPayloadString(this.builder.build());
+        final Map<String, Object> serializedAttributes = (Map<String, Object>) aps.get("attributes");
+
+        assertEquals(stringValue, serializedAttributes.get(keyForStringValue));
+        assertEquals(longValue, serializedAttributes.get(keyForLongValue));
+        assertEquals(subMap, serializedAttributes.get(keyForMapValue));
+    }
+
     @Test
     void testAddContentStateProperty() {
         final String keyForStringValue = "string";