Skip to content

Commit a38e47c

Browse files
authored
Implemented CriticalSound API for FCM (#233)
* Implemented CriticalSounf API for FCM * Made the name field required * Ignoring empty values in sound field * Documentation updates
1 parent 7f5c012 commit a38e47c

File tree

4 files changed

+216
-17
lines changed

4 files changed

+216
-17
lines changed

CHANGELOG.md

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

3+
- `Aps` class now supports configuring a critical sound. A new
4+
`CriticalSound` class has been introduced for this purpose.
35
- [fixed] `Firestore` instances initialized by the SDK are now cleaned
46
up, when `FirebaseApp.delete()` is called.
57

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

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ public class Aps {
3535
private Aps(Builder builder) {
3636
checkArgument(Strings.isNullOrEmpty(builder.alertString) || (builder.alert == null),
3737
"Multiple alert specifications (string and ApsAlert) found.");
38+
checkArgument(Strings.isNullOrEmpty(builder.sound) || (builder.criticalSound == null),
39+
"Multiple sound specifications (sound and CriticalSound) found.");
3840
ImmutableMap.Builder<String, Object> fields = ImmutableMap.builder();
3941
if (builder.alert != null) {
4042
fields.put("alert", builder.alert);
@@ -44,8 +46,10 @@ private Aps(Builder builder) {
4446
if (builder.badge != null) {
4547
fields.put("badge", builder.badge);
4648
}
47-
if (builder.sound != null) {
49+
if (!Strings.isNullOrEmpty(builder.sound)) {
4850
fields.put("sound", builder.sound);
51+
} else if (builder.criticalSound != null) {
52+
fields.put("sound", builder.criticalSound.getFields());
4953
}
5054
if (builder.contentAvailable) {
5155
fields.put("content-available", 1);
@@ -82,6 +86,7 @@ public static class Builder {
8286
private ApsAlert alert;
8387
private Integer badge;
8488
private String sound;
89+
private CriticalSound criticalSound;
8590
private boolean contentAvailable;
8691
private boolean mutableContent;
8792
private String category;
@@ -125,7 +130,8 @@ public Builder setBadge(int badge) {
125130
}
126131

127132
/**
128-
* Sets the sound to be played with the message.
133+
* Sets the sound to be played with the message. For critical alerts use the
134+
* {@link #setSound(CriticalSound)} method.
129135
*
130136
* @param sound Sound file name or {@code "default"}.
131137
* @return This builder.
@@ -135,6 +141,17 @@ public Builder setSound(String sound) {
135141
return this;
136142
}
137143

144+
/**
145+
* Sets the critical alert sound to be played with the message.
146+
*
147+
* @param sound A {@link CriticalSound} instance containing the alert sound configuration.
148+
* @return This builder.
149+
*/
150+
public Builder setSound(CriticalSound sound) {
151+
this.criticalSound = sound;
152+
return this;
153+
}
154+
138155
/**
139156
* Specifies whether to configure a background update notification.
140157
*
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
/*
2+
* Copyright 2018 Google Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.firebase.messaging;
18+
19+
import static com.google.common.base.Preconditions.checkArgument;
20+
21+
import com.google.common.base.Strings;
22+
import com.google.common.collect.ImmutableMap;
23+
import java.util.Map;
24+
25+
/**
26+
* The sound configuration for APNs critical alerts.
27+
*/
28+
public final class CriticalSound {
29+
30+
private final Map<String, Object> fields;
31+
32+
private CriticalSound(Builder builder) {
33+
checkArgument(!Strings.isNullOrEmpty(builder.name), "name must not be null or empty");
34+
ImmutableMap.Builder<String, Object> fields = ImmutableMap.<String, Object>builder()
35+
.put("name", builder.name);
36+
if (builder.critical) {
37+
fields.put("critical", 1);
38+
}
39+
if (builder.volume != null) {
40+
checkArgument(builder.volume >= 0 && builder.volume <= 1,
41+
"volume must be in the interval [0,1]");
42+
fields.put("volume", builder.volume);
43+
}
44+
this.fields = fields.build();
45+
}
46+
47+
Map<String, Object> getFields() {
48+
return fields;
49+
}
50+
51+
/**
52+
* Creates a new {@link CriticalSound.Builder}.
53+
*
54+
* @return A {@link CriticalSound.Builder} instance.
55+
*/
56+
public static Builder builder() {
57+
return new Builder();
58+
}
59+
60+
public static final class Builder {
61+
62+
private boolean critical;
63+
private String name;
64+
private Double volume;
65+
66+
private Builder() {
67+
}
68+
69+
/**
70+
* Sets the critical alert flag on the sound configuration.
71+
*
72+
* @param critical True to set the critical alert flag.
73+
* @return This builder.
74+
*/
75+
public Builder setCritical(boolean critical) {
76+
this.critical = critical;
77+
return this;
78+
}
79+
80+
/**
81+
* The name of a sound file in your app's main bundle or in the {@code Library/Sounds} folder
82+
* of your app’s container directory. Specify the string {@code default} to play the system
83+
* sound.
84+
*
85+
* @param name Sound file name.
86+
* @return This builder.
87+
*/
88+
public Builder setName(String name) {
89+
this.name = name;
90+
return this;
91+
}
92+
93+
/**
94+
* The volume for the critical alert's sound. Must be a value between 0.0 (silent) and 1.0
95+
* (full volume).
96+
*
97+
* @param volume A volume between 0.0 (inclusive) and 1.0 (inclusive).
98+
* @return This builder.
99+
*/
100+
public Builder setVolume(double volume) {
101+
this.volume = volume;
102+
return this;
103+
}
104+
105+
/**
106+
* Builds a new {@link CriticalSound} instance from the fields set on this builder.
107+
*
108+
* @return A non-null {@link CriticalSound}.
109+
* @throws IllegalArgumentException If the volume value is out of range.
110+
*/
111+
public CriticalSound build() {
112+
return new CriticalSound(this);
113+
}
114+
}
115+
}

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

Lines changed: 80 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,72 @@ public void testApnsMessageWithPayloadAndAps() throws IOException {
448448
message);
449449
}
450450

451+
@Test
452+
public void testApnsMessageWithCriticalSound() throws IOException {
453+
Message message = Message.builder()
454+
.setApnsConfig(ApnsConfig.builder()
455+
.setAps(Aps.builder()
456+
.setSound(CriticalSound.builder() // All fields
457+
.setCritical(true)
458+
.setName("default")
459+
.setVolume(0.5)
460+
.build())
461+
.build())
462+
.build())
463+
.setTopic("test-topic")
464+
.build();
465+
Map<String, Object> payload = ImmutableMap.<String, Object>of(
466+
"aps", ImmutableMap.builder()
467+
.put("sound", ImmutableMap.of(
468+
"critical", new BigDecimal(1),
469+
"name", "default",
470+
"volume", new BigDecimal(0.5)))
471+
.build());
472+
assertJsonEquals(
473+
ImmutableMap.of(
474+
"topic", "test-topic",
475+
"apns", ImmutableMap.<String, Object>of("payload", payload)),
476+
message);
477+
478+
message = Message.builder()
479+
.setApnsConfig(ApnsConfig.builder()
480+
.setAps(Aps.builder()
481+
.setSound(CriticalSound.builder() // Name field only
482+
.setName("default")
483+
.build())
484+
.build())
485+
.build())
486+
.setTopic("test-topic")
487+
.build();
488+
payload = ImmutableMap.<String, Object>of(
489+
"aps", ImmutableMap.builder()
490+
.put("sound", ImmutableMap.of("name", "default"))
491+
.build());
492+
assertJsonEquals(
493+
ImmutableMap.of(
494+
"topic", "test-topic",
495+
"apns", ImmutableMap.<String, Object>of("payload", payload)),
496+
message);
497+
}
498+
499+
@Test
500+
public void testInvalidCriticalSound() {
501+
List<CriticalSound.Builder> soundBuilders = ImmutableList.of(
502+
CriticalSound.builder(),
503+
CriticalSound.builder().setCritical(true).setVolume(0.5),
504+
CriticalSound.builder().setVolume(-0.1),
505+
CriticalSound.builder().setVolume(1.1)
506+
);
507+
for (int i = 0; i < soundBuilders.size(); i++) {
508+
try {
509+
soundBuilders.get(i).build();
510+
fail("No error thrown for invalid sound: " + i);
511+
} catch (IllegalArgumentException expected) {
512+
// expected
513+
}
514+
}
515+
}
516+
451517
@Test
452518
public void testInvalidApnsConfig() {
453519
List<ApnsConfig.Builder> configBuilders = ImmutableList.of(
@@ -464,23 +530,22 @@ public void testInvalidApnsConfig() {
464530
}
465531
}
466532

467-
Aps.Builder builder = Aps.builder().setAlert("string").setAlert(ApsAlert.builder().build());
468-
try {
469-
builder.build();
470-
fail("No error thrown for invalid aps");
471-
} catch (IllegalArgumentException expected) {
472-
// expected
473-
}
474-
475-
builder = Aps.builder().setMutableContent(true).putCustomData("mutable-content", 1);
476-
try {
477-
builder.build();
478-
fail("No error thrown for invalid aps");
479-
} catch (IllegalArgumentException expected) {
480-
// expected
533+
List<Aps.Builder> apsBuilders = ImmutableList.of(
534+
Aps.builder().setAlert("string").setAlert(ApsAlert.builder().build()),
535+
Aps.builder().setSound("default").setSound(CriticalSound.builder()
536+
.setName("default")
537+
.build()),
538+
Aps.builder().setMutableContent(true).putCustomData("mutable-content", 1)
539+
);
540+
for (int i = 0; i < apsBuilders.size(); i++) {
541+
try {
542+
apsBuilders.get(i).build();
543+
fail("No error thrown for invalid aps: " + i);
544+
} catch (IllegalArgumentException expected) {
545+
// expected
546+
}
481547
}
482548

483-
484549
List<ApsAlert.Builder> notificationBuilders = ImmutableList.of(
485550
ApsAlert.builder().addLocalizationArg("foo"),
486551
ApsAlert.builder().addTitleLocalizationArg("foo"),

0 commit comments

Comments
 (0)