put jingle messages in MAM and parse call log during catchup

Daniel Gultsch created

Change summary

src/main/java/eu/siacs/conversations/entities/Conversation.java           |  14 
src/main/java/eu/siacs/conversations/generator/MessageGenerator.java      | 447 
src/main/java/eu/siacs/conversations/parser/MessageParser.java            |  63 
src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java |   4 
4 files changed, 294 insertions(+), 234 deletions(-)

Detailed changes

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

@@ -733,6 +733,18 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
 		}
 	}
 
+	public Message findRtpSession(final String sessionId, final int s) {
+		synchronized (this.messages) {
+			for (int i = this.messages.size() - 1; i >= 0; --i) {
+				final Message message = this.messages.get(i);
+				if ((message.getStatus() == s) && (message.getType() == Message.TYPE_RTP_SESSION) && sessionId.equals(message.getRemoteMsgId())) {
+					return message;
+				}
+			}
+		}
+		return null;
+	}
+
 	public boolean possibleDuplicate(final String serverMsgId, final String remoteMsgId) {
 		if (serverMsgId == null || remoteMsgId == null) {
 			return false;
@@ -1007,7 +1019,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
 		return UIHelper.getColorForName(getName().toString());
 	}
 
-	public interface OnMessageFound {
+    public interface OnMessageFound {
 		void onMessageFound(final Message message);
 	}
 

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

@@ -24,245 +24,248 @@ import eu.siacs.conversations.xmpp.stanzas.MessagePacket;
 import rocks.xmpp.addr.Jid;
 
 public class MessageGenerator extends AbstractGenerator {
-	private static final String OMEMO_FALLBACK_MESSAGE = "I sent you an OMEMO encrypted message but your client doesn’t seem to support that. Find more information on https://conversations.im/omemo";
-	private static final String PGP_FALLBACK_MESSAGE = "I sent you a PGP encrypted message but your client doesn’t seem to support that.";
+    private static final String OMEMO_FALLBACK_MESSAGE = "I sent you an OMEMO encrypted message but your client doesn’t seem to support that. Find more information on https://conversations.im/omemo";
+    private static final String PGP_FALLBACK_MESSAGE = "I sent you a PGP encrypted message but your client doesn’t seem to support that.";
 
-	public MessageGenerator(XmppConnectionService service) {
-		super(service);
-	}
+    public MessageGenerator(XmppConnectionService service) {
+        super(service);
+    }
 
-	private MessagePacket preparePacket(Message message) {
-		Conversation conversation = (Conversation) message.getConversation();
-		Account account = conversation.getAccount();
-		MessagePacket packet = new MessagePacket();
-		final boolean isWithSelf = conversation.getContact().isSelf();
-		if (conversation.getMode() == Conversation.MODE_SINGLE) {
-			packet.setTo(message.getCounterpart());
-			packet.setType(MessagePacket.TYPE_CHAT);
-			if (!isWithSelf) {
-				packet.addChild("request", "urn:xmpp:receipts");
-			}
-		} else if (message.isPrivateMessage()) {
-			packet.setTo(message.getCounterpart());
-			packet.setType(MessagePacket.TYPE_CHAT);
-			packet.addChild("x", "http://jabber.org/protocol/muc#user");
-			packet.addChild("request", "urn:xmpp:receipts");
-		} else {
-			packet.setTo(message.getCounterpart().asBareJid());
-			packet.setType(MessagePacket.TYPE_GROUPCHAT);
-		}
-		if (conversation.isSingleOrPrivateAndNonAnonymous() && !message.isPrivateMessage()) {
-			packet.addChild("markable", "urn:xmpp:chat-markers:0");
-		}
-		packet.setFrom(account.getJid());
-		packet.setId(message.getUuid());
-		packet.addChild("origin-id", Namespace.STANZA_IDS).setAttribute("id", message.getUuid());
-		if (message.edited()) {
-			packet.addChild("replace", "urn:xmpp:message-correct:0").setAttribute("id", message.getEditedIdWireFormat());
-		}
-		return packet;
-	}
+    private MessagePacket preparePacket(Message message) {
+        Conversation conversation = (Conversation) message.getConversation();
+        Account account = conversation.getAccount();
+        MessagePacket packet = new MessagePacket();
+        final boolean isWithSelf = conversation.getContact().isSelf();
+        if (conversation.getMode() == Conversation.MODE_SINGLE) {
+            packet.setTo(message.getCounterpart());
+            packet.setType(MessagePacket.TYPE_CHAT);
+            if (!isWithSelf) {
+                packet.addChild("request", "urn:xmpp:receipts");
+            }
+        } else if (message.isPrivateMessage()) {
+            packet.setTo(message.getCounterpart());
+            packet.setType(MessagePacket.TYPE_CHAT);
+            packet.addChild("x", "http://jabber.org/protocol/muc#user");
+            packet.addChild("request", "urn:xmpp:receipts");
+        } else {
+            packet.setTo(message.getCounterpart().asBareJid());
+            packet.setType(MessagePacket.TYPE_GROUPCHAT);
+        }
+        if (conversation.isSingleOrPrivateAndNonAnonymous() && !message.isPrivateMessage()) {
+            packet.addChild("markable", "urn:xmpp:chat-markers:0");
+        }
+        packet.setFrom(account.getJid());
+        packet.setId(message.getUuid());
+        packet.addChild("origin-id", Namespace.STANZA_IDS).setAttribute("id", message.getUuid());
+        if (message.edited()) {
+            packet.addChild("replace", "urn:xmpp:message-correct:0").setAttribute("id", message.getEditedIdWireFormat());
+        }
+        return packet;
+    }
 
-	public void addDelay(MessagePacket packet, long timestamp) {
-		final SimpleDateFormat mDateFormat = new SimpleDateFormat(
-				"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US);
-		mDateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
-		Element delay = packet.addChild("delay", "urn:xmpp:delay");
-		Date date = new Date(timestamp);
-		delay.setAttribute("stamp", mDateFormat.format(date));
-	}
+    public void addDelay(MessagePacket packet, long timestamp) {
+        final SimpleDateFormat mDateFormat = new SimpleDateFormat(
+                "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US);
+        mDateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
+        Element delay = packet.addChild("delay", "urn:xmpp:delay");
+        Date date = new Date(timestamp);
+        delay.setAttribute("stamp", mDateFormat.format(date));
+    }
 
-	public MessagePacket generateAxolotlChat(Message message, XmppAxolotlMessage axolotlMessage) {
-		MessagePacket packet = preparePacket(message);
-		if (axolotlMessage == null) {
-			return null;
-		}
-		packet.setAxolotlMessage(axolotlMessage.toElement());
-		packet.setBody(OMEMO_FALLBACK_MESSAGE);
-		packet.addChild("store", "urn:xmpp:hints");
-		packet.addChild("encryption", "urn:xmpp:eme:0")
-				.setAttribute("name", "OMEMO")
-				.setAttribute("namespace", AxolotlService.PEP_PREFIX);
-		return packet;
-	}
+    public MessagePacket generateAxolotlChat(Message message, XmppAxolotlMessage axolotlMessage) {
+        MessagePacket packet = preparePacket(message);
+        if (axolotlMessage == null) {
+            return null;
+        }
+        packet.setAxolotlMessage(axolotlMessage.toElement());
+        packet.setBody(OMEMO_FALLBACK_MESSAGE);
+        packet.addChild("store", "urn:xmpp:hints");
+        packet.addChild("encryption", "urn:xmpp:eme:0")
+                .setAttribute("name", "OMEMO")
+                .setAttribute("namespace", AxolotlService.PEP_PREFIX);
+        return packet;
+    }
 
-	public MessagePacket generateKeyTransportMessage(Jid to, XmppAxolotlMessage axolotlMessage) {
-		MessagePacket packet = new MessagePacket();
-		packet.setType(MessagePacket.TYPE_CHAT);
-		packet.setTo(to);
-		packet.setAxolotlMessage(axolotlMessage.toElement());
-		packet.addChild("store", "urn:xmpp:hints");
-		return packet;
-	}
+    public MessagePacket generateKeyTransportMessage(Jid to, XmppAxolotlMessage axolotlMessage) {
+        MessagePacket packet = new MessagePacket();
+        packet.setType(MessagePacket.TYPE_CHAT);
+        packet.setTo(to);
+        packet.setAxolotlMessage(axolotlMessage.toElement());
+        packet.addChild("store", "urn:xmpp:hints");
+        return packet;
+    }
 
-	public MessagePacket generateChat(Message message) {
-		MessagePacket packet = preparePacket(message);
-		String content;
-		if (message.hasFileOnRemoteHost()) {
-			Message.FileParams fileParams = message.getFileParams();
-			final URL url = fileParams.url;
-			if (P1S3UrlStreamHandler.PROTOCOL_NAME.equals(url.getProtocol())) {
-				Element x = packet.addChild("x", Namespace.P1_S3_FILE_TRANSFER);
-				final String file = url.getFile();
-				x.setAttribute("name", file.charAt(0) == '/' ? file.substring(1) : file);
-				x.setAttribute("fileid", url.getHost());
-				return packet;
-			} else {
-				content = url.toString();
-				packet.addChild("x", Namespace.OOB).addChild("url").setContent(content);
-			}
-		} else {
-			content = message.getBody();
-		}
-		packet.setBody(content);
-		return packet;
-	}
+    public MessagePacket generateChat(Message message) {
+        MessagePacket packet = preparePacket(message);
+        String content;
+        if (message.hasFileOnRemoteHost()) {
+            Message.FileParams fileParams = message.getFileParams();
+            final URL url = fileParams.url;
+            if (P1S3UrlStreamHandler.PROTOCOL_NAME.equals(url.getProtocol())) {
+                Element x = packet.addChild("x", Namespace.P1_S3_FILE_TRANSFER);
+                final String file = url.getFile();
+                x.setAttribute("name", file.charAt(0) == '/' ? file.substring(1) : file);
+                x.setAttribute("fileid", url.getHost());
+                return packet;
+            } else {
+                content = url.toString();
+                packet.addChild("x", Namespace.OOB).addChild("url").setContent(content);
+            }
+        } else {
+            content = message.getBody();
+        }
+        packet.setBody(content);
+        return packet;
+    }
 
-	public MessagePacket generatePgpChat(Message message) {
-		MessagePacket packet = preparePacket(message);
-		if (message.hasFileOnRemoteHost()) {
-			Message.FileParams fileParams = message.getFileParams();
-			final URL url = fileParams.url;
-			if (P1S3UrlStreamHandler.PROTOCOL_NAME.equals(url.getProtocol())) {
-				Element x = packet.addChild("x", Namespace.P1_S3_FILE_TRANSFER);
-				final String file = url.getFile();
-				x.setAttribute("name", file.charAt(0) == '/' ? file.substring(1) : file);
-				x.setAttribute("fileid", url.getHost());
-			} else {
-				packet.setBody(url.toString());
-				packet.addChild("x", Namespace.OOB).addChild("url").setContent(url.toString());
-			}
-		} else {
-			if (Config.supportUnencrypted()) {
-				packet.setBody(PGP_FALLBACK_MESSAGE);
-			}
-			if (message.getEncryption() == Message.ENCRYPTION_DECRYPTED) {
-				packet.addChild("x", "jabber:x:encrypted").setContent(message.getEncryptedBody());
-			} else if (message.getEncryption() == Message.ENCRYPTION_PGP) {
-				packet.addChild("x", "jabber:x:encrypted").setContent(message.getBody());
-			}
-			packet.addChild("encryption", "urn:xmpp:eme:0")
-					.setAttribute("namespace", "jabber:x:encrypted");
-		}
-		return packet;
-	}
+    public MessagePacket generatePgpChat(Message message) {
+        MessagePacket packet = preparePacket(message);
+        if (message.hasFileOnRemoteHost()) {
+            Message.FileParams fileParams = message.getFileParams();
+            final URL url = fileParams.url;
+            if (P1S3UrlStreamHandler.PROTOCOL_NAME.equals(url.getProtocol())) {
+                Element x = packet.addChild("x", Namespace.P1_S3_FILE_TRANSFER);
+                final String file = url.getFile();
+                x.setAttribute("name", file.charAt(0) == '/' ? file.substring(1) : file);
+                x.setAttribute("fileid", url.getHost());
+            } else {
+                packet.setBody(url.toString());
+                packet.addChild("x", Namespace.OOB).addChild("url").setContent(url.toString());
+            }
+        } else {
+            if (Config.supportUnencrypted()) {
+                packet.setBody(PGP_FALLBACK_MESSAGE);
+            }
+            if (message.getEncryption() == Message.ENCRYPTION_DECRYPTED) {
+                packet.addChild("x", "jabber:x:encrypted").setContent(message.getEncryptedBody());
+            } else if (message.getEncryption() == Message.ENCRYPTION_PGP) {
+                packet.addChild("x", "jabber:x:encrypted").setContent(message.getBody());
+            }
+            packet.addChild("encryption", "urn:xmpp:eme:0")
+                    .setAttribute("namespace", "jabber:x:encrypted");
+        }
+        return packet;
+    }
 
-	public MessagePacket generateChatState(Conversation conversation) {
-		final Account account = conversation.getAccount();
-		MessagePacket packet = new MessagePacket();
-		packet.setType(conversation.getMode() == Conversation.MODE_MULTI ? MessagePacket.TYPE_GROUPCHAT : MessagePacket.TYPE_CHAT);
-		packet.setTo(conversation.getJid().asBareJid());
-		packet.setFrom(account.getJid());
-		packet.addChild(ChatState.toElement(conversation.getOutgoingChatState()));
-		packet.addChild("no-store", "urn:xmpp:hints");
-		packet.addChild("no-storage", "urn:xmpp:hints"); //wrong! don't copy this. Its *store*
-		return packet;
-	}
+    public MessagePacket generateChatState(Conversation conversation) {
+        final Account account = conversation.getAccount();
+        MessagePacket packet = new MessagePacket();
+        packet.setType(conversation.getMode() == Conversation.MODE_MULTI ? MessagePacket.TYPE_GROUPCHAT : MessagePacket.TYPE_CHAT);
+        packet.setTo(conversation.getJid().asBareJid());
+        packet.setFrom(account.getJid());
+        packet.addChild(ChatState.toElement(conversation.getOutgoingChatState()));
+        packet.addChild("no-store", "urn:xmpp:hints");
+        packet.addChild("no-storage", "urn:xmpp:hints"); //wrong! don't copy this. Its *store*
+        return packet;
+    }
 
-	public MessagePacket confirm(final Account account, final Jid to, final String id, final Jid counterpart, final boolean groupChat) {
-		MessagePacket packet = new MessagePacket();
-		packet.setType(groupChat ? MessagePacket.TYPE_GROUPCHAT : MessagePacket.TYPE_CHAT);
-		packet.setTo(groupChat ? to.asBareJid() : to);
-		packet.setFrom(account.getJid());
-		Element displayed = packet.addChild("displayed", "urn:xmpp:chat-markers:0");
-		displayed.setAttribute("id", id);
-		if (groupChat && counterpart != null) {
-			displayed.setAttribute("sender", counterpart.toString());
-		}
-		packet.addChild("store", "urn:xmpp:hints");
-		return packet;
-	}
+    public MessagePacket confirm(final Account account, final Jid to, final String id, final Jid counterpart, final boolean groupChat) {
+        MessagePacket packet = new MessagePacket();
+        packet.setType(groupChat ? MessagePacket.TYPE_GROUPCHAT : MessagePacket.TYPE_CHAT);
+        packet.setTo(groupChat ? to.asBareJid() : to);
+        packet.setFrom(account.getJid());
+        Element displayed = packet.addChild("displayed", "urn:xmpp:chat-markers:0");
+        displayed.setAttribute("id", id);
+        if (groupChat && counterpart != null) {
+            displayed.setAttribute("sender", counterpart.toString());
+        }
+        packet.addChild("store", "urn:xmpp:hints");
+        return packet;
+    }
 
-	public MessagePacket conferenceSubject(Conversation conversation, String subject) {
-		MessagePacket packet = new MessagePacket();
-		packet.setType(MessagePacket.TYPE_GROUPCHAT);
-		packet.setTo(conversation.getJid().asBareJid());
-		packet.addChild("subject").setContent(subject);
-		packet.setFrom(conversation.getAccount().getJid().asBareJid());
-		return packet;
-	}
+    public MessagePacket conferenceSubject(Conversation conversation, String subject) {
+        MessagePacket packet = new MessagePacket();
+        packet.setType(MessagePacket.TYPE_GROUPCHAT);
+        packet.setTo(conversation.getJid().asBareJid());
+        packet.addChild("subject").setContent(subject);
+        packet.setFrom(conversation.getAccount().getJid().asBareJid());
+        return packet;
+    }
 
-	public MessagePacket directInvite(final Conversation conversation, final Jid contact) {
-		MessagePacket packet = new MessagePacket();
-		packet.setType(MessagePacket.TYPE_NORMAL);
-		packet.setTo(contact);
-		packet.setFrom(conversation.getAccount().getJid());
-		Element x = packet.addChild("x", "jabber:x:conference");
-		x.setAttribute("jid", conversation.getJid().asBareJid().toString());
-		String password = conversation.getMucOptions().getPassword();
-		if (password != null) {
-			x.setAttribute("password", password);
-		}
-		if (contact.isFullJid()) {
-			packet.addChild("no-store", "urn:xmpp:hints");
-			packet.addChild("no-copy", "urn:xmpp:hints");
-		}
-		return packet;
-	}
+    public MessagePacket directInvite(final Conversation conversation, final Jid contact) {
+        MessagePacket packet = new MessagePacket();
+        packet.setType(MessagePacket.TYPE_NORMAL);
+        packet.setTo(contact);
+        packet.setFrom(conversation.getAccount().getJid());
+        Element x = packet.addChild("x", "jabber:x:conference");
+        x.setAttribute("jid", conversation.getJid().asBareJid().toString());
+        String password = conversation.getMucOptions().getPassword();
+        if (password != null) {
+            x.setAttribute("password", password);
+        }
+        if (contact.isFullJid()) {
+            packet.addChild("no-store", "urn:xmpp:hints");
+            packet.addChild("no-copy", "urn:xmpp:hints");
+        }
+        return packet;
+    }
 
-	public MessagePacket invite(Conversation conversation, Jid contact) {
-		MessagePacket packet = new MessagePacket();
-		packet.setTo(conversation.getJid().asBareJid());
-		packet.setFrom(conversation.getAccount().getJid());
-		Element x = new Element("x");
-		x.setAttribute("xmlns", "http://jabber.org/protocol/muc#user");
-		Element invite = new Element("invite");
-		invite.setAttribute("to", contact.asBareJid().toString());
-		x.addChild(invite);
-		packet.addChild(x);
-		return packet;
-	}
+    public MessagePacket invite(Conversation conversation, Jid contact) {
+        MessagePacket packet = new MessagePacket();
+        packet.setTo(conversation.getJid().asBareJid());
+        packet.setFrom(conversation.getAccount().getJid());
+        Element x = new Element("x");
+        x.setAttribute("xmlns", "http://jabber.org/protocol/muc#user");
+        Element invite = new Element("invite");
+        invite.setAttribute("to", contact.asBareJid().toString());
+        x.addChild(invite);
+        packet.addChild(x);
+        return packet;
+    }
 
-	public MessagePacket received(Account account, MessagePacket originalMessage, ArrayList<String> namespaces, int type) {
-		MessagePacket receivedPacket = new MessagePacket();
-		receivedPacket.setType(type);
-		receivedPacket.setTo(originalMessage.getFrom());
-		receivedPacket.setFrom(account.getJid());
-		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, MessagePacket originalMessage, ArrayList<String> namespaces, int type) {
+        MessagePacket receivedPacket = new MessagePacket();
+        receivedPacket.setType(type);
+        receivedPacket.setTo(originalMessage.getFrom());
+        receivedPacket.setFrom(account.getJid());
+        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 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 sessionProposal(final JingleConnectionManager.RtpSessionProposal proposal) {
-		final MessagePacket packet = new MessagePacket();
+    public MessagePacket sessionProposal(final JingleConnectionManager.RtpSessionProposal proposal) {
+        final MessagePacket packet = new MessagePacket();
         packet.setType(MessagePacket.TYPE_CHAT); //we want to carbon copy those
-		packet.setTo(proposal.with);
-		packet.setId(JingleRtpConnection.JINGLE_MESSAGE_PROPOSE_ID_PREFIX +proposal.sessionId);
-		final Element propose = packet.addChild("propose", Namespace.JINGLE_MESSAGE);
-		propose.setAttribute("id", proposal.sessionId);
-		propose.addChild("description", Namespace.JINGLE_APPS_RTP);
-		packet.addChild("request", "urn:xmpp:receipts");
-		return packet;
-	}
+        packet.setTo(proposal.with);
+        packet.setId(JingleRtpConnection.JINGLE_MESSAGE_PROPOSE_ID_PREFIX + proposal.sessionId);
+        final Element propose = packet.addChild("propose", Namespace.JINGLE_MESSAGE);
+        propose.setAttribute("id", proposal.sessionId);
+        propose.addChild("description", Namespace.JINGLE_APPS_RTP);
+        packet.addChild("request", "urn:xmpp:receipts");
+        packet.addChild("store", "urn:xmpp:hints");
+        return packet;
+    }
 
-	public MessagePacket sessionRetract(final JingleConnectionManager.RtpSessionProposal proposal) {
-		final MessagePacket packet = new MessagePacket();
+    public MessagePacket sessionRetract(final JingleConnectionManager.RtpSessionProposal proposal) {
+        final MessagePacket packet = new MessagePacket();
         packet.setType(MessagePacket.TYPE_CHAT); //we want to carbon copy those
-		packet.setTo(proposal.with);
-		final Element propose = packet.addChild("retract", Namespace.JINGLE_MESSAGE);
-		propose.setAttribute("id", proposal.sessionId);
-		propose.addChild("description", Namespace.JINGLE_APPS_RTP);
-		return packet;
-	}
+        packet.setTo(proposal.with);
+        final Element propose = packet.addChild("retract", Namespace.JINGLE_MESSAGE);
+        propose.setAttribute("id", proposal.sessionId);
+        propose.addChild("description", Namespace.JINGLE_APPS_RTP);
+        packet.addChild("store", "urn:xmpp:hints");
+        return packet;
+    }
 
-	public MessagePacket sessionReject(final Jid with, final String sessionId) {
-		final MessagePacket packet = new MessagePacket();
-		packet.setType(MessagePacket.TYPE_CHAT); //we want to carbon copy those
-		packet.setTo(with);
-		final Element propose = packet.addChild("reject", Namespace.JINGLE_MESSAGE);
-		propose.setAttribute("id", sessionId);
-		propose.addChild("description", Namespace.JINGLE_APPS_RTP);
-		return packet;
-	}
+    public MessagePacket sessionReject(final Jid with, final String sessionId) {
+        final MessagePacket packet = new MessagePacket();
+        packet.setType(MessagePacket.TYPE_CHAT); //we want to carbon copy those
+        packet.setTo(with);
+        final Element propose = packet.addChild("reject", Namespace.JINGLE_MESSAGE);
+        propose.setAttribute("id", sessionId);
+        propose.addChild("description", Namespace.JINGLE_APPS_RTP);
+        packet.addChild("store", "urn:xmpp:hints");
+        return packet;
+    }
 }

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

@@ -31,6 +31,7 @@ import eu.siacs.conversations.entities.Message;
 import eu.siacs.conversations.entities.MucOptions;
 import eu.siacs.conversations.entities.ReadByMarker;
 import eu.siacs.conversations.entities.ReceiptRequest;
+import eu.siacs.conversations.entities.RtpSessionStatus;
 import eu.siacs.conversations.http.HttpConnectionManager;
 import eu.siacs.conversations.http.P1S3UrlStreamHandler;
 import eu.siacs.conversations.services.MessageArchiveService;
@@ -73,6 +74,11 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
         return safeToExtract ? extractStanzaId(packet, by) : null;
     }
 
+    private static String extractStanzaId(Account account, Element packet) {
+        final boolean safeToExtract = account.getXmppConnection().getFeatures().stanzaIds();
+        return safeToExtract ? extractStanzaId(packet, account.getJid().asBareJid()) : null;
+    }
+
     private static String extractStanzaId(Element packet, Jid by) {
         for (Element child : packet.getChildren()) {
             if (child.getName().equals("stanza-id")
@@ -829,17 +835,56 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
             if (!isTypeGroupChat) {
                 for (Element child : packet.getChildren()) {
                     if (Namespace.JINGLE_MESSAGE.equals(child.getNamespace()) && JINGLE_MESSAGE_ELEMENT_NAMES.contains(child.getName())) {
-                        //TODO in this case we probably only want to send receipts for live messages
-                        //as soon as it comes from MAM it is probably too late anyway
-                        if (!account.getJid().asBareJid().equals(from.asBareJid())) {
-                            processMessageReceipts(account, packet, query);
-                        }
-                        //TODO only live propose messages should get processed that way; however we may want to deliver 'accept' and 'reject' to stop ringing
-                        mXmppConnectionService.getJingleConnectionManager().deliverMessage(account, packet.getTo(), packet.getFrom(), child, serverMsgId, timestamp);
+                        final String action = child.getName();
+                        if (query == null) {
+                            if (!account.getJid().asBareJid().equals(from.asBareJid())) {
+                                processMessageReceipts(account, packet, query);
+                            }
+                            if (serverMsgId == null) {
+                                serverMsgId = extractStanzaId(account, packet);
+                            }
+                            mXmppConnectionService.getJingleConnectionManager().deliverMessage(account, packet.getTo(), packet.getFrom(), child, serverMsgId, timestamp);
+                        } else if (query.isCatchup()) {
+                            final String sessionId = child.getAttribute("id");
+                            if (sessionId == null) {
+                                break;
+                            }
+                            if ("propose".equals(action)) {
+                                final Element description = child.findChild("description");
+                                final String namespace = description == null ? null : description.getNamespace();
+                                if (Namespace.JINGLE_APPS_RTP.equals(namespace)) {
+                                    final Conversation c = mXmppConnectionService.findOrCreateConversation(account, counterpart.asBareJid(), false, false);
+                                    final Message message = new Message(
+                                            c,
+                                            status,
+                                            Message.TYPE_RTP_SESSION,
+                                            sessionId
+                                    );
+                                    message.setServerMsgId(serverMsgId);
+                                    message.setTime(timestamp);
+                                    message.setBody(new RtpSessionStatus(false, 0).toString());
+                                    c.add(message);
+                                    mXmppConnectionService.databaseBackend.createMessage(message);
+                                }
 
+                            } else if ("proceed".equals(action)) {
+                                //status needs to be flipped to find the original propose
+                                final Conversation c = mXmppConnectionService.findOrCreateConversation(account, counterpart.asBareJid(), false, false);
+                                final int s = packet.fromAccount(account) ? Message.STATUS_RECEIVED : Message.STATUS_SEND;
+                                final Message message = c.findRtpSession(sessionId, s);
+                                if (message != null) {
+                                    message.setBody(new RtpSessionStatus(true, 0).toString());
+                                    if (serverMsgId != null) {
+                                        message.setServerMsgId(serverMsgId);
+                                    }
+                                    message.setTime(timestamp);
+                                    mXmppConnectionService.updateMessage(message, true);
+                                } else {
+                                    Log.d(Config.LOGTAG, "unable to find original rtp session message for received propose");
+                                }
 
-                        //TODO for queries we might want to process 'propose' and 'proceed'
-                        //TODO propose will trigger a 'missed call' entry; 'proceed' might update that to a non missed call
+                            }
+                        }
                         break;
                     }
                 }

src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java 🔗

@@ -494,7 +494,7 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
         if (from.equals(id.with)) {
             if (transition(State.RETRACTED)) {
                 xmppConnectionService.getNotificationService().cancelIncomingCallNotification();
-                Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": session with " + id.with + " has been retracted");
+                Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": session with " + id.with + " has been retracted (serverMsgId="+serverMsgId+")");
                 if (serverMsgId != null) {
                     this.message.setServerMsgId(serverMsgId);
                 }
@@ -776,7 +776,7 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
         messagePacket.setType(MessagePacket.TYPE_CHAT); //we want to carbon copy those
         messagePacket.setTo(to);
         messagePacket.addChild(action, Namespace.JINGLE_MESSAGE).setAttribute("id", id.sessionId);
-        Log.d(Config.LOGTAG, messagePacket.toString());
+        messagePacket.addChild("store", "urn:xmpp:hints");
         xmppConnectionService.sendMessagePacket(id.account, messagePacket);
     }