show notification if message failed to deliver. closes #3540

Daniel Gultsch created

Change summary

.travis.yml                                                              |  2 
src/main/java/eu/siacs/conversations/entities/Conversation.java          | 12 
src/main/java/eu/siacs/conversations/services/NotificationService.java   | 45 
src/main/java/eu/siacs/conversations/services/XmppConnectionService.java |  5 
src/main/res/drawable-hdpi/ic_error_white_24dp.png                       |  0 
src/main/res/drawable-mdpi/ic_error_white_24dp.png                       |  0 
src/main/res/drawable-xhdpi/ic_error_white_24dp.png                      |  0 
src/main/res/drawable-xxhdpi/ic_error_white_24dp.png                     |  0 
src/main/res/drawable-xxxhdpi/ic_error_white_24dp.png                    |  0 
src/main/res/values/strings.xml                                          |  6 
10 files changed, 67 insertions(+), 3 deletions(-)

Detailed changes

.travis.yml 🔗

@@ -11,7 +11,7 @@ android:
     - '.+'
 before_script:
     - mkdir libs
-    - wget -O libs/libwebrtc-m85.aar http://gultsch.de/files/libwebrtc-m85.aar
+    - wget -O libs/libwebrtc-m85.aar https://gultsch.de/files/libwebrtc-m85.aar
 script:
     - ./gradlew assembleConversationsFreeSystemRelease
     - ./gradlew assembleQuicksyFreeCompatRelease

src/main/java/eu/siacs/conversations/entities/Conversation.java 🔗

@@ -186,6 +186,18 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
         return null;
     }
 
