Repository: firebase/firebase-admin-java
Pull request #221 (2018-11-27 20:14:03)

Make adjustments to the public API as discussed offline
As discussed offline, we will remove the `name` field from the `{Android|Ios}AppMetadata` classes from the public API, as well as making `displayName` a `null` field when it is empty to stay consistent with other services.


Patch:
diff --git a/src/main/java/com/google/firebase/messaging/ApsAlert.java b/src/main/java/com/google/firebase/messaging/ApsAlert.java
index 6f7e249..2efa944 100644
--- a/src/main/java/com/google/firebase/messaging/ApsAlert.java
+++ b/src/main/java/com/google/firebase/messaging/ApsAlert.java
@@ -34,6 +34,9 @@ public class ApsAlert {
   @Key("title")
   private final String title;
 
+  @Key("subtitle")
+  private final String subtitle;
+
   @Key("body")
   private final String body;
 
@@ -49,6 +52,12 @@ public class ApsAlert {
   @Key("title-loc-args")
   private final List<String> titleLocArgs;
 
+  @Key("subtitle-loc-key")
+  private final String subtitleLocKey;
+
+  @Key("subtitle-loc-args")
+  private final List<String> subtitleLocArgs;
+
   @Key("action-loc-key")
   private final String actionLocKey;
 
@@ -57,6 +66,7 @@ public class ApsAlert {
 
   private ApsAlert(Builder builder) {
     this.title = builder.title;
+    this.subtitle = builder.subtitle;
     this.body = builder.body;
     this.actionLocKey = builder.actionLocKey;
     this.locKey = builder.locKey;
@@ -76,6 +86,14 @@ public class ApsAlert {
     } else {
       this.titleLocArgs = null;
     }
+    this.subtitleLocKey = builder.subtitleLocKey;
+    if (!builder.subtitleLocArgs.isEmpty()) {
+      checkArgument(!Strings.isNullOrEmpty(builder.subtitleLocKey),
+          "subtitleLocKey is required when specifying subtitleLocArgs");
+      this.subtitleLocArgs = ImmutableList.copyOf(builder.subtitleLocArgs);
+    } else {
+      this.subtitleLocArgs = null;
+    }
     this.launchImage = builder.launchImage;
   }
 
@@ -91,11 +109,14 @@ public class ApsAlert {
   public static class Builder {
 
     private String title;
+    private String subtitle;
     private String body;
     private String locKey;
     private List<String> locArgs = new ArrayList<>();
     private String titleLocKey;
     private List<String> titleLocArgs = new ArrayList<>();
+    private String subtitleLocKey;
+    private List<String> subtitleLocArgs = new ArrayList<>();
     private String actionLocKey;
     private String launchImage;
 
@@ -113,6 +134,17 @@ public class ApsAlert {
       return this;
     }
 
+    /**
+     * Sets the subtitle of the alert.
+     *
+     * @param subtitle Subtitle of the notification.
+     * @return This builder.
+     */
+    public Builder setSubtitle(String subtitle) {
+      this.subtitle = subtitle;
+      return this;
+    }
+
     /**
      * Sets the body of the alert. When provided, overrides the body sent
      * via {@link Notification}.
@@ -209,6 +241,42 @@ public class ApsAlert {
       return this;
     }
 
+    /**
+     * Sets the key of the subtitle string in the app's string resources to use to localize 
+     * the subtitle text.
+     *
+     * @param subtitleLocKey Resource key string.
+     * @return This builder.
+     */
+    public Builder setSubtitleLocalizationKey(String subtitleLocKey) {
+      this.subtitleLocKey = subtitleLocKey;
+      return this;
+    }
+
+    /**
+     * Adds a resource key string that will be used in place of the format specifiers in
+     * {@code subtitleLocKey}.
+     *
+     * @param arg Resource key string.
+     * @return This builder.
+     */
+    public Builder addSubtitleLocalizationArg(@NonNull String arg) {
+      this.subtitleLocArgs.add(arg);
+      return this;
+    }
+
+    /**
+     * Adds a list of resource keys that will be used in place of the format specifiers in
+     * {@code subtitleLocKey}.
+     *
+     * @param args List of resource key strings.
+     * @return This builder.
+     */
+    public Builder addAllSubtitleLocArgs(@NonNull List<String> args) {
+      this.subtitleLocArgs.addAll(args);
+      return this;
+    }
+
     /**
      * Sets the launch image for the notification action.
      *
diff --git a/src/main/java/com/google/firebase/projectmanagement/AndroidAppMetadata.java b/src/main/java/com/google/firebase/projectmanagement/AndroidAppMetadata.java
index f60a0b0..055994d 100644
--- a/src/main/java/com/google/firebase/projectmanagement/AndroidAppMetadata.java
+++ b/src/main/java/com/google/firebase/projectmanagement/AndroidAppMetadata.java
@@ -18,6 +18,7 @@ package com.google.firebase.projectmanagement;
 import com.google.common.base.MoreObjects;
 import com.google.common.base.Objects;
 import com.google.common.base.Preconditions;
+import com.google.firebase.internal.Nullable;
 
 /**
  * Contains detailed information about an Android App. Instances of this class are immutable.
@@ -34,7 +35,7 @@ public class AndroidAppMetadata {
       String name, String appId, String displayName, String projectId, String packageName) {
     this.name = Preconditions.checkNotNull(name, "Null name");
     this.appId = Preconditions.checkNotNull(appId, "Null appId");
-    this.displayName = Preconditions.checkNotNull(displayName, "Null displayName");
+    this.displayName = displayName;
     this.projectId = Preconditions.checkNotNull(projectId, "Null projectId");
     this.packageName = Preconditions.checkNotNull(packageName, "Null packageName");
   }
@@ -42,7 +43,7 @@ public class AndroidAppMetadata {
   /**
    * Returns the fully qualified resource name of this Android App.
    */
-  public String getName() {
+  String getName() {
     return name;
   }
 
@@ -55,8 +56,10 @@ public class AndroidAppMetadata {
   }
 
   /**
-   * Returns the user-assigned display name of this Android App.
+   * Returns the user-assigned display name of this Android App. Returns {@code null} if it has
+   * never been set.
    */
+  @Nullable
   public String getDisplayName() {
     return displayName;
   }
diff --git a/src/main/java/com/google/firebase/projectmanagement/FirebaseProjectManagementServiceImpl.java b/src/main/java/com/google/firebase/projectmanagement/FirebaseProjectManagementServiceImpl.java
index 474b8b0..6ce7f35 100644
--- a/src/main/java/com/google/firebase/projectmanagement/FirebaseProjectManagementServiceImpl.java
+++ b/src/main/java/com/google/firebase/projectmanagement/FirebaseProjectManagementServiceImpl.java
@@ -103,7 +103,7 @@ class FirebaseProjectManagementServiceImpl implements AndroidAppService, IosAppS
         return new AndroidAppMetadata(
             parsedResponse.name,
             parsedResponse.appId,
-            Strings.nullToEmpty(parsedResponse.displayName),
+            Strings.emptyToNull(parsedResponse.displayName),
             parsedResponse.projectId,
             parsedResponse.packageName);
       }
@@ -134,7 +134,7 @@ class FirebaseProjectManagementServiceImpl implements AndroidAppService, IosAppS
         return new IosAppMetadata(
             parsedResponse.name,
             parsedResponse.appId,
-            Strings.nullToEmpty(parsedResponse.displayName),
+            Strings.emptyToNull(parsedResponse.displayName),
             parsedResponse.projectId,
             parsedResponse.bundleId);
       }
diff --git a/src/main/java/com/google/firebase/projectmanagement/IosAppMetadata.java b/src/main/java/com/google/firebase/projectmanagement/IosAppMetadata.java
index bbd8167..5de66f7 100644
--- a/src/main/java/com/google/firebase/projectmanagement/IosAppMetadata.java
+++ b/src/main/java/com/google/firebase/projectmanagement/IosAppMetadata.java
@@ -18,6 +18,7 @@ package com.google.firebase.projectmanagement;
 import com.google.api.client.util.Preconditions;
 import com.google.common.base.MoreObjects;
 import com.google.common.base.Objects;
+import com.google.firebase.internal.Nullable;
 
 /**
  * Contains detailed information about an iOS App. Instances of this class are immutable.
@@ -33,7 +34,7 @@ public class IosAppMetadata {
       String name, String appId, String displayName, String projectId, String bundleId) {
     this.name = Preconditions.checkNotNull(name, "Null name");
     this.appId = Preconditions.checkNotNull(appId, "Null appId");
-    this.displayName = Preconditions.checkNotNull(displayName, "Null displayName");
+    this.displayName = displayName;
     this.projectId = Preconditions.checkNotNull(projectId, "Null projectId");
     this.bundleId = Preconditions.checkNotNull(bundleId, "Null bundleId");
   }
@@ -41,7 +42,7 @@ public class IosAppMetadata {
   /**
    * Returns the fully qualified resource name of this iOS App.
    */
-  public String getName() {
+  String getName() {
     return name;
   }
 
@@ -54,8 +55,10 @@ public class IosAppMetadata {
   }
 
   /**
-   * Returns the user-assigned display name of this iOS App.
+   * Returns the user-assigned display name of this iOS App. Returns {@code null} if it has never
+   * been set.
    */
+  @Nullable
   public String getDisplayName() {
     return displayName;
   }


Test patch:
diff --git a/src/test/java/com/google/firebase/messaging/FirebaseMessagingTest.java b/src/test/java/com/google/firebase/messaging/FirebaseMessagingTest.java
index 4b51513..1ad74ac 100644
--- a/src/test/java/com/google/firebase/messaging/FirebaseMessagingTest.java
+++ b/src/test/java/com/google/firebase/messaging/FirebaseMessagingTest.java
@@ -644,6 +644,7 @@ public class FirebaseMessagingTest {
                     .setBadge(42)
                     .setAlert(ApsAlert.builder()
                         .setTitle("test-title")
+                        .setSubtitle("test-subtitle")
                         .setBody("test-body")
                         .build())
                     .build())
@@ -657,7 +658,8 @@ public class FirebaseMessagingTest {
                 "payload", ImmutableMap.of("k1", "v1", "k2", true,
                     "aps", ImmutableMap.<String, Object>of("badge", new BigDecimal(42),
                         "alert", ImmutableMap.<String, Object>of(
-                            "title", "test-title", "body", "test-body"))))
+                            "title", "test-title", "subtitle", "test-subtitle", 
+                            "body", "test-body"))))
         ));
 
     // Webpush message (no notification)
