Skip to content

Commit 76346cb

Browse files
authored
Implementing support for APNS content-mutable field (firebase#156)
* Implementing support for APNS content-mutable field * Updated annotations and docs * Corrected mutable-content option name * Renamed customFields to customData * Deleted test Main
1 parent a580111 commit 76346cb

4 files changed

Lines changed: 99 additions & 30 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# Unreleased
22

3+
- [added] Added new `setMutableContent()`, `putCustomData()` and
4+
`putAllCustomData()` methods to the `Aps.Builder` API.
35
- [fixed] Improved error handling in FCM by mapping more server-side
46
errors to client-side error codes.
57

src/main/java/com/google/firebase/messaging/ApnsConfig.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ private ApnsConfig(Builder builder) {
4545
this.headers = builder.headers.isEmpty() ? null : ImmutableMap.copyOf(builder.headers);
4646
this.payload = ImmutableMap.<String, Object>builder()
4747
.putAll(builder.customData)
48-
.put("aps", builder.aps)
48+
.put("aps", builder.aps.getFields())
4949
.build();
5050
}
5151

src/main/java/com/google/firebase/messaging/Aps.java

Lines changed: 78 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -18,46 +18,53 @@
1818

1919
import static com.google.common.base.Preconditions.checkArgument;
2020

21-
import com.google.api.client.util.Key;
2221
import com.google.common.base.Strings;
22+
import com.google.common.collect.ImmutableMap;
23+
import com.google.firebase.internal.NonNull;
24+
import java.util.HashMap;
25+
import java.util.Map;
2326

2427
/**
2528
* Represents the <a href="https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/PayloadKeyReference.html">
2629
* aps dictionary</a> that is part of every APNS message.
2730
*/
2831
public class Aps {
2932

30-
@Key("alert")
31-
private final Object alert;
32-
33-
@Key("badge")
34-
private final Integer badge;
35-
36-
@Key("sound")
37-
private final String sound;
38-
39-
@Key("content-available")
40-
private final Integer contentAvailable;
41-
42-
@Key("category")
43-
private final String category;
44-
45-
@Key("thread-id")
46-
private final String threadId;
33+
private final Map<String, Object> fields;
4734

4835
private Aps(Builder builder) {
4936
checkArgument(Strings.isNullOrEmpty(builder.alertString) || (builder.alert == null),
5037
"Multiple alert specifications (string and ApsAlert) found.");
38+
ImmutableMap.Builder<String, Object> fields = ImmutableMap.builder();
5139
if (builder.alert != null) {
52-
this.alert = builder.alert;
53-
} else {
54-
this.alert = builder.alertString;
55-
}
56-
this.badge = builder.badge;
57-
this.sound = builder.sound;
58-
this.contentAvailable = builder.contentAvailable ? 1 : null;
59-
this.category = builder.category;
60-
this.threadId = builder.threadId;
40+
fields.put("alert", builder.alert);
41+
} else if (builder.alertString != null) {
42+
fields.put("alert", builder.alertString);
43+
}
44+
if (builder.badge != null) {
45+
fields.put("badge", builder.badge);
46+
}
47+
if (builder.sound != null) {
48+
fields.put("sound", builder.sound);
49+
}
50+
if (builder.contentAvailable) {
51+
fields.put("content-available", 1);
52+
}
53+
if (builder.mutableContent) {
54+
fields.put("mutable-content", 1);
55+
}
56+
if (builder.category != null) {
57+
fields.put("category", builder.category);
58+
}
59+
if (builder.threadId != null) {
60+
fields.put("thread-id", builder.threadId);
61+
}
62+
fields.putAll(builder.customData);
63+
this.fields = fields.build();
64+
}
65+
66+
Map<String, Object> getFields() {
67+
return this.fields;
6168
}
6269

6370
/**
@@ -76,8 +83,10 @@ public static class Builder {
7683
private Integer badge;
7784
private String sound;
7885
private boolean contentAvailable;
86+
private boolean mutableContent;
7987
private String category;
8088
private String threadId;
89+
private final Map<String, Object> customData = new HashMap<>();
8190

8291
private Builder() {}
8392

@@ -137,6 +146,41 @@ public Builder setContentAvailable(boolean contentAvailable) {
137146
return this;
138147
}
139148

149+
/**
150+
* Specifies whether to set the {@code mutable-content} property on the message, so the
151+
* clients can modify the notification via app extensions.
152+
*
153+
* @param mutableContent True to make the content mutable via app extensions.
154+
* @return This builder.
155+
*/
156+
public Builder setMutableContent(boolean mutableContent) {
157+
this.mutableContent = mutableContent;
158+
return this;
159+
}
160+
161+
/**
162+
* Puts a custom key-value pair to the aps dictionary.
163+
*
164+
* @param key A non-null key.
165+
* @param value A non-null, json-serializable value.
166+
* @return This builder.
167+
*/
168+
public Builder putCustomData(@NonNull String key, @NonNull Object value) {
169+
this.customData.put(key, value);
170+
return this;
171+
}
172+
173+
/**
174+
* Puts all the key-value pairs in the specified map to the aps dictionary.
175+
*
176+
* @param fields A non-null map. Map must not contain null keys or values.
177+
* @return This builder.
178+
*/
179+
public Builder putAllCustomData(@NonNull Map<String, Object> fields) {
180+
this.customData.putAll(fields);
181+
return this;
182+
}
183+
140184
/**
141185
* Sets the notification type.
142186
*
@@ -159,6 +203,13 @@ public Builder setThreadId(String threadId) {
159203
return this;
160204
}
161205

206+
/**
207+
* Builds a new {@link Aps} instance from the fields set on this builder.
208+
*
209+
* @return A non-null {@link Aps}.
210+
* @throws IllegalArgumentException If the alert is specified both as an object and a string.
211+
* Or if the same field is set both using a setter method, and as a custom field.
212+
*/
162213
public Aps build() {
163214
return new Aps(this);
164215
}

src/test/java/com/google/firebase/messaging/MessageTest.java

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
public class MessageTest {
3737

3838
@Test(expected = IllegalArgumentException.class)
39-
public void testMessageWithoutTarget() throws IOException {
39+
public void testMessageWithoutTarget() {
4040
Message.builder().build();
4141
}
4242

@@ -214,7 +214,7 @@ public void testAndroidMessageWithoutLocalization() throws IOException {
214214
}
215215

216216
@Test
217-
public void testInvalidAndroidConfig() throws IOException {
217+
public void testInvalidAndroidConfig() {
218218
try {
219219
AndroidConfig.builder().setTtl(-1).build();
220220
fail("No error thrown for invalid ttl");
@@ -363,6 +363,7 @@ public void testApnsMessageWithPayloadAndAps() throws IOException {
363363
.setBadge(42)
364364
.setCategory("test-category")
365365
.setContentAvailable(true)
366+
.setMutableContent(true)
366367
.setSound("test-sound")
367368
.setThreadId("test-thread-id")
368369
.build())
@@ -376,6 +377,7 @@ public void testApnsMessageWithPayloadAndAps() throws IOException {
376377
.put("badge", new BigDecimal(42))
377378
.put("category", "test-category")
378379
.put("content-available", new BigDecimal(1))
380+
.put("mutable-content", new BigDecimal(1))
379381
.put("sound", "test-sound")
380382
.put("thread-id", "test-thread-id")
381383
.build());
@@ -404,6 +406,8 @@ public void testApnsMessageWithPayloadAndAps() throws IOException {
404406
.setCategory("test-category")
405407
.setSound("test-sound")
406408
.setThreadId("test-thread-id")
409+
.putCustomData("ck1", "cv1")
410+
.putAllCustomData(ImmutableMap.<String, Object>of("ck2", "cv2", "ck3", 1))
407411
.build())
408412
.build())
409413
.setTopic("test-topic")
@@ -424,6 +428,9 @@ public void testApnsMessageWithPayloadAndAps() throws IOException {
424428
.put("category", "test-category")
425429
.put("sound", "test-sound")
426430
.put("thread-id", "test-thread-id")
431+
.put("ck1", "cv1")
432+
.put("ck2", "cv2")
433+
.put("ck3", new BigDecimal(1))
427434
.build());
428435
assertJsonEquals(
429436
ImmutableMap.of(
@@ -456,6 +463,15 @@ public void testInvalidApnsConfig() {
456463
// expected
457464
}
458465

466+
builder = Aps.builder().setMutableContent(true).putCustomData("mutable-content", 1);
467+
try {
468+
builder.build();
469+
fail("No error thrown for invalid aps");
470+
} catch (IllegalArgumentException expected) {
471+
// expected
472+
}
473+
474+
459475
List<ApsAlert.Builder> notificationBuilders = ImmutableList.of(
460476
ApsAlert.builder().addLocalizationArg("foo"),
461477
ApsAlert.builder().addTitleLocalizationArg("foo")

0 commit comments

Comments
 (0)