ensure that message is parsed into bubble OR receipt, marker, reaction

Daniel Gultsch created

This commit ensures that Conversations always parses a message into a
bubble or as meta data (receipt, display marker, reaction) never both
and with a preference for bubble.

Conversations goes through great length to ensure that all participants
of a group chat have a similiar (or ideally the same) view of the chat
history.
For example Conversations doesn’t use xhtml, shows a language tag when
the content is available in multiple languages, etc.

So a message that has both a body and a reaction for example shouldn't
show differently in clients that support reactions and clients that
don't.

Take the following example:

```xml
<message to='romeo@capulet.net/troll' type='groupchat'>
  <body>Conversations users are stupid!</body>
  <reactions id='a1' xmlns='urn:xmpp:reactions:0'>
    <reaction>💩</reaction>
  </reactions>
</message>
```

This message should render as what it is: A message and the body
shouldn't be silently discarded for clients that support reactions.

(The same goes for display markers and receipts)

Fallback messages for something that is not supposed to render as a
bubble should be discouraged.

Change summary

src/main/java/eu/siacs/conversations/parser/MessageParser.java   | 583 +
src/main/java/im/conversations/android/xmpp/model/Extension.java |  11 
2 files changed, 440 insertions(+), 154 deletions(-)

Detailed changes

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

@@ -2,22 +2,8 @@ package eu.siacs.conversations.parser;
 
 import android.util.Log;
 import android.util.Pair;
-
 import com.google.common.base.Strings;
 import com.google.common.collect.ImmutableSet;
-
-import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.Date;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Set;
-import java.util.UUID;
-import java.util.function.Consumer;
-
 import eu.siacs.conversations.AppSettings;
 import eu.siacs.conversations.Config;
 import eu.siacs.conversations.R;
@@ -47,22 +33,36 @@ import eu.siacs.conversations.xml.LocalizedContent;
 import eu.siacs.conversations.xml.Namespace;
 import eu.siacs.conversations.xmpp.InvalidJid;
 import eu.siacs.conversations.xmpp.Jid;
-import eu.siacs.conversations.xmpp.OnMessagePacketReceived;
 import eu.siacs.conversations.xmpp.chatstate.ChatState;
 import eu.siacs.conversations.xmpp.jingle.JingleConnectionManager;
 import eu.siacs.conversations.xmpp.jingle.JingleRtpConnection;
 import eu.siacs.conversations.xmpp.pep.Avatar;
 import im.conversations.android.xmpp.model.Extension;
+import im.conversations.android.xmpp.model.axolotl.Encrypted;
 import im.conversations.android.xmpp.model.carbons.Received;
 import im.conversations.android.xmpp.model.carbons.Sent;
 import im.conversations.android.xmpp.model.correction.Replace;
 import im.conversations.android.xmpp.model.forward.Forwarded;
+import im.conversations.android.xmpp.model.markers.Displayed;
 import im.conversations.android.xmpp.model.occupant.OccupantId;
 import im.conversations.android.xmpp.model.reactions.Reactions;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+import java.util.function.Consumer;
 
