sent message receipts after mam catchup

Daniel Gultsch created

Change summary

src/main/java/eu/siacs/conversations/entities/ReceiptRequest.java        | 75 
src/main/java/eu/siacs/conversations/generator/MessageGenerator.java     | 10 
src/main/java/eu/siacs/conversations/parser/MessageParser.java           | 58 
src/main/java/eu/siacs/conversations/services/MessageArchiveService.java | 18 
4 files changed, 138 insertions(+), 23 deletions(-)

Detailed changes

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

@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2018, Daniel Gultsch All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation and/or
+ * other materials provided with the distribution.
+ *
+ * 3. Neither the name of the copyright holder nor the names of its contributors
+ * may be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package eu.siacs.conversations.entities;
+
+import eu.siacs.conversations.xmpp.jid.Jid;
+
+public class ReceiptRequest {
+
+	private final Jid jid;
+	private final String id;
+
+	public ReceiptRequest(Jid jid, String id) {
+		if (id == null) {
+			throw new IllegalArgumentException("id must not be null");
+		}
+		if (jid == null) {
+			throw new IllegalArgumentException("jid must not be null");
+		}
+		this.jid = jid.toBareJid();
+		this.id = id;
+	}
+
+	@Override
+	public boolean equals(Object o) {
+		if (this == o) return true;
+		if (o == null || getClass() != o.getClass()) return false;
+
+		ReceiptRequest that = (ReceiptRequest) o;
+
+		if (!jid.equals(that.jid)) return false;
+		return id.equals(that.id);
+	}
+
+	@Override
+	public int hashCode() {
+		int result = jid.hashCode();
+		result = 31 * result + id.hashCode();
+		return result;
+	}
+
+	public String getId() {
+		return id;
+	}
+
+	public Jid getJid() {
+		return jid;
+	}
+}

src/main/java/eu/siacs/conversations/generator/MessageGenerator.java 🔗

@@ -242,9 +242,19 @@ public class MessageGenerator extends AbstractGenerator {
 		for(String namespace : namespaces) {
 			receivedPacket.addChild("received", namespace).setAttribute("id", originalMessage.getId());
 		}
+		receivedPacket.addChild("store", "urn:xmpp:hints");
 		return receivedPacket;
 	}
 