+    public int countFailedDeliveries() {
+        int count = 0;
+        synchronized (this.messages) {
+            for(final Message message : this.messages) {
+                if (message.getStatus() == Message.STATUS_SEND_FAILED) {
+                    ++count;
+                }
+            }
+        }
+        return count;
+    }
+
     public Message getLastEditableMessage() {
         synchronized (this.messages) {
             for (final Message message : Lists.reverse(this.messages)) {

src/main/java/eu/siacs/conversations/services/NotificationService.java 🔗

@@ -83,6 +83,7 @@ public class NotificationService {
     private static final int ERROR_NOTIFICATION_ID = NOTIFICATION_ID_MULTIPLIER * 6;
     private static final int INCOMING_CALL_NOTIFICATION_ID = NOTIFICATION_ID_MULTIPLIER * 8;
     public static final int ONGOING_CALL_NOTIFICATION_ID = NOTIFICATION_ID_MULTIPLIER * 10;
+    private static final int DELIVERY_FAILED_NOTIFICATION_ID = NOTIFICATION_ID_MULTIPLIER * 12;
     private final XmppConnectionService mXmppConnectionService;
     private final LinkedHashMap<String, ArrayList<Message>> notifications = new LinkedHashMap<>();
     private final HashMap<Conversation, AtomicInteger> mBacklogMessageCounter = new HashMap<>();
@@ -221,9 +222,20 @@ public class NotificationService {
         quietHoursChannel.setSound(null, null);
 
         notificationManager.createNotificationChannel(quietHoursChannel);
+
+        final NotificationChannel deliveryFailedChannel = new NotificationChannel("delivery_failed",
+                c.getString(R.string.delivery_failed_channel_name),
+                NotificationManager.IMPORTANCE_DEFAULT);
+        deliveryFailedChannel.setShowBadge(false);
+        deliveryFailedChannel.setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION), new AudioAttributes.Builder()
+                .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
+                .setUsage(AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_INSTANT)
+                .build());
+        deliveryFailedChannel.setGroup("chats");
+        notificationManager.createNotificationChannel(deliveryFailedChannel);
     }
 
-    public boolean notify(final Message message) {
+    private boolean notify(final Message message) {
         final Conversation conversation = (Conversation) message.getConversation();
         return message.getStatus() == Message.STATUS_RECEIVED
                 && !conversation.isMuted()
@@ -343,6 +355,37 @@ public class NotificationService {
         }
     }
 
+    public void pushFailedDelivery(final Message message) {
+        final Conversation conversation = (Conversation) message.getConversation();
+        final boolean isScreenOn = mXmppConnectionService.isInteractive();
+        if (this.mIsInForeground && isScreenOn && this.mOpenConversation == message.getConversation()) {
+            Log.d(Config.LOGTAG, message.getConversation().getAccount().getJid().asBareJid() + ": suppressing failed delivery notification because conversation is open");
+            return;
+        }
+        final PendingIntent pendingIntent = createContentIntent(conversation);
+        final int notificationId = generateRequestCode(conversation, 0) + DELIVERY_FAILED_NOTIFICATION_ID;
+        final int failedDeliveries = conversation.countFailedDeliveries();
+        final Notification notification =
+                new Builder(mXmppConnectionService, "delivery_failed")
+                        .setContentTitle(conversation.getName())
+                        .setAutoCancel(true)
+                        .setSmallIcon(R.drawable.ic_error_white_24dp)
+                        .setContentText(mXmppConnectionService.getResources().getQuantityText(R.plurals.some_messages_could_not_be_delivered, failedDeliveries))
+                        .setGroup("delivery_failed")
+                        .setContentIntent(pendingIntent).build();
+        final Notification summaryNotification =
+                new Builder(mXmppConnectionService, "delivery_failed")
+                        .setContentTitle(mXmppConnectionService.getString(R.string.failed_deliveries))
+                        .setContentText(mXmppConnectionService.getResources().getQuantityText(R.plurals.some_messages_could_not_be_delivered, 1024))
+                        .setSmallIcon(R.drawable.ic_error_white_24dp)
+                        .setGroup("delivery_failed")
+                        .setGroupSummary(true)
+                        .setAutoCancel(true)
+                        .build();
+        notify(notificationId, notification);
+        notify(DELIVERY_FAILED_NOTIFICATION_ID, summaryNotification);
+    }
+
     public void showIncomingCallNotification(final AbstractJingleConnection.Id id, final Set<Media> media) {
         final Intent fullScreenIntent = new Intent(mXmppConnectionService, RtpSessionActivity.class);
         fullScreenIntent.putExtra(RtpSessionActivity.EXTRA_ACCOUNT, id.account.getJid().asBareJid().toEscapedString());

src/main/java/eu/siacs/conversations/services/XmppConnectionService.java 🔗

@@ -3922,7 +3922,7 @@ public class XmppConnectionService extends Service {
     }
 
 
-    public void markMessage(Message message, int status, String errorMessage) {
+    public void markMessage(final Message message, final int status, final String errorMessage) {
         final int oldStatus = message.getStatus();
         if (status == Message.STATUS_SEND_FAILED && (oldStatus == Message.STATUS_SEND_RECEIVED || oldStatus == Message.STATUS_SEND_DISPLAYED)) {
             return;
@@ -3934,6 +3934,9 @@ public class XmppConnectionService extends Service {
         message.setStatus(status);
         databaseBackend.updateMessage(message, false);
         updateConversationUi();
+        if (oldStatus != status && status == Message.STATUS_SEND_FAILED) {
+            mNotificationService.pushFailedDelivery(message);
+        }
     }
 
     private SharedPreferences getPreferences() {

src/main/res/values/strings.xml 🔗

@@ -755,6 +755,7 @@
     <string name="ongoing_calls_channel_name">Ongoing calls</string>
     <string name="silent_messages_channel_name">Silent messages</string>
     <string name="silent_messages_channel_description">This notification group is used to display notifications that should not trigger any sound. For example when being active on another device (Grace Period).</string>
+    <string name="delivery_failed_channel_name">Failed deliveries</string>
     <string name="pref_message_notification_settings">Message notification settings</string>
     <string name="pref_incoming_call_notification_settings">Incoming calls notification settings</string>
     <string name="pref_more_notification_settings_summary">Importance, Sound, Vibrate</string>
@@ -942,4 +943,9 @@
         <item quantity="one">View %1$d Participant</item>
         <item quantity="other">View %1$d Participants</item>
     </plurals>
+    <plurals name="some_messages_could_not_be_delivered">
+        <item quantity="one">A message could not be delivered</item>
+        <item quantity="other">Some messages could not be delivered</item>
+    </plurals>
+    <string name="failed_deliveries">Failed deliveries</string>
 </resources>