diff --git a/src/test/java/com/google/firebase/messaging/MessageTest.java b/src/test/java/com/google/firebase/messaging/MessageTest.java
index 3313f2b..b6025c0 100644
--- a/src/test/java/com/google/firebase/messaging/MessageTest.java
+++ b/src/test/java/com/google/firebase/messaging/MessageTest.java
@@ -393,14 +393,18 @@ public class MessageTest {
             .setAps(Aps.builder()
                 .setAlert(ApsAlert.builder()
                     .setTitle("test-title")
+                    .setSubtitle("test-subtitle")
                     .setBody("test-body")
                     .setLocalizationKey("test-loc-key")
                     .setActionLocalizationKey("test-action-loc-key")
                     .setTitleLocalizationKey("test-title-loc-key")
+                    .setSubtitleLocalizationKey("test-subtitle-loc-key")
                     .addLocalizationArg("arg1")
                     .addAllLocalizationArgs(ImmutableList.of("arg2", "arg3"))
                     .addTitleLocalizationArg("arg4")
                     .addAllTitleLocArgs(ImmutableList.of("arg5", "arg6"))
+                    .addSubtitleLocalizationArg("arg7")
+                    .addAllSubtitleLocArgs(ImmutableList.of("arg8", "arg9"))
                     .setLaunchImage("test-image")
                     .build())
                 .setCategory("test-category")
@@ -417,12 +421,15 @@ public class MessageTest {
         "aps", ImmutableMap.<String, Object>builder()
             .put("alert", ImmutableMap.<String, Object>builder()
                 .put("title", "test-title")
+                .put("subtitle", "test-subtitle")
                 .put("body", "test-body")
                 .put("loc-key", "test-loc-key")
                 .put("action-loc-key", "test-action-loc-key")
                 .put("title-loc-key", "test-title-loc-key")
+                .put("subtitle-loc-key", "test-subtitle-loc-key")
                 .put("loc-args", ImmutableList.of("arg1", "arg2", "arg3"))
                 .put("title-loc-args", ImmutableList.of("arg4", "arg5", "arg6"))
+                .put("subtitle-loc-args", ImmutableList.of("arg7", "arg8", "arg9"))
                 .put("launch-image", "test-image")
                 .build())
             .put("category", "test-category")
@@ -474,7 +481,8 @@ public class MessageTest {
 
     List<ApsAlert.Builder> notificationBuilders = ImmutableList.of(
         ApsAlert.builder().addLocalizationArg("foo"),
-        ApsAlert.builder().addTitleLocalizationArg("foo")
+        ApsAlert.builder().addTitleLocalizationArg("foo"),
+        ApsAlert.builder().addSubtitleLocalizationArg("foo")
     );
     for (int i = 0; i < notificationBuilders.size(); i++) {
       try {
diff --git a/src/test/java/com/google/firebase/projectmanagement/FirebaseProjectManagementServiceImplTest.java b/src/test/java/com/google/firebase/projectmanagement/FirebaseProjectManagementServiceImplTest.java
index 5ba86b2..45b51a2 100644
--- a/src/test/java/com/google/firebase/projectmanagement/FirebaseProjectManagementServiceImplTest.java
+++ b/src/test/java/com/google/firebase/projectmanagement/FirebaseProjectManagementServiceImplTest.java
@@ -70,6 +70,11 @@ public class FirebaseProjectManagementServiceImplTest {
   private static final AndroidAppMetadata ANDROID_APP_METADATA =
       new AndroidAppMetadata(
           ANDROID_APP_RESOURCE_NAME, ANDROID_APP_ID, DISPLAY_NAME, PROJECT_ID, PACKAGE_NAME);
+  private static final IosAppMetadata IOS_APP_NO_DISPLAY_NAME_METADATA =
+      new IosAppMetadata(IOS_APP_RESOURCE_NAME, IOS_APP_ID, null, PROJECT_ID, BUNDLE_ID);
+  private static final AndroidAppMetadata ANDROID_APP_NO_DISPLAY_NAME_METADATA =
+      new AndroidAppMetadata(
+          ANDROID_APP_RESOURCE_NAME, ANDROID_APP_ID, null, PROJECT_ID, PACKAGE_NAME);
 
   private static final String IOS_CONFIG_CONTENT = "ios-config-content";
   private static final String ANDROID_CONFIG_CONTENT = "android-config-content";
@@ -100,6 +105,11 @@ public class FirebaseProjectManagementServiceImplTest {
           + "\"displayName\" : \"%s\", "
           + "\"projectId\" : \"test-project-id\", "
           + "\"bundleId\" : \"test.ios.app\"}";
+  private static final String GET_IOS_NO_DISPLAY_NAME_RESPONSE =
+      "{\"name\" : \"ios/11111\", "
+          + "\"appId\" : \"test-ios-app-id\", "
+          + "\"projectId\" : \"test-project-id\", "
+          + "\"bundleId\" : \"test.ios.app\"}";
   private static final String GET_IOS_CONFIG_RESPONSE =
       "{\"configFilename\" : \"test-ios-app-config-name\", "
           + "\"configFileContents\" : \"ios-config-content\"}";
@@ -154,6 +164,11 @@ public class FirebaseProjectManagementServiceImplTest {
           + "\"displayName\" : \"%s\", "
           + "\"projectId\" : \"test-project-id\", "
           + "\"packageName\" : \"test.android.app\"}";
+  private static final String GET_ANDROID_NO_DISPLAY_NAME_RESPONSE =
+      "{\"name\" : \"android/11111\", "
+          + "\"appId\" : \"test-android-app-id\", "
+          + "\"projectId\" : \"test-project-id\", "
+          + "\"packageName\" : \"test.android.app\"}";
   private static final String GET_ANDROID_CONFIG_RESPONSE =
       "{\"configFilename\" : \"test-android-app-config-name\", "
           + "\"configFileContents\" : \"android-config-content\"}";
@@ -243,6 +258,32 @@ public class FirebaseProjectManagementServiceImplTest {
     assertEquals(iosAppMetadata, IOS_APP_METADATA);
   }
 
+  @Test
+  public void getIosAppNoDisplayName() throws Exception {
+    String expectedUrl = String.format(
+        "%s/v1beta1/projects/-/iosApps/%s", FIREBASE_PROJECT_MANAGEMENT_URL, IOS_APP_ID);
+    firstRpcResponse.setContent(GET_IOS_NO_DISPLAY_NAME_RESPONSE);
+    serviceImpl = initServiceImpl(firstRpcResponse, interceptor);
+
+    IosAppMetadata iosAppMetadata = serviceImpl.getIosApp(IOS_APP_ID);
+
+    checkRequestHeader(expectedUrl, HttpMethod.GET);
+    assertEquals(iosAppMetadata, IOS_APP_NO_DISPLAY_NAME_METADATA);
+  }
+
+  @Test
+  public void getIosAppAsyncNoDisplayName() throws Exception {
+    String expectedUrl = String.format(
+        "%s/v1beta1/projects/-/iosApps/%s", FIREBASE_PROJECT_MANAGEMENT_URL, IOS_APP_ID);
+    firstRpcResponse.setContent(GET_IOS_NO_DISPLAY_NAME_RESPONSE);
+    serviceImpl = initServiceImpl(firstRpcResponse, interceptor);
+
+    IosAppMetadata iosAppMetadata = serviceImpl.getIosAppAsync(IOS_APP_ID).get();
+
+    checkRequestHeader(expectedUrl, HttpMethod.GET);
+    assertEquals(iosAppMetadata, IOS_APP_NO_DISPLAY_NAME_METADATA);
+  }
+
   @Test
   public void listIosApps() throws Exception {
     String expectedUrl = String.format(
@@ -485,6 +526,32 @@ public class FirebaseProjectManagementServiceImplTest {
     assertEquals(androidAppMetadata, ANDROID_APP_METADATA);
   }
 
+  @Test
+  public void getAndroidAppNoDisplayName() throws Exception {
+    firstRpcResponse.setContent(GET_ANDROID_NO_DISPLAY_NAME_RESPONSE);
+    serviceImpl = initServiceImpl(firstRpcResponse, interceptor);
+
+    AndroidAppMetadata androidAppMetadata = serviceImpl.getAndroidApp(ANDROID_APP_ID);
+
+    String expectedUrl = String.format(
+        "%s/v1beta1/projects/-/androidApps/%s", FIREBASE_PROJECT_MANAGEMENT_URL, ANDROID_APP_ID);
+    checkRequestHeader(expectedUrl, HttpMethod.GET);
+    assertEquals(androidAppMetadata, ANDROID_APP_NO_DISPLAY_NAME_METADATA);
+  }
+
+  @Test
+  public void getAndroidAppAsyncNoDisplayName() throws Exception {
+    firstRpcResponse.setContent(GET_ANDROID_NO_DISPLAY_NAME_RESPONSE);
+    serviceImpl = initServiceImpl(firstRpcResponse, interceptor);
+
+    AndroidAppMetadata androidAppMetadata = serviceImpl.getAndroidAppAsync(ANDROID_APP_ID).get();
+
+    String expectedUrl = String.format(
+        "%s/v1beta1/projects/-/androidApps/%s", FIREBASE_PROJECT_MANAGEMENT_URL, ANDROID_APP_ID);
+    checkRequestHeader(expectedUrl, HttpMethod.GET);
+    assertEquals(androidAppMetadata, ANDROID_APP_NO_DISPLAY_NAME_METADATA);
+  }
+
   @Test
   public void listAndroidApps() throws Exception {
     firstRpcResponse.setContent(LIST_ANDROID_APPS_RESPONSE);
diff --git a/src/test/java/com/google/firebase/projectmanagement/FirebaseProjectManagementTest.java b/src/test/java/com/google/firebase/projectmanagement/FirebaseProjectManagementTest.java
index 3d51eec..7569b0b 100644
--- a/src/test/java/com/google/firebase/projectmanagement/FirebaseProjectManagementTest.java
+++ b/src/test/java/com/google/firebase/projectmanagement/FirebaseProjectManagementTest.java
@@ -32,7 +32,6 @@ import com.google.firebase.TestOnlyImplFirebaseTrampolines;
 import com.google.firebase.auth.MockGoogleCredentials;
 import java.util.List;
 import java.util.concurrent.ExecutionException;
-import org.junit.After;
 import org.junit.AfterClass;
 import org.junit.Before;
 import org.junit.BeforeClass;