+	public MessagePacket received(Account account, Jid to, String id) {
+		MessagePacket packet = new MessagePacket();
+		packet.setFrom(account.getJid());
+		packet.setTo(to);
+		packet.addChild("received","urn:xmpp:receipts").setAttribute("id",id);
+		packet.addChild("store", "urn:xmpp:hints");
+		return packet;
+	}
+
 	public MessagePacket generateOtrError(Jid to, String id, String errorText) {
 		MessagePacket packet = new MessagePacket();
 		packet.setType(MessagePacket.TYPE_ERROR);

src/main/java/eu/siacs/conversations/parser/MessageParser.java 🔗

@@ -30,6 +30,7 @@ import eu.siacs.conversations.entities.Message;
 import eu.siacs.conversations.entities.MucOptions;
 import eu.siacs.conversations.entities.Presence;
 import eu.siacs.conversations.entities.ReadByMarker;
+import eu.siacs.conversations.entities.ReceiptRequest;
 import eu.siacs.conversations.entities.ServiceDiscoveryResult;
 import eu.siacs.conversations.http.HttpConnectionManager;
 import eu.siacs.conversations.services.MessageArchiveService;
@@ -564,11 +565,12 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
 							mXmppConnectionService.updateMessage(replacedMessage, uuid);
 							mXmppConnectionService.getNotificationService().updateNotification(false);
 							if (mXmppConnectionService.confirmMessages()
+									&& replacedMessage.getStatus() == Message.STATUS_RECEIVED
 									&& (replacedMessage.trusted() || replacedMessage.getType() == Message.TYPE_PRIVATE)
 									&& remoteMsgId != null
-									&& !isForwarded
+									&& (!isForwarded || query != null)
 									&& !isTypeGroupChat) {
-								sendMessageReceipts(account, packet);
+								processMessageReceipts(account, packet, query);
 							}
 							if (replacedMessage.getEncryption() == Message.ENCRYPTION_PGP) {
 								conversation.getAccount().getPgpDecryptionService().discard(replacedMessage);
@@ -641,11 +643,12 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
 			}
 
 			if (mXmppConnectionService.confirmMessages()
+					&& message.getStatus() == Message.STATUS_RECEIVED
 					&& (message.trusted() || message.getType() == Message.TYPE_PRIVATE)
 					&& remoteMsgId != null
-					&& !isForwarded
+					&& (!isForwarded || query != null)
 					&& !isTypeGroupChat) {
-				sendMessageReceipts(account, packet);
+				processMessageReceipts(account, packet, query);
 			}
 
 			if (message.getStatus() == Message.STATUS_RECEIVED
@@ -735,8 +738,15 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
 		if (received == null) {
 			received = packet.findChild("received", "urn:xmpp:receipts");
 		}
-		if (received != null && !packet.fromAccount(account)) {
-			mXmppConnectionService.markMessage(account, from.toBareJid(), received.getAttribute("id"), Message.STATUS_SEND_RECEIVED);
+		if (received != null) {
+			String id = received.getAttribute("id");
+			if (packet.fromAccount(account)) {
+				if (query != null && id != null && packet.getTo() != null) {
+					query.pendingReceiptRequests.remove(new ReceiptRequest(packet.getTo(),id));
+				}
+			} else {
+				mXmppConnectionService.markMessage(account, from.toBareJid(), received.getAttribute("id"), Message.STATUS_SEND_RECEIVED);
+			}
 		}
 		Element displayed = packet.findChild("displayed", "urn:xmpp:chat-markers:0");
 		if (displayed != null) {
@@ -800,20 +810,28 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
 		return result != null ? result : fallback;
 	}
 
-	private void sendMessageReceipts(Account account, MessagePacket packet) {
-		ArrayList<String> receiptsNamespaces = new ArrayList<>();
-		if (packet.hasChild("markable", "urn:xmpp:chat-markers:0")) {
-			receiptsNamespaces.add("urn:xmpp:chat-markers:0");
-		}
-		if (packet.hasChild("request", "urn:xmpp:receipts")) {
-			receiptsNamespaces.add("urn:xmpp:receipts");
-		}
-		if (receiptsNamespaces.size() > 0) {
-			MessagePacket receipt = mXmppConnectionService.getMessageGenerator().received(account,
-					packet,
-					receiptsNamespaces,
-					packet.getType());
-			mXmppConnectionService.sendMessagePacket(account, receipt);
+	private void processMessageReceipts(Account account, MessagePacket packet, MessageArchiveService.Query query) {
+		final boolean markable = packet.hasChild("markable", "urn:xmpp:chat-markers:0");
+		final boolean request = packet.hasChild("request", "urn:xmpp:receipts");
+		if (query == null) {
+			final ArrayList<String> receiptsNamespaces = new ArrayList<>();
+			if (markable) {
+				receiptsNamespaces.add("urn:xmpp:chat-markers:0");
+			}
+			if (request) {
+				receiptsNamespaces.add("urn:xmpp:receipts");
+			}
+			if (receiptsNamespaces.size() > 0) {
+				MessagePacket receipt = mXmppConnectionService.getMessageGenerator().received(account,
+						packet,
+						receiptsNamespaces,
+						packet.getType());
+				mXmppConnectionService.sendMessagePacket(account, receipt);
+			}
+		} else {
+			if (request) {
+				query.pendingReceiptRequests.add(new ReceiptRequest(packet.getFrom(),packet.getId()));
+			}
 		}
 	}
 

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

@@ -1,7 +1,6 @@
 package eu.siacs.conversations.services;
 
 import android.util.Log;
-import android.util.Pair;
 
 import java.math.BigInteger;
 import java.util.ArrayList;
@@ -11,9 +10,9 @@ import java.util.List;
 
 import eu.siacs.conversations.Config;
 import eu.siacs.conversations.R;
-import eu.siacs.conversations.crypto.axolotl.AxolotlService;
 import eu.siacs.conversations.entities.Account;
 import eu.siacs.conversations.entities.Conversation;
+import eu.siacs.conversations.entities.ReceiptRequest;
 import eu.siacs.conversations.generator.AbstractGenerator;
 import eu.siacs.conversations.xml.Namespace;
 import eu.siacs.conversations.xml.Element;
@@ -280,7 +279,7 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded {
 			if (query.isCatchup() && query.getActualMessageCount() > 0) {
 				mXmppConnectionService.getNotificationService().finishBacklog(true,query.getAccount());
 			}
-			query.account.getAxolotlService().processPostponed();
+			processPostponed(query);
 		} else {
 			final Query nextQuery;
 			if (query.getPagingOrder() == PagingOrder.NORMAL) {
@@ -296,6 +295,17 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded {
 		}
 	}
 
+	private void processPostponed(Query query) {
+		query.account.getAxolotlService().processPostponed();
+		Log.d(Config.LOGTAG,query.getAccount().getJid().toBareJid()+": found "+query.pendingReceiptRequests.size()+" pending receipt requests");
+		Iterator<ReceiptRequest> iterator = query.pendingReceiptRequests.iterator();
+		while (iterator.hasNext()) {
+			ReceiptRequest rr = iterator.next();
+			mXmppConnectionService.sendMessagePacket(query.account,mXmppConnectionService.getMessageGenerator().received(query.account,        rr.getJid(),rr.getId()));
+			iterator.remove();
+		}
+	}
+
 	public Query findQuery(String id) {
 		if (id == null) {
 			return null;
@@ -329,6 +339,7 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded {
 		private PagingOrder pagingOrder = PagingOrder.NORMAL;
 		private XmppConnectionService.OnMoreMessagesLoaded callback = null;
 		private boolean catchup = true;
+		public HashSet<ReceiptRequest> pendingReceiptRequests = new HashSet<>();
 
 
 		public Query(Conversation conversation, MamReference start, long end, boolean catchup) {
@@ -354,6 +365,7 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded {
 			query.conversation = conversation;
 			query.totalCount = totalCount;
 			query.actualCount = actualCount;
+			query.pendingReceiptRequests = pendingReceiptRequests;
 			query.callback = callback;
 			query.catchup = catchup;
 			return query;