diff --git a/src/main/java/eu/siacs/conversations/entities/ReceiptRequest.java b/src/main/java/eu/siacs/conversations/entities/ReceiptRequest.java new file mode 100644 index 000000000..01b53f09a --- /dev/null +++ b/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; + } +} diff --git a/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java b/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java index 5a1eded20..4539396b3 100644 --- a/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java +++ b/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); diff --git a/src/main/java/eu/siacs/conversations/parser/MessageParser.java b/src/main/java/eu/siacs/conversations/parser/MessageParser.java index 87f865074..b5e2d58da 100644 --- a/src/main/java/eu/siacs/conversations/parser/MessageParser.java +++ b/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 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 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())); + } } } diff --git a/src/main/java/eu/siacs/conversations/services/MessageArchiveService.java b/src/main/java/eu/siacs/conversations/services/MessageArchiveService.java index 444b47132..4f39b27a4 100644 --- a/src/main/java/eu/siacs/conversations/services/MessageArchiveService.java +++ b/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 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 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;