-public class MessageParser extends AbstractParser implements Consumer<im.conversations.android.xmpp.model.stanza.Message> {
+public class MessageParser extends AbstractParser
+        implements Consumer<im.conversations.android.xmpp.model.stanza.Message> {
 
-    private static final SimpleDateFormat TIME_FORMAT = new SimpleDateFormat("HH:mm:ss", Locale.ENGLISH);
+    private static final SimpleDateFormat TIME_FORMAT =
+            new SimpleDateFormat("HH:mm:ss", Locale.ENGLISH);
 
     private static final List<String> JINGLE_MESSAGE_ELEMENT_NAMES =
             Arrays.asList("accept", "propose", "proceed", "reject", "retract", "ringing", "finish");
@@ -71,7 +71,8 @@ public class MessageParser extends AbstractParser implements Consumer<im.convers
         super(service, account);
     }
 
-    private static String extractStanzaId(Element packet, boolean isTypeGroupChat, Conversation conversation) {
+    private static String extractStanzaId(
+            Element packet, boolean isTypeGroupChat, Conversation conversation) {
         final Jid by;
         final boolean safeToExtract;
         if (isTypeGroupChat) {
@@ -103,11 +104,15 @@ public class MessageParser extends AbstractParser implements Consumer<im.convers
 
     private static Jid getTrueCounterpart(Element mucUserElement, Jid fallback) {
         final Element item = mucUserElement == null ? null : mucUserElement.findChild("item");
-        Jid result = item == null ? null : InvalidJid.getNullForInvalid(item.getAttributeAsJid("jid"));
+        Jid result =
+                item == null ? null : InvalidJid.getNullForInvalid(item.getAttributeAsJid("jid"));
         return result != null ? result : fallback;
     }
 
-    private boolean extractChatState(Conversation c, final boolean isTypeGroupChat, final im.conversations.android.xmpp.model.stanza.Message packet) {
+    private boolean extractChatState(
+            Conversation c,
+            final boolean isTypeGroupChat,
+            final im.conversations.android.xmpp.model.stanza.Message packet) {
         ChatState state = ChatState.parse(packet);
         if (state != null && c != null) {
             final Account account = c.getAccount();
@@ -138,45 +143,76 @@ public class MessageParser extends AbstractParser implements Consumer<im.convers
         return false;
     }
 
-    private Message parseAxolotlChat(Element axolotlMessage, Jid from, Conversation conversation, int status, final boolean checkedForDuplicates, boolean postpone) {
+    private Message parseAxolotlChat(
+            final Encrypted axolotlMessage,
+            final Jid from,
+            final Conversation conversation,
+            final int status,
+            final boolean checkedForDuplicates,
+            final boolean postpone) {
         final AxolotlService service = conversation.getAccount().getAxolotlService();
         final XmppAxolotlMessage xmppAxolotlMessage;
         try {
             xmppAxolotlMessage = XmppAxolotlMessage.fromElement(axolotlMessage, from.asBareJid());
         } catch (Exception e) {
-            Log.d(Config.LOGTAG, conversation.getAccount().getJid().asBareJid() + ": invalid omemo message received " + e.getMessage());
+            Log.d(
+                    Config.LOGTAG,
+                    conversation.getAccount().getJid().asBareJid()
+                            + ": invalid omemo message received "
+                            + e.getMessage());
             return null;
         }
         if (xmppAxolotlMessage.hasPayload()) {
             final XmppAxolotlMessage.XmppAxolotlPlaintextMessage plaintextMessage;
             try {
-                plaintextMessage = service.processReceivingPayloadMessage(xmppAxolotlMessage, postpone);
+                plaintextMessage =
+                        service.processReceivingPayloadMessage(xmppAxolotlMessage, postpone);
             } catch (BrokenSessionException e) {
                 if (checkedForDuplicates) {
                     if (service.trustedOrPreviouslyResponded(from.asBareJid())) {
                         service.reportBrokenSessionException(e, postpone);
-                        return new Message(conversation, "", Message.ENCRYPTION_AXOLOTL_FAILED, status);
+                        return new Message(
+                                conversation, "", Message.ENCRYPTION_AXOLOTL_FAILED, status);
                     } else {
-                        Log.d(Config.LOGTAG, "ignoring broken session exception because contact was not trusted");
-                        return new Message(conversation, "", Message.ENCRYPTION_AXOLOTL_FAILED, status);
+                        Log.d(
+                                Config.LOGTAG,
+                                "ignoring broken session exception because contact was not"
+                                        + " trusted");
+                        return new Message(
+                                conversation, "", Message.ENCRYPTION_AXOLOTL_FAILED, status);
                     }
                 } else {
-                    Log.d(Config.LOGTAG, "ignoring broken session exception because checkForDuplicates failed");
+                    Log.d(
+                            Config.LOGTAG,
+                            "ignoring broken session exception because checkForDuplicates failed");
                     return null;
                 }
             } catch (NotEncryptedForThisDeviceException e) {
-                return new Message(conversation, "", Message.ENCRYPTION_AXOLOTL_NOT_FOR_THIS_DEVICE, status);
+                return new Message(
+                        conversation, "", Message.ENCRYPTION_AXOLOTL_NOT_FOR_THIS_DEVICE, status);
             } catch (OutdatedSenderException e) {
                 return new Message(conversation, "", Message.ENCRYPTION_AXOLOTL_FAILED, status);
             }
             if (plaintextMessage != null) {
-                Message finishedMessage = new Message(conversation, plaintextMessage.getPlaintext(), Message.ENCRYPTION_AXOLOTL, status);
+                Message finishedMessage =
+                        new Message(
+                                conversation,
+                                plaintextMessage.getPlaintext(),
+                                Message.ENCRYPTION_AXOLOTL,
+                                status);
                 finishedMessage.setFingerprint(plaintextMessage.getFingerprint());
-                Log.d(Config.LOGTAG, AxolotlService.getLogprefix(finishedMessage.getConversation().getAccount()) + " Received Message with session fingerprint: " + plaintextMessage.getFingerprint());
+                Log.d(
+                        Config.LOGTAG,
+                        AxolotlService.getLogprefix(finishedMessage.getConversation().getAccount())
+                                + " Received Message with session fingerprint: "
+                                + plaintextMessage.getFingerprint());
                 return finishedMessage;
             }
         } else {
-            Log.d(Config.LOGTAG, conversation.getAccount().getJid().asBareJid() + ": received OMEMO key transport message");
+            Log.d(
+                    Config.LOGTAG,
+                    conversation.getAccount().getJid().asBareJid()
+                            + ": received OMEMO key transport message");
             service.processReceivingKeyTransportMessage(xmppAxolotlMessage, postpone);
         }
         return null;
@@ -191,7 +227,7 @@ public class MessageParser extends AbstractParser implements Consumer<im.convers
                 final Jid from = InvalidJid.getNullForInvalid(invite.getAttributeAsJid("from"));
                 final Jid to = InvalidJid.getNullForInvalid(invite.getAttributeAsJid("to"));
                 if (to != null && from == null) {
-                    Log.d(Config.LOGTAG,"do not parse outgoing mediated invite "+message);
+                    Log.d(Config.LOGTAG, "do not parse outgoing mediated invite " + message);
                     return null;
                 }
                 final Jid room = InvalidJid.getNullForInvalid(message.getAttributeAsJid("from"));
@@ -250,7 +286,14 @@ public class MessageParser extends AbstractParser implements Consumer<im.convers
         } else if (AxolotlService.PEP_DEVICE_LIST.equals(node)) {
             Element item = items.findChild("item");
             final Set<Integer> deviceIds = IqParser.deviceIds(item);
-            Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Received PEP device list " + deviceIds + " update from " + from + ", processing... ");
+            Log.d(
+                    Config.LOGTAG,
+                    AxolotlService.getLogprefix(account)
+                            + "Received PEP device list "
+                            + deviceIds
+                            + " update from "
+                            + from
+                            + ", processing... ");
             final AxolotlService axolotlService = account.getAxolotlService();
             axolotlService.registerDevices(from, deviceIds);
         } else if (Namespace.BOOKMARKS.equals(node) && account.getJid().asBareJid().equals(from)) {
@@ -260,7 +303,8 @@ public class MessageParser extends AbstractParser implements Consumer<im.convers
                     Log.w(
                             Config.LOGTAG,
                             account.getJid().asBareJid()
-                                    + ": received storage:bookmark notification even though we opted into bookmarks:1");
+                                    + ": received storage:bookmark notification even though we"
+                                    + " opted into bookmarks:1");
                 }
                 final Element i = items.findChild("item");
                 final Element storage =
@@ -274,7 +318,8 @@ public class MessageParser extends AbstractParser implements Consumer<im.convers
                 Log.d(
                         Config.LOGTAG,
                         account.getJid().asBareJid()
-                                + ": ignoring bookmark PEP event because bookmark conversion was not detected");
+                                + ": ignoring bookmark PEP event because bookmark conversion was"
+                                + " not detected");
             }
         } else if (Namespace.BOOKMARKS2.equals(node) && account.getJid().asBareJid().equals(from)) {
             final Element item = items.findChild("item");
@@ -291,7 +336,9 @@ public class MessageParser extends AbstractParser implements Consumer<im.convers
                 final Jid id = InvalidJid.getNullForInvalid(retract.getAttributeAsJid("id"));
                 if (id != null) {
                     account.removeBookmark(id);
-                    Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": deleted bookmark for " + id);
+                    Log.d(
+                            Config.LOGTAG,
+                            account.getJid().asBareJid() + ": deleted bookmark for " + id);
                     mXmppConnectionService.processDeletedBookmark(account, id);
                     mXmppConnectionService.updateConversationUi();
                 }
@@ -319,8 +366,9 @@ public class MessageParser extends AbstractParser implements Consumer<im.convers
         } else if (Namespace.BOOKMARKS2.equals(node) && account.getJid().asBareJid().equals(from)) {
             Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": deleted bookmarks node");
             deleteAllBookmarks(account);
-        } else if (Namespace.AVATAR_METADATA.equals(node) && account.getJid().asBareJid().equals(from)) {
-            Log.d(Config.LOGTAG,account.getJid().asBareJid()+": deleted avatar metadata node");
+        } else if (Namespace.AVATAR_METADATA.equals(node)
+                && account.getJid().asBareJid().equals(from)) {
+            Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": deleted avatar metadata node");
         }
     }
 
