Skip to content

Commit

Permalink
Implemented CriticalSound API for FCM (#233)
Browse files Browse the repository at this point in the history
* Implemented CriticalSounf API for FCM

* Made the name field required

* Ignoring empty values in sound field

* Documentation updates
  • Loading branch information
hiranya911 authored Dec 18, 2018
1 parent 7f5c012 commit a38e47c
Show file tree
Hide file tree
Showing 4 changed files with 216 additions and 17 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Unreleased

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

Expand Down
21 changes: 19 additions & 2 deletions src/main/java/com/google/firebase/messaging/Aps.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ public class Aps {
private Aps(Builder builder) {
checkArgument(Strings.isNullOrEmpty(builder.alertString) || (builder.alert == null),
"Multiple alert specifications (string and ApsAlert) found.");
checkArgument(Strings.isNullOrEmpty(builder.sound) || (builder.criticalSound == null),
"Multiple sound specifications (sound and CriticalSound) found.");
ImmutableMap.Builder<String, Object> fields = ImmutableMap.builder();
if (builder.alert != null) {
fields.put("alert", builder.alert);
Expand All @@ -44,8 +46,10 @@ private Aps(Builder builder) {
if (builder.badge != null) {
fields.put("badge", builder.badge);
}
if (builder.sound != null) {
if (!Strings.isNullOrEmpty(builder.sound)) {
fields.put("sound", builder.sound);
} else if (builder.criticalSound != null) {
fields.put("sound", builder.criticalSound.getFields());
}
if (builder.contentAvailable) {
fields.put("content-available", 1);
Expand Down Expand Up @@ -82,6 +86,7 @@ public static class Builder {
private ApsAlert alert;
private Integer badge;
private String sound;
private CriticalSound criticalSound;
private boolean contentAvailable;
private boolean mutableContent;
private String category;
Expand Down Expand Up @@ -125,7 +130,8 @@ public Builder setBadge(int badge) {
}

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

/**
* Sets the critical alert sound to be played with the message.
*
* @param sound A {@link CriticalSound} instance containing the alert sound configuration.
* @return This builder.
*/
public Builder setSound(CriticalSound sound) {
this.criticalSound = sound;
return this;
}

/**
* Specifies whether to configure a background update notification.
*
Expand Down
115 changes: 115 additions & 0 deletions src/main/java/com/google/firebase/messaging/CriticalSound.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
/*
* Copyright 2018 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.google.firebase.messaging;

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

import com.google.common.base.Strings;
import com.google.common.collect.ImmutableMap;
import java.util.Map;

/**
* The sound configuration for APNs critical alerts.
*/
public final class CriticalSound {

private final Map<String, Object> fields;

private CriticalSound(Builder builder) {
checkArgument(!Strings.isNullOrEmpty(builder.name), "name must not be null or empty");
ImmutableMap.Builder<String, Object> fields = ImmutableMap.<String, Object>builder()
.put("name", builder.name);
if (builder.critical) {
fields.put("critical", 1);
}
if (builder.volume != null) {
checkArgument(builder.volume >= 0 && builder.volume <= 1,
"volume must be in the interval [0,1]");
fields.put("volume", builder.volume);
}
this.fields = fields.build();
}

Map<String, Object> getFields() {
return fields;
}

/**
* Creates a new {@link CriticalSound.Builder}.
*
* @return A {@link CriticalSound.Builder} instance.
*/
public static Builder builder() {
return new Builder();
}

public static final class Builder {

private boolean critical;
private String name;
private Double volume;

private Builder() {
}

/**
* Sets the critical alert flag on the sound configuration.
*
* @param critical True to set the critical alert flag.
* @return This builder.
*/
public Builder setCritical(boolean critical) {
this.critical = critical;
return this;
}

/**
* The name of a sound file in your app's main bundle or in the {@code Library/Sounds} folder
* of your app’s container directory. Specify the string {@code default} to play the system
* sound.
*
* @param name Sound file name.
* @return This builder.
*/
public Builder setName(String name) {
this.name = name;
return this;
}

/**
* The volume for the critical alert's sound. Must be a value between 0.0 (silent) and 1.0
* (full volume).
*
* @param volume A volume between 0.0 (inclusive) and 1.0 (inclusive).
* @return This builder.
*/
public Builder setVolume(double volume) {
this.volume = volume;
return this;
}

/**
* Builds a new {@link CriticalSound} instance from the fields set on this builder.
*
* @return A non-null {@link CriticalSound}.
* @throws IllegalArgumentException If the volume value is out of range.
*/
public CriticalSound build() {
return new CriticalSound(this);
}
}
}
95 changes: 80 additions & 15 deletions src/test/java/com/google/firebase/messaging/MessageTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,72 @@ public void testApnsMessageWithPayloadAndAps() throws IOException {
message);
}

@Test
public void testApnsMessageWithCriticalSound() throws IOException {
Message message = Message.builder()
.setApnsConfig(ApnsConfig.builder()
.setAps(Aps.builder()
.setSound(CriticalSound.builder() // All fields
.setCritical(true)
.setName("default")
.setVolume(0.5)
.build())
.build())
.build())
.setTopic("test-topic")
.build();
Map<String, Object> payload = ImmutableMap.<String, Object>of(
"aps", ImmutableMap.builder()
.put("sound", ImmutableMap.of(
"critical", new BigDecimal(1),
"name", "default",
"volume", new BigDecimal(0.5)))
.build());
assertJsonEquals(
ImmutableMap.of(
"topic", "test-topic",
"apns", ImmutableMap.<String, Object>of("payload", payload)),
message);

message = Message.builder()
.setApnsConfig(ApnsConfig.builder()
.setAps(Aps.builder()
.setSound(CriticalSound.builder() // Name field only
.setName("default")
.build())
.build())
.build())
.setTopic("test-topic")
.build();
payload = ImmutableMap.<String, Object>of(
"aps", ImmutableMap.builder()
.put("sound", ImmutableMap.of("name", "default"))
.build());
assertJsonEquals(
ImmutableMap.of(
"topic", "test-topic",
"apns", ImmutableMap.<String, Object>of("payload", payload)),
message);
}

@Test
public void testInvalidCriticalSound() {
List<CriticalSound.Builder> soundBuilders = ImmutableList.of(
CriticalSound.builder(),
CriticalSound.builder().setCritical(true).setVolume(0.5),
CriticalSound.builder().setVolume(-0.1),
CriticalSound.builder().setVolume(1.1)
);
for (int i = 0; i < soundBuilders.size(); i++) {
try {
soundBuilders.get(i).build();
fail("No error thrown for invalid sound: " + i);
} catch (IllegalArgumentException expected) {
// expected
}
}
}

@Test
public void testInvalidApnsConfig() {
List<ApnsConfig.Builder> configBuilders = ImmutableList.of(
Expand All @@ -464,23 +530,22 @@ public void testInvalidApnsConfig() {
}
}

Aps.Builder builder = Aps.builder().setAlert("string").setAlert(ApsAlert.builder().build());
try {
builder.build();
fail("No error thrown for invalid aps");
} catch (IllegalArgumentException expected) {
// expected
}

builder = Aps.builder().setMutableContent(true).putCustomData("mutable-content", 1);
try {
builder.build();
fail("No error thrown for invalid aps");
} catch (IllegalArgumentException expected) {
// expected
List<Aps.Builder> apsBuilders = ImmutableList.of(
Aps.builder().setAlert("string").setAlert(ApsAlert.builder().build()),
Aps.builder().setSound("default").setSound(CriticalSound.builder()
.setName("default")
.build()),
Aps.builder().setMutableContent(true).putCustomData("mutable-content", 1)
);
for (int i = 0; i < apsBuilders.size(); i++) {
try {
apsBuilders.get(i).build();
fail("No error thrown for invalid aps: " + i);
} catch (IllegalArgumentException expected) {
// expected
}
}


List<ApsAlert.Builder> notificationBuilders = ImmutableList.of(
ApsAlert.builder().addLocalizationArg("foo"),
ApsAlert.builder().addTitleLocalizationArg("foo"),
Expand Down

0 comments on commit a38e47c

Please sign in to comment.