From c367a712233caa647df1413ba197abacf6560a9f Mon Sep 17 00:00:00 2001 From: Jordan Rose Date: Wed, 9 Feb 2022 17:59:53 -0800 Subject: [PATCH] APNS: include a collapse-id for non-VOIP notifications This has two benefits: - The APNS server should only send an iOS client a single push notification for any missed messages while the device is offline (server-side coalescing). Note that the client can still turn that into multiple "user notifications" as it pulls from its queue. - If multiple notifications get delivered but iOS is unable to process them (say, because the phone just restarted and hasn't been unlocked yet), the user should only get one "You may have received messages" notification (client-side coalescing). --- .../org/whispersystems/textsecuregcm/push/APNSender.java | 3 ++- .../org/whispersystems/textsecuregcm/push/ApnMessage.java | 8 ++++++++ .../textsecuregcm/push/RetryingApnsClient.java | 4 ++-- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/push/APNSender.java b/service/src/main/java/org/whispersystems/textsecuregcm/push/APNSender.java index 9e1a4dd76..24761b77b 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/push/APNSender.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/push/APNSender.java @@ -82,7 +82,8 @@ public class APNSender implements Managed { ListenableFuture future = apnsClient.send(message.getApnId(), topic, message.getMessage(), Instant.ofEpochMilli(message.getExpirationTime()), - message.isVoip()); + message.isVoip(), + message.getCollapseId()); Futures.addCallback(future, new FutureCallback<>() { @Override diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/push/ApnMessage.java b/service/src/main/java/org/whispersystems/textsecuregcm/push/ApnMessage.java index 804200030..1c69b0921 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/push/ApnMessage.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/push/ApnMessage.java @@ -66,6 +66,14 @@ public class ApnMessage { } } + @Nullable + public String getCollapseId() { + if (type == Type.NOTIFICATION && !isVoip) { + return "incoming-message"; + } + return null; + } + @VisibleForTesting public Optional getChallengeData() { return challengeData; diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/push/RetryingApnsClient.java b/service/src/main/java/org/whispersystems/textsecuregcm/push/RetryingApnsClient.java index 168dd46a3..119d9c1d2 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/push/RetryingApnsClient.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/push/RetryingApnsClient.java @@ -67,9 +67,9 @@ public class RetryingApnsClient { this.apnsClient = apnsClient; } - ListenableFuture send(final String apnId, final String topic, final String payload, final Instant expiration, final boolean isVoip) { + ListenableFuture send(final String apnId, final String topic, final String payload, final Instant expiration, final boolean isVoip, final String collapseId) { SettableFuture result = SettableFuture.create(); - SimpleApnsPushNotification notification = new SimpleApnsPushNotification(apnId, topic, payload, expiration, DeliveryPriority.IMMEDIATE, isVoip ? PushType.VOIP : PushType.ALERT); + SimpleApnsPushNotification notification = new SimpleApnsPushNotification(apnId, topic, payload, expiration, DeliveryPriority.IMMEDIATE, isVoip ? PushType.VOIP : PushType.ALERT, collapseId); apnsClient.sendNotification(notification).whenComplete(new ResponseHandler(result));