@@ -357,10 +405,13 @@ public class MessageParser extends AbstractParser implements Consumer<im.convers
         mXmppConnectionService.updateAccountUi();
     }
 
-    private boolean handleErrorMessage(final Account account, final im.conversations.android.xmpp.model.stanza.Message packet) {
+    private boolean handleErrorMessage(
+            final Account account,
+            final im.conversations.android.xmpp.model.stanza.Message packet) {
         if (packet.getType() == im.conversations.android.xmpp.model.stanza.Message.Type.ERROR) {
             if (packet.fromServer(account)) {
-                final var forwarded = getForwardedMessagePacket(packet,"received", Namespace.CARBONS);
+                final var forwarded =
+                        getForwardedMessagePacket(packet, "received", Namespace.CARBONS);
                 if (forwarded != null) {
                     return handleErrorMessage(account, forwarded.first);
                 }
@@ -369,29 +420,51 @@ public class MessageParser extends AbstractParser implements Consumer<im.convers
             final String id = packet.getId();
             if (from != null && id != null) {
                 if (id.startsWith(JingleRtpConnection.JINGLE_MESSAGE_PROPOSE_ID_PREFIX)) {
-                    final String sessionId = id.substring(JingleRtpConnection.JINGLE_MESSAGE_PROPOSE_ID_PREFIX.length());
-                    mXmppConnectionService.getJingleConnectionManager()
-                            .updateProposedSessionDiscovered(account, from, sessionId, JingleConnectionManager.DeviceDiscoveryState.FAILED);
+                    final String sessionId =
+                            id.substring(
+                                    JingleRtpConnection.JINGLE_MESSAGE_PROPOSE_ID_PREFIX.length());
+                    mXmppConnectionService
+                            .getJingleConnectionManager()
+                            .updateProposedSessionDiscovered(
+                                    account,
+                                    from,
+                                    sessionId,
+                                    JingleConnectionManager.DeviceDiscoveryState.FAILED);
                     return true;
                 }
                 if (id.startsWith(JingleRtpConnection.JINGLE_MESSAGE_PROCEED_ID_PREFIX)) {
-                    final String sessionId = id.substring(JingleRtpConnection.JINGLE_MESSAGE_PROCEED_ID_PREFIX.length());
+                    final String sessionId =
+                            id.substring(
+                                    JingleRtpConnection.JINGLE_MESSAGE_PROCEED_ID_PREFIX.length());
                     final String message = extractErrorMessage(packet);
-                    mXmppConnectionService.getJingleConnectionManager().failProceed(account, from, sessionId, message);
+                    mXmppConnectionService
+                            .getJingleConnectionManager()
+                            .failProceed(account, from, sessionId, message);
                     return true;
                 }
-                mXmppConnectionService.markMessage(account,
+                mXmppConnectionService.markMessage(
+                        account,
                         from.asBareJid(),
                         id,
                         Message.STATUS_SEND_FAILED,
                         extractErrorMessage(packet));
                 final Element error = packet.findChild("error");
-                final boolean pingWorthyError = error != null && (error.hasChild("not-acceptable") || error.hasChild("remote-server-timeout") || error.hasChild("remote-server-not-found"));
+                final boolean pingWorthyError =
+                        error != null
+                                && (error.hasChild("not-acceptable")
+                                        || error.hasChild("remote-server-timeout")
+                                        || error.hasChild("remote-server-not-found"));
                 if (pingWorthyError) {
                     Conversation conversation = mXmppConnectionService.find(account, from);
-                    if (conversation != null && conversation.getMode() == Conversational.MODE_MULTI) {
+                    if (conversation != null
+                            && conversation.getMode() == Conversational.MODE_MULTI) {
                         if (conversation.getMucOptions().online()) {
-                            Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": received ping worthy error for seemingly online muc at " + from);
+                            Log.d(
+                                    Config.LOGTAG,
+                                    account.getJid().asBareJid()
+                                            + ": received ping worthy error for seemingly online"
+                                            + " muc at "
+                                            + from);
                             mXmppConnectionService.mucSelfPingAndRejoin(conversation);
                         }
                     }
@@ -411,17 +484,24 @@ public class MessageParser extends AbstractParser implements Consumer<im.convers
         Long timestamp = null;
         boolean isCarbon = false;
         String serverMsgId = null;
-        final Element fin = original.findChild("fin", MessageArchiveService.Version.MAM_0.namespace);
+        final Element fin =
+                original.findChild("fin", MessageArchiveService.Version.MAM_0.namespace);
         if (fin != null) {
-            mXmppConnectionService.getMessageArchiveService().processFinLegacy(fin, original.getFrom());
+            mXmppConnectionService
+                    .getMessageArchiveService()
+                    .processFinLegacy(fin, original.getFrom());
             return;
         }
         final Element result = MessageArchiveService.Version.findResult(original);
         final String queryId = result == null ? null : result.getAttribute("queryid");
-        final MessageArchiveService.Query query = queryId == null ? null : mXmppConnectionService.getMessageArchiveService().findQuery(queryId);
-        final boolean offlineMessagesRetrieved = account.getXmppConnection().isOfflineMessagesRetrieved();
+        final MessageArchiveService.Query query =
+                queryId == null
+                        ? null
+                        : mXmppConnectionService.getMessageArchiveService().findQuery(queryId);
+        final boolean offlineMessagesRetrieved =
+                account.getXmppConnection().isOfflineMessagesRetrieved();
         if (query != null && query.validFrom(original.getFrom())) {
-            final var f = getForwardedMessagePacket(original,"result", query.version.namespace);
+            final var f = getForwardedMessagePacket(original, "result", query.version.namespace);
             if (f == null) {
                 return;
             }
@@ -433,10 +513,18 @@ public class MessageParser extends AbstractParser implements Consumer<im.convers
                 return;
             }
         } else if (query != null) {
-            Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": received mam result with invalid from (" + original.getFrom() + ") or queryId (" + queryId + ")");
+            Log.d(
+                    Config.LOGTAG,
+                    account.getJid().asBareJid()
+                            + ": received mam result with invalid from ("
+                            + original.getFrom()
+                            + ") or queryId ("
+                            + queryId
+                            + ")");
             return;
         } else if (original.fromServer(account)
-                && original.getType() != im.conversations.android.xmpp.model.stanza.Message.Type.GROUPCHAT) {
+                && original.getType()
+                        != im.conversations.android.xmpp.model.stanza.Message.Type.GROUPCHAT) {
             Pair<im.conversations.android.xmpp.model.stanza.Message, Long> f;
             f = getForwardedMessagePacket(original, Received.class);
             f = f == null ? getForwardedMessagePacket(original, Sent.class) : f;
@@ -451,19 +539,21 @@ public class MessageParser extends AbstractParser implements Consumer<im.convers
         }
 
         if (timestamp == null) {
-            timestamp = AbstractParser.parseTimestamp(original, AbstractParser.parseTimestamp(packet));
+            timestamp =
+                    AbstractParser.parseTimestamp(original, AbstractParser.parseTimestamp(packet));
         }
-        final Reactions reactions = packet.getExtension(Reactions.class);
         final LocalizedContent body = packet.getBody();
         final Element mucUserElement = packet.findChild("x", Namespace.MUC_USER);
-        final boolean isTypeGroupChat = packet.getType() == im.conversations.android.xmpp.model.stanza.Message.Type.GROUPCHAT;
+        final boolean isTypeGroupChat =
+                packet.getType()
+                        == im.conversations.android.xmpp.model.stanza.Message.Type.GROUPCHAT;
         final String pgpEncrypted = packet.findChildContent("x", "jabber:x:encrypted");
 
         final Element oob = packet.findChild("x", Namespace.OOB);
         final String oobUrl = oob != null ? oob.findChildContent("url") : null;
         final var replace = packet.getExtension(Replace.class);
         final var replacementId = replace == null ? null : replace.getId();
-        final Element axolotlEncrypted = packet.findChildEnsureSingle(XmppAxolotlMessage.CONTAINERTAG, AxolotlService.PEP_PREFIX);
+        final var axolotlEncrypted = packet.getOnlyExtension(Encrypted.class);
         int status;
         final Jid counterpart;
         final Jid to = packet.getTo();
@@ -482,7 +572,12 @@ public class MessageParser extends AbstractParser implements Consumer<im.convers
             return;
         }
         if (query != null && !query.muc() && isTypeGroupChat) {
-            Log.e(Config.LOGTAG, account.getJid().asBareJid() + ": received groupchat (" + from + ") message on regular MAM request. skipping");
+            Log.e(
+                    Config.LOGTAG,
+                    account.getJid().asBareJid()
+                            + ": received groupchat ("
+                            + from
+                            + ") message on regular MAM request. skipping");
             return;
         }
         final Jid mucTrueCounterPart;
@@ -511,7 +606,11 @@ public class MessageParser extends AbstractParser implements Consumer<im.convers
             mucTrueCounterPart = null;
             occupant = null;
         }
-        boolean isMucStatusMessage = InvalidJid.hasValidFrom(packet) && from.isBareJid() && mucUserElement != null && mucUserElement.hasChild("status");
+        boolean isMucStatusMessage =
+                InvalidJid.hasValidFrom(packet)
+                        && from.isBareJid()
+                        && mucUserElement != null
+                        && mucUserElement.hasChild("status");
         boolean selfAddressed;
         if (packet.fromAccount(account)) {
             status = Message.STATUS_SEND;
@@ -530,27 +629,60 @@ public class MessageParser extends AbstractParser implements Consumer<im.convers
         final Invite invite = extractInvite(packet);
         if (invite != null) {
             if (invite.jid.asBareJid().equals(account.getJid().asBareJid())) {
-                Log.d(Config.LOGTAG,account.getJid().asBareJid()+": ignore invite to "+invite.jid+" because it matches account");
+                Log.d(
+                        Config.LOGTAG,
+                        account.getJid().asBareJid()
+                                + ": ignore invite to "
+                                + invite.jid
+                                + " because it matches account");
             } else if (isTypeGroupChat) {
-                Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": ignoring invite to " + invite.jid + " because it was received as group chat");
-            } else if (invite.direct && (mucUserElement != null || invite.inviter == null || mXmppConnectionService.isMuc(account, invite.inviter))) {
-                Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": ignoring direct invite to " + invite.jid + " because it was received in MUC");
+                Log.d(
+                        Config.LOGTAG,
+                        account.getJid().asBareJid()
+                                + ": ignoring invite to "
+                                + invite.jid
+                                + " because it was received as group chat");
+            } else if (invite.direct
+                    && (mucUserElement != null
+                            || invite.inviter == null
+                            || mXmppConnectionService.isMuc(account, invite.inviter))) {
+                Log.d(
+                        Config.LOGTAG,
+                        account.getJid().asBareJid()
+                                + ": ignoring direct invite to "
+                                + invite.jid
+                                + " because it was received in MUC");
             } else {
                 invite.execute(account);
                 return;
             }
         }
 
-        if (reactions == null && (body != null || pgpEncrypted != null || (axolotlEncrypted != null && axolotlEncrypted.hasChild("payload")) || oobUrl != null) && !isMucStatusMessage) {
-            final boolean conversationIsProbablyMuc = isTypeGroupChat || mucUserElement != null || account.getXmppConnection().getMucServersWithholdAccount().contains(counterpart.getDomain().toEscapedString());
-            final Conversation conversation = mXmppConnectionService.findOrCreateConversation(account, counterpart.asBareJid(), conversationIsProbablyMuc, false, query, false);
+        if ((body != null
+                        || pgpEncrypted != null
+                        || (axolotlEncrypted != null && axolotlEncrypted.hasChild("payload"))
+                        || oobUrl != null)
+                && !isMucStatusMessage) {
+            final boolean conversationIsProbablyMuc =
+                    isTypeGroupChat
+                            || mucUserElement != null
+                            || account.getXmppConnection()
+                                    .getMucServersWithholdAccount()
+                                    .contains(counterpart.getDomain().toEscapedString());
+            final Conversation conversation =
+                    mXmppConnectionService.findOrCreateConversation(
+                            account,
+                            counterpart.asBareJid(),
+                            conversationIsProbablyMuc,
+                            false,
+                            query,
+                            false);
             final boolean conversationMultiMode = conversation.getMode() == Conversation.MODE_MULTI;
 
             if (serverMsgId == null) {
                 serverMsgId = extractStanzaId(packet, isTypeGroupChat, conversation);
             }
 
-
             if (selfAddressed) {
                 // don’t store serverMsgId on reflections for edits
                 final var reflectedServerMsgId =
@@ -563,7 +695,8 @@ public class MessageParser extends AbstractParser implements Consumer<im.convers
                     return;
                 }
                 status = Message.STATUS_RECEIVED;
-                if (remoteMsgId != null && conversation.findMessageWithRemoteId(remoteMsgId, counterpart) != null) {
+                if (remoteMsgId != null
+                        && conversation.findMessageWithRemoteId(remoteMsgId, counterpart) != null) {
                     return;
                 }
             }
@@ -571,11 +704,12 @@ public class MessageParser extends AbstractParser implements Consumer<im.convers
             if (isTypeGroupChat) {
                 if (conversation.getMucOptions().isSelf(counterpart)) {
                     status = Message.STATUS_SEND_RECEIVED;
-                    isCarbon = true; //not really carbon but received from another resource
+                    isCarbon = true; // not really carbon but received from another resource
                     // don’t store serverMsgId on reflections for edits
                     final var reflectedServerMsgId =
                             Strings.isNullOrEmpty(replacementId) ? serverMsgId : null;
-                    if (mXmppConnectionService.markMessage(conversation, remoteMsgId, status, reflectedServerMsgId, body)) {
+                    if (mXmppConnectionService.markMessage(
+                            conversation, remoteMsgId, status, reflectedServerMsgId, body)) {
                         return;
                     } else if (remoteMsgId == null || Config.IGNORE_ID_REWRITE_IN_MUC) {
                         if (body != null) {
@@ -597,17 +731,25 @@ public class MessageParser extends AbstractParser implements Consumer<im.convers
                 Jid origin;
                 Set<Jid> fallbacksBySourceId = Collections.emptySet();
                 if (conversationMultiMode) {
-                    final Jid fallback = conversation.getMucOptions().getTrueCounterpart(counterpart);
+                    final Jid fallback =
+                            conversation.getMucOptions().getTrueCounterpart(counterpart);
                     origin = getTrueCounterpart(query != null ? mucUserElement : null, fallback);
                     if (origin == null) {
                         try {
-                            fallbacksBySourceId = account.getAxolotlService().findCounterpartsBySourceId(XmppAxolotlMessage.parseSourceId(axolotlEncrypted));
+                            fallbacksBySourceId =
+                                    account.getAxolotlService()
+                                            .findCounterpartsBySourceId(
+                                                    XmppAxolotlMessage.parseSourceId(
+                                                            axolotlEncrypted));
                         } catch (IllegalArgumentException e) {
-                            //ignoring
+                            // ignoring
                         }
                     }
-                    if (origin == null && fallbacksBySourceId.size() == 0) {
-                        Log.d(Config.LOGTAG, "axolotl message in anonymous conference received and no possible fallbacks");
+                    if (origin == null && fallbacksBySourceId.isEmpty()) {
+                        Log.d(
+                                Config.LOGTAG,
+                                "axolotl message in anonymous conference received and no possible"
+                                        + " fallbacks");
                         return;
                     }
                 } else {
@@ -615,17 +757,40 @@ public class MessageParser extends AbstractParser implements Consumer<im.convers
                     origin = from;
                 }
 
-                final boolean liveMessage = query == null && !isTypeGroupChat && mucUserElement == null;
-                final boolean checkedForDuplicates = liveMessage || (serverMsgId != null && remoteMsgId != null && !conversation.possibleDuplicate(serverMsgId, remoteMsgId));
+                final boolean liveMessage =
+                        query == null && !isTypeGroupChat && mucUserElement == null;
+                final boolean checkedForDuplicates =
+                        liveMessage
+                                || (serverMsgId != null
+                                        && remoteMsgId != null
+                                        && !conversation.possibleDuplicate(
+                                                serverMsgId, remoteMsgId));
 
                 if (origin != null) {
-                    message = parseAxolotlChat(axolotlEncrypted, origin, conversation, status, checkedForDuplicates, query != null);
+                    message =
+                            parseAxolotlChat(
+                                    axolotlEncrypted,
+                                    origin,
+                                    conversation,
+                                    status,
+                                    checkedForDuplicates,
+                                    query != null);
                 } else {
                     Message trial = null;
                     for (Jid fallback : fallbacksBySourceId) {
-                        trial = parseAxolotlChat(axolotlEncrypted, fallback, conversation, status, checkedForDuplicates && fallbacksBySourceId.size() == 1, query != null);
+                        trial =
+                                parseAxolotlChat(
+                                        axolotlEncrypted,
+                                        fallback,
+                                        conversation,
+                                        status,
+                                        checkedForDuplicates && fallbacksBySourceId.size() == 1,
+                                        query != null);
                         if (trial != null) {
-                            Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": decoded muc message using fallback");
+                            Log.d(
+                                    Config.LOGTAG,
+                                    account.getJid().asBareJid()
+                                            + ": decoded muc message using fallback");
                             origin = fallback;
                             break;
                         }
@@ -633,15 +798,26 @@ public class MessageParser extends AbstractParser implements Consumer<im.convers
                     message = trial;
                 }
                 if (message == null) {
-                    if (query == null && extractChatState(mXmppConnectionService.find(account, counterpart.asBareJid()), isTypeGroupChat, packet)) {
+                    if (query == null
+                            && extractChatState(
+                                    mXmppConnectionService.find(account, counterpart.asBareJid()),
+                                    isTypeGroupChat,
+                                    packet)) {
                         mXmppConnectionService.updateConversationUi();
                     }
                     if (query != null && status == Message.STATUS_SEND && remoteMsgId != null) {
                         Message previouslySent = conversation.findSentMessageWithUuid(remoteMsgId);
-                        if (previouslySent != null && previouslySent.getServerMsgId() == null && serverMsgId != null) {
+                        if (previouslySent != null
+                                && previouslySent.getServerMsgId() == null
+                                && serverMsgId != null) {
                             previouslySent.setServerMsgId(serverMsgId);
-                            mXmppConnectionService.databaseBackend.updateMessage(previouslySent, false);
-                            Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": encountered previously sent OMEMO message without serverId. updating...");
+                            mXmppConnectionService.databaseBackend.updateMessage(
+                                    previouslySent, false);
+                            Log.d(
+                                    Config.LOGTAG,
+                                    account.getJid().asBareJid()
+                                            + ": encountered previously sent OMEMO message without"
+                                            + " serverId. updating...");
                         }
                     }
                     return;
@@ -691,7 +867,10 @@ public class MessageParser extends AbstractParser implements Consumer<im.convers
                 }
                 if (trueCounterpart != null && isTypeGroupChat) {
                     if (trueCounterpart.asBareJid().equals(account.getJid().asBareJid())) {
-                        status = isTypeGroupChat ? Message.STATUS_SEND_RECEIVED : Message.STATUS_SEND;
+                        status =
+                                isTypeGroupChat
+                                        ? Message.STATUS_SEND_RECEIVED
+                                        : Message.STATUS_SEND;
                     } else {
                         status = Message.STATUS_RECEIVED;
                         message.setCarbon(false);
@@ -707,24 +886,41 @@ public class MessageParser extends AbstractParser implements Consumer<im.convers
             }
 
             if (replacementId != null && mXmppConnectionService.allowMessageCorrection()) {
-                final Message replacedMessage = conversation.findMessageWithRemoteIdAndCounterpart(replacementId,
-                        counterpart,
-                        message.getStatus() == Message.STATUS_RECEIVED,
-                        message.isCarbon());
+                final Message replacedMessage =
+                        conversation.findMessageWithRemoteIdAndCounterpart(
+                                replacementId,
+                                counterpart,
+                                message.getStatus() == Message.STATUS_RECEIVED,
+                                message.isCarbon());
                 if (replacedMessage != null) {
-                    final boolean fingerprintsMatch = replacedMessage.getFingerprint() == null
-                            || replacedMessage.getFingerprint().equals(message.getFingerprint());
-                    final boolean trueCountersMatch = replacedMessage.getTrueCounterpart() != null
-                            && message.getTrueCounterpart() != null
-                            && replacedMessage.getTrueCounterpart().asBareJid().equals(message.getTrueCounterpart().asBareJid());
+                    final boolean fingerprintsMatch =
+                            replacedMessage.getFingerprint() == null
+                                    || replacedMessage
+                                            .getFingerprint()
+                                            .equals(message.getFingerprint());
+                    final boolean trueCountersMatch =
+                            replacedMessage.getTrueCounterpart() != null
+                                    && message.getTrueCounterpart() != null
+                                    && replacedMessage
+                                            .getTrueCounterpart()
+                                            .asBareJid()
+                                            .equals(message.getTrueCounterpart().asBareJid());
                     final boolean occupantIdMatch =
                             replacedMessage.getOccupantId() != null
                                     && replacedMessage
                                             .getOccupantId()
                                             .equals(message.getOccupantId());
-                    final boolean mucUserMatches = query == null && replacedMessage.sameMucUser(message); //can not be checked when using mam
+                    final boolean mucUserMatches =
+                            query == null
+                                    && replacedMessage.sameMucUser(
+                                            message); // can not be checked when using mam
                     final boolean duplicate = conversation.hasDuplicateMessage(message);
-                    if (fingerprintsMatch && (trueCountersMatch || occupantIdMatch || !conversationMultiMode || mucUserMatches) && !duplicate) {
+                    if (fingerprintsMatch
+                            && (trueCountersMatch
+                                    || occupantIdMatch
+                                    || !conversationMultiMode
+                                    || mucUserMatches)
+                            && !duplicate) {
                         synchronized (replacedMessage) {
                             final String uuid = replacedMessage.getUuid();
                             replacedMessage.setUuid(UUID.randomUUID().toString());
@@ -752,35 +948,58 @@ public class MessageParser extends AbstractParser implements Consumer<im.convers
                             mXmppConnectionService.updateMessage(replacedMessage, uuid);
                             if (mXmppConnectionService.confirmMessages()
                                     && replacedMessage.getStatus() == Message.STATUS_RECEIVED
-                                    && (replacedMessage.trusted() || replacedMessage.isPrivateMessage()) //TODO do we really want to send receipts for all PMs?
+                                    && (replacedMessage.trusted()
+                                            || replacedMessage
+                                                    .isPrivateMessage()) // TODO do we really want
+                                    // to send receipts for all
+                                    // PMs?
                                     && remoteMsgId != null
                                     && !selfAddressed
                                     && !isTypeGroupChat) {
                                 processMessageReceipts(account, packet, remoteMsgId, query);
                             }
                             if (replacedMessage.getEncryption() == Message.ENCRYPTION_PGP) {
-                                conversation.getAccount().getPgpDecryptionService().discard(replacedMessage);
-                                conversation.getAccount().getPgpDecryptionService().decrypt(replacedMessage, false);
+                                conversation
+                                        .getAccount()
+                                        .getPgpDecryptionService()
+                                        .discard(replacedMessage);
+                                conversation
+                                        .getAccount()
+                                        .getPgpDecryptionService()
+                                        .decrypt(replacedMessage, false);
                             }
                         }
                         mXmppConnectionService.getNotificationService().updateNotification();
                         return;
                     } else {
-                        Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": received message correction but verification didn't check out");
+                        Log.d(
+                                Config.LOGTAG,
+                                account.getJid().asBareJid()
+                                        + ": received message correction but verification didn't"
+                                        + " check out");
                     }
                 }
             }
 
             long deletionDate = mXmppConnectionService.getAutomaticMessageDeletionDate();
             if (deletionDate != 0 && message.getTimeSent() < deletionDate) {
-                Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": skipping message from " + message.getCounterpart().toString() + " because it was sent prior to our deletion date");
+                Log.d(
+                        Config.LOGTAG,
+                        account.getJid().asBareJid()
+                                + ": skipping message from "
+                                + message.getCounterpart().toString()
+                                + " because it was sent prior to our deletion date");
                 return;
             }
 
-            boolean checkForDuplicates = (isTypeGroupChat && packet.hasChild("delay", "urn:xmpp:delay"))
-                    || message.isPrivateMessage()
-                    || message.getServerMsgId() != null
-                    || (query == null && mXmppConnectionService.getMessageArchiveService().isCatchupInProgress(conversation));
+            boolean checkForDuplicates =
+                    (isTypeGroupChat && packet.hasChild("delay", "urn:xmpp:delay"))
+                            || message.isPrivateMessage()
+                            || message.getServerMsgId() != null
+                            || (query == null
+                                    && mXmppConnectionService
+                                            .getMessageArchiveService()
+                                            .isCatchupInProgress(conversation));
             if (checkForDuplicates) {
                 final Message duplicate = conversation.findDuplicateMessage(message);
                 if (duplicate != null) {
@@ -790,7 +1009,8 @@ public class MessageParser extends AbstractParser implements Consumer<im.convers
                             && duplicate.getServerMsgId() == null
                             && message.getServerMsgId() != null) {
                         duplicate.setServerMsgId(message.getServerMsgId());
-                        if (mXmppConnectionService.databaseBackend.updateMessage(duplicate, false)) {
+                        if (mXmppConnectionService.databaseBackend.updateMessage(
+                                duplicate, false)) {
                             serverMsgIdUpdated = true;
                         } else {
                             serverMsgIdUpdated = false;
@@ -799,12 +1019,18 @@ public class MessageParser extends AbstractParser implements Consumer<im.convers
                     } else {
                         serverMsgIdUpdated = false;
                     }
-                    Log.d(Config.LOGTAG, "skipping duplicate message with " + message.getCounterpart() + ". serverMsgIdUpdated=" + serverMsgIdUpdated);
+                    Log.d(
+                            Config.LOGTAG,
+                            "skipping duplicate message with "
+                                    + message.getCounterpart()
+                                    + ". serverMsgIdUpdated="
+                                    + serverMsgIdUpdated);
                     return;
                 }
             }
 
-            if (query != null && query.getPagingOrder() == MessageArchiveService.PagingOrder.REVERSE) {
+            if (query != null
+                    && query.getPagingOrder() == MessageArchiveService.PagingOrder.REVERSE) {
                 conversation.prepend(query.getActualInThisQuery(), message);
             } else {
                 conversation.add(message);
@@ -813,7 +1039,7 @@ public class MessageParser extends AbstractParser implements Consumer<im.convers
                 query.incrementActualMessageCount();
             }
 
-            if (query == null || query.isCatchup()) { //either no mam or catchup
+            if (query == null || query.isCatchup()) { // either no mam or catchup
                 if (status == Message.STATUS_SEND || status == Message.STATUS_SEND_RECEIVED) {
                     mXmppConnectionService.markRead(conversation);
                     if (query == null) {
@@ -826,13 +1052,21 @@ public class MessageParser extends AbstractParser implements Consumer<im.convers
             }
 
             if (message.getEncryption() == Message.ENCRYPTION_PGP) {
-                notify = conversation.getAccount().getPgpDecryptionService().decrypt(message, notify);
-            } else if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL_NOT_FOR_THIS_DEVICE || message.getEncryption() == Message.ENCRYPTION_AXOLOTL_FAILED) {
+                notify =
+                        conversation
+                                .getAccount()
+                                .getPgpDecryptionService()
+                                .decrypt(message, notify);
+            } else if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL_NOT_FOR_THIS_DEVICE
+                    || message.getEncryption() == Message.ENCRYPTION_AXOLOTL_FAILED) {
                 notify = false;
             }
 
             if (query == null) {
-                extractChatState(mXmppConnectionService.find(account, counterpart.asBareJid()), isTypeGroupChat, packet);
+                extractChatState(
+                        mXmppConnectionService.find(account, counterpart.asBareJid()),
+                        isTypeGroupChat,
+                        packet);
                 mXmppConnectionService.updateConversationUi();
             }
 
@@ -846,8 +1080,11 @@ public class MessageParser extends AbstractParser implements Consumer<im.convers
             }
 
             mXmppConnectionService.databaseBackend.createMessage(message);
-            final HttpConnectionManager manager = this.mXmppConnectionService.getHttpConnectionManager();
-            if (message.trusted() && message.treatAsDownloadable() && manager.getAutoAcceptFileSize() > 0) {
+            final HttpConnectionManager manager =
+                    this.mXmppConnectionService.getHttpConnectionManager();
+            if (message.trusted()
+                    && message.treatAsDownloadable()
+                    && manager.getAutoAcceptFileSize() > 0) {
                 manager.createNewDownloadConnection(message);
             } else if (notify) {
                 if (query != null && query.isCatchup()) {
@@ -856,16 +1093,20 @@ public class MessageParser extends AbstractParser implements Consumer<im.convers
                     mXmppConnectionService.getNotificationService().push(message);
                 }
             }
-        } else if (!packet.hasChild("body")) { //no body
+        } else if (!packet.hasChild("body")) { // no body
 
-            final Conversation conversation = mXmppConnectionService.find(account, from.asBareJid());
+            final Conversation conversation =
+                    mXmppConnectionService.find(account, from.asBareJid());
             if (axolotlEncrypted != null) {
                 Jid origin;
                 if (conversation != null && conversation.getMode() == Conversation.MODE_MULTI) {
-                    final Jid fallback = conversation.getMucOptions().getTrueCounterpart(counterpart);
+                    final Jid fallback =
+                            conversation.getMucOptions().getTrueCounterpart(counterpart);
                     origin = getTrueCounterpart(query != null ? mucUserElement : null, fallback);
                     if (origin == null) {
-                        Log.d(Config.LOGTAG, "omemo key transport message in anonymous conference received");
+                        Log.d(
+                                Config.LOGTAG,
+                                "omemo key transport message in anonymous conference received");
                         return;
                     }
                 } else if (isTypeGroupChat) {
@@ -874,25 +1115,43 @@ public class MessageParser extends AbstractParser implements Consumer<im.convers
                     origin = from;
                 }
                 try {
-                    final XmppAxolotlMessage xmppAxolotlMessage = XmppAxolotlMessage.fromElement(axolotlEncrypted, origin.asBareJid());
-                    account.getAxolotlService().processReceivingKeyTransportMessage(xmppAxolotlMessage, query != null);
-                    Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": omemo key transport message received from " + origin);
+                    final XmppAxolotlMessage xmppAxolotlMessage =
+                            XmppAxolotlMessage.fromElement(axolotlEncrypted, origin.asBareJid());
+                    account.getAxolotlService()
+                            .processReceivingKeyTransportMessage(xmppAxolotlMessage, query != null);
+                    Log.d(
+                            Config.LOGTAG,
+                            account.getJid().asBareJid()
+                                    + ": omemo key transport message received from "
+                                    + origin);
                 } catch (Exception e) {
-                    Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": invalid omemo key transport message received " + e.getMessage());
+                    Log.d(
+                            Config.LOGTAG,
+                            account.getJid().asBareJid()
+                                    + ": invalid omemo key transport message received "
+                                    + e.getMessage());
                     return;
                 }
             }
 
-            if (query == null && extractChatState(mXmppConnectionService.find(account, counterpart.asBareJid()), isTypeGroupChat, packet)) {
+            if (query == null
+                    && extractChatState(
+                            mXmppConnectionService.find(account, counterpart.asBareJid()),
+                            isTypeGroupChat,
+                            packet)) {
                 mXmppConnectionService.updateConversationUi();
             }
 
             if (isTypeGroupChat) {
-                if (packet.hasChild("subject") && !packet.hasChild("thread")) { // We already know it has no body per above
+                if (packet.hasChild("subject")
+                        && !packet.hasChild("thread")) { // We already know it has no body per above
                     if (conversation != null && conversation.getMode() == Conversation.MODE_MULTI) {
                         conversation.setHasMessagesLeftOnServer(conversation.countMessages() > 0);
-                        final LocalizedContent subject = packet.findInternationalizedChildContentInDefaultNamespace("subject");
-                        if (subject != null && conversation.getMucOptions().setSubject(subject.content)) {
+                        final LocalizedContent subject =
+                                packet.findInternationalizedChildContentInDefaultNamespace(
+                                        "subject");
+                        if (subject != null
+                                && conversation.getMucOptions().setSubject(subject.content)) {
                             mXmppConnectionService.updateConversation(conversation);
                         }
                         mXmppConnectionService.updateConversationUi();
@@ -900,7 +1159,10 @@ public class MessageParser extends AbstractParser implements Consumer<im.convers
                     }
                 }
             }
-            if (conversation != null && mucUserElement != null && InvalidJid.hasValidFrom(packet) && from.isBareJid()) {
+            if (conversation != null
+                    && mucUserElement != null
+                    && InvalidJid.hasValidFrom(packet)
+                    && from.isBareJid()) {
                 for (Element child : mucUserElement.getChildren()) {
                     if ("status".equals(child.getName())) {
                         try {
@@ -910,13 +1172,19 @@ public class MessageParser extends AbstractParser implements Consumer<im.convers
                                 break;
                             }
                         } catch (Exception e) {
-                            //ignored
+                            // ignored
                         }
                     } else if ("item".equals(child.getName())) {
                         MucOptions.User user = AbstractParser.parseItem(conversation, child);
-                        Log.d(Config.LOGTAG, account.getJid() + ": changing affiliation for "
-                                + user.getRealJid() + " to " + user.getAffiliation() + " in "
-                                + conversation.getJid().asBareJid());
+                        Log.d(
+                                Config.LOGTAG,
+                                account.getJid()
+                                        + ": changing affiliation for "
+                                        + user.getRealJid()
+                                        + " to "
+                                        + user.getAffiliation()
+                                        + " in "
+                                        + conversation.getJid().asBareJid());
                         if (!user.realJidMatchesAccount()) {
                             boolean isNew = conversation.getMucOptions().updateUser(user);
                             mXmppConnectionService.getAvatarService().clear(conversation);
@@ -927,7 +1195,13 @@ public class MessageParser extends AbstractParser implements Consumer<im.convers
                                 Jid jid = user.getRealJid();
                                 List<Jid> cryptoTargets = conversation.getAcceptedCryptoTargets();
                                 if (cryptoTargets.remove(user.getRealJid())) {
-                                    Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": removed " + jid + " from crypto targets of " + conversation.getName());
+                                    Log.d(
+                                            Config.LOGTAG,
+                                            account.getJid().asBareJid()
+                                                    + ": removed "
+                                                    + jid
+                                                    + " from crypto targets of "
+                                                    + conversation.getName());
                                     conversation.setAcceptedCryptoTargets(cryptoTargets);
                                     mXmppConnectionService.updateConversation(conversation);
                                 }
@@ -935,7 +1209,8 @@ public class MessageParser extends AbstractParser implements Consumer<im.convers
                                     && user.getRealJid() != null
                                     && conversation.getMucOptions().isPrivateAndNonAnonymous()
                                     && (contact == null || !contact.mutualPresenceSubscription())
-                                    && account.getAxolotlService().hasEmptyDeviceList(user.getRealJid())) {
+                                    && account.getAxolotlService()
+                                            .hasEmptyDeviceList(user.getRealJid())) {
                                 account.getAxolotlService().fetchDeviceIds(user.getRealJid());
                             }
                         }
@@ -944,7 +1219,8 @@ public class MessageParser extends AbstractParser implements Consumer<im.convers
             }
             if (!isTypeGroupChat) {
                 for (Element child : packet.getChildren()) {
-                    if (Namespace.JINGLE_MESSAGE.equals(child.getNamespace()) && JINGLE_MESSAGE_ELEMENT_NAMES.contains(child.getName())) {
+                    if (Namespace.JINGLE_MESSAGE.equals(child.getNamespace())
+                            && JINGLE_MESSAGE_ELEMENT_NAMES.contains(child.getName())) {
                         final String action = child.getName();
                         final String sessionId = child.getAttribute("id");
                         if (sessionId == null) {
@@ -974,7 +1250,8 @@ public class MessageParser extends AbstractParser implements Consumer<im.convers
                             if (remoteMsgId != null && !contact.isSelf() && sendReceipts) {
                                 processMessageReceipts(account, packet, remoteMsgId, null);
                             }
-                        } else if ((query != null && query.isCatchup()) || !offlineMessagesRetrieved) {
+                        } else if ((query != null && query.isCatchup())
+                                || !offlineMessagesRetrieved) {
                             if ("propose".equals(action)) {
                                 final Element description = child.findChild("description");
                                 final String namespace =
@@ -1019,37 +1296,41 @@ public class MessageParser extends AbstractParser implements Consumer<im.convers
                                 } else {
                                     Log.d(
                                             Config.LOGTAG,
-                                            "unable to find original rtp session message for received propose");
+                                            "unable to find original rtp session message for"
+                                                    + " received propose");
                                 }
 
                             } else if ("finish".equals(action)) {
                                 Log.d(
                                         Config.LOGTAG,
-                                        "received JMI 'finish' during MAM catch-up. Can be used to update success/failure and duration");
+                                        "received JMI 'finish' during MAM catch-up. Can be used to"
+                                                + " update success/failure and duration");
                             }
                         } else {
-                            //MAM reloads (non catchups
+                            // MAM reloads (non catchups
                             if ("propose".equals(action)) {
                                 final Element description = child.findChild("description");
-                                final String namespace = description == null ? null : description.getNamespace();
+                                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 preExistingMessage = c.findRtpSession(sessionId, status);
+                                    final Conversation c =
+                                            mXmppConnectionService.findOrCreateConversation(
+                                                    account, counterpart.asBareJid(), false, false);
+                                    final Message preExistingMessage =
+                                            c.findRtpSession(sessionId, status);
                                     if (preExistingMessage != null) {
                                         preExistingMessage.setServerMsgId(serverMsgId);
                                         mXmppConnectionService.updateMessage(preExistingMessage);
                                         break;
                                     }
-                                    final Message message = new Message(
-                                            c,
-                                            status,
-                                            Message.TYPE_RTP_SESSION,
-                                            sessionId
-                                    );
+                                    final Message message =
+                                            new Message(
+                                                    c, status, Message.TYPE_RTP_SESSION, sessionId);
                                     message.setServerMsgId(serverMsgId);
                                     message.setTime(timestamp);
                                     message.setBody(new RtpSessionStatus(true, 0).toString());
-                                    if (query.getPagingOrder() == MessageArchiveService.PagingOrder.REVERSE) {
+                                    if (query.getPagingOrder()
+                                            == MessageArchiveService.PagingOrder.REVERSE) {
                                         c.prepend(query.getActualInThisQuery(), message);
                                     } else {
                                         c.add(message);

src/main/java/im/conversations/android/xmpp/model/Extension.java 🔗

@@ -3,11 +3,8 @@ package im.conversations.android.xmpp.model;
 import com.google.common.base.Preconditions;
 import com.google.common.collect.Collections2;
 import com.google.common.collect.Iterables;
-
 import eu.siacs.conversations.xml.Element;
-
 import im.conversations.android.xmpp.ExtensionFactory;
-
 import java.util.Collection;
 
 public class Extension extends Element {
@@ -39,6 +36,14 @@ public class Extension extends Element {
         return clazz.cast(extension);
     }
 
+    public <E extends Extension> E getOnlyExtension(final Class<E> clazz) {
+        final var extensions = getExtensions(clazz);
+        if (extensions.size() == 1) {
+            return Iterables.getOnlyElement(extensions);
+        }
+        return null;
+    }
+
     public <E extends Extension> Collection<E> getExtensions(final Class<E> clazz) {
         return Collections2.transform(
                 Collections2.filter(this.children, clazz::isInstance), clazz::cast);