MessageParser.java

   1package eu.siacs.conversations.parser;
   2
   3import android.util.Log;
   4import android.util.Pair;
   5import androidx.annotation.NonNull;
   6import com.google.common.base.Strings;
   7import com.google.common.collect.ImmutableSet;
   8import eu.siacs.conversations.AppSettings;
   9import eu.siacs.conversations.Config;
  10import eu.siacs.conversations.R;
  11import eu.siacs.conversations.crypto.axolotl.AxolotlService;
  12import eu.siacs.conversations.crypto.axolotl.BrokenSessionException;
  13import eu.siacs.conversations.crypto.axolotl.NotEncryptedForThisDeviceException;
  14import eu.siacs.conversations.crypto.axolotl.OutdatedSenderException;
  15import eu.siacs.conversations.crypto.axolotl.XmppAxolotlMessage;
  16import eu.siacs.conversations.entities.Account;
  17import eu.siacs.conversations.entities.Bookmark;
  18import eu.siacs.conversations.entities.Contact;
  19import eu.siacs.conversations.entities.Conversation;
  20import eu.siacs.conversations.entities.Conversational;
  21import eu.siacs.conversations.entities.Message;
  22import eu.siacs.conversations.entities.MucOptions;
  23import eu.siacs.conversations.entities.Reaction;
  24import eu.siacs.conversations.entities.ReadByMarker;
  25import eu.siacs.conversations.entities.ReceiptRequest;
  26import eu.siacs.conversations.entities.RtpSessionStatus;
  27import eu.siacs.conversations.http.HttpConnectionManager;
  28import eu.siacs.conversations.services.MessageArchiveService;
  29import eu.siacs.conversations.services.QuickConversationsService;
  30import eu.siacs.conversations.services.XmppConnectionService;
  31import eu.siacs.conversations.utils.CryptoHelper;
  32import eu.siacs.conversations.xml.Element;
  33import eu.siacs.conversations.xml.LocalizedContent;
  34import eu.siacs.conversations.xml.Namespace;
  35import eu.siacs.conversations.xmpp.Jid;
  36import eu.siacs.conversations.xmpp.XmppConnection;
  37import eu.siacs.conversations.xmpp.chatstate.ChatState;
  38import eu.siacs.conversations.xmpp.jingle.JingleConnectionManager;
  39import eu.siacs.conversations.xmpp.jingle.JingleRtpConnection;
  40import eu.siacs.conversations.xmpp.manager.RosterManager;
  41import eu.siacs.conversations.xmpp.pep.Avatar;
  42import im.conversations.android.xmpp.model.Extension;
  43import im.conversations.android.xmpp.model.avatar.Metadata;
  44import im.conversations.android.xmpp.model.axolotl.DeviceList;
  45import im.conversations.android.xmpp.model.axolotl.Encrypted;
  46import im.conversations.android.xmpp.model.bookmark.Storage;
  47import im.conversations.android.xmpp.model.bookmark2.Conference;
  48import im.conversations.android.xmpp.model.carbons.Received;
  49import im.conversations.android.xmpp.model.carbons.Sent;
  50import im.conversations.android.xmpp.model.correction.Replace;
  51import im.conversations.android.xmpp.model.forward.Forwarded;
  52import im.conversations.android.xmpp.model.markers.Displayed;
  53import im.conversations.android.xmpp.model.nick.Nick;
  54import im.conversations.android.xmpp.model.occupant.OccupantId;
  55import im.conversations.android.xmpp.model.oob.OutOfBandData;
  56import im.conversations.android.xmpp.model.pubsub.Items;
  57import im.conversations.android.xmpp.model.pubsub.event.Delete;
  58import im.conversations.android.xmpp.model.pubsub.event.Event;
  59import im.conversations.android.xmpp.model.pubsub.event.Purge;
  60import im.conversations.android.xmpp.model.reactions.Reactions;
  61import im.conversations.android.xmpp.model.receipts.Request;
  62import im.conversations.android.xmpp.model.unique.StanzaId;
  63import java.text.SimpleDateFormat;
  64import java.util.Arrays;
  65import java.util.Collections;
  66import java.util.Date;
  67import java.util.HashSet;
  68import java.util.List;
  69import java.util.Locale;
  70import java.util.Map;
  71import java.util.Set;
  72import java.util.UUID;
  73import java.util.function.Consumer;
  74
  75public class MessageParser extends AbstractParser
  76        implements Consumer<im.conversations.android.xmpp.model.stanza.Message> {
  77
  78    private static final SimpleDateFormat TIME_FORMAT =
  79            new SimpleDateFormat("HH:mm:ss", Locale.ENGLISH);
  80
  81    private static final List<String> JINGLE_MESSAGE_ELEMENT_NAMES =
  82            Arrays.asList("accept", "propose", "proceed", "reject", "retract", "ringing", "finish");
  83
  84    public MessageParser(final XmppConnectionService service, final XmppConnection connection) {
  85        super(service, connection);
  86    }
  87
  88    private static String extractStanzaId(
  89            final im.conversations.android.xmpp.model.stanza.Message packet,
  90            final boolean isTypeGroupChat,
  91            final Conversation conversation) {
  92        final Jid by;
  93        final boolean safeToExtract;
  94        if (isTypeGroupChat) {
  95            by = conversation.getJid().asBareJid();
  96            safeToExtract = conversation.getMucOptions().hasFeature(Namespace.STANZA_IDS);
  97        } else {
  98            Account account = conversation.getAccount();
  99            by = account.getJid().asBareJid();
 100            safeToExtract = account.getXmppConnection().getFeatures().stanzaIds();
 101        }
 102        return safeToExtract ? StanzaId.get(packet, by) : null;
 103    }
 104
 105    private static String extractStanzaId(
 106            final Account account,
 107            final im.conversations.android.xmpp.model.stanza.Message packet) {
 108        final boolean safeToExtract = account.getXmppConnection().getFeatures().stanzaIds();
 109        return safeToExtract ? StanzaId.get(packet, account.getJid().asBareJid()) : null;
 110    }
 111
 112    private static Jid getTrueCounterpart(Element mucUserElement, Jid fallback) {
 113        final Element item = mucUserElement == null ? null : mucUserElement.findChild("item");
 114        Jid result =
 115                item == null ? null : Jid.Invalid.getNullForInvalid(item.getAttributeAsJid("jid"));
 116        return result != null ? result : fallback;
 117    }
 118
 119    private boolean extractChatState(
 120            Conversation c,
 121            final boolean isTypeGroupChat,
 122            final im.conversations.android.xmpp.model.stanza.Message packet) {
 123        ChatState state = ChatState.parse(packet);
 124        if (state != null && c != null) {
 125            final Account account = c.getAccount();
 126            final Jid from = packet.getFrom();
 127            if (from.asBareJid().equals(account.getJid().asBareJid())) {
 128                c.setOutgoingChatState(state);
 129                if (state == ChatState.ACTIVE || state == ChatState.COMPOSING) {
 130                    if (c.getContact().isSelf()) {
 131                        return false;
 132                    }
 133                    mXmppConnectionService.markRead(c);
 134                    activateGracePeriod(account);
 135                }
 136                return false;
 137            } else {
 138                if (isTypeGroupChat) {
 139                    MucOptions.User user = c.getMucOptions().findUserByFullJid(from);
 140                    if (user != null) {
 141                        return user.setChatState(state);
 142                    } else {
 143                        return false;
 144                    }
 145                } else {
 146                    return c.setIncomingChatState(state);
 147                }
 148            }
 149        }
 150        return false;
 151    }
 152
 153    private Message parseAxolotlChat(
 154            final Encrypted axolotlMessage,
 155            final Jid from,
 156            final Conversation conversation,
 157            final int status,
 158            final boolean checkedForDuplicates,
 159            final boolean postpone) {
 160        final AxolotlService service = conversation.getAccount().getAxolotlService();
 161        final XmppAxolotlMessage xmppAxolotlMessage;
 162        try {
 163            xmppAxolotlMessage = XmppAxolotlMessage.fromElement(axolotlMessage, from.asBareJid());
 164        } catch (final Exception e) {
 165            Log.d(
 166                    Config.LOGTAG,
 167                    conversation.getAccount().getJid().asBareJid()
 168                            + ": invalid omemo message received "
 169                            + e.getMessage());
 170            return null;
 171        }
 172        if (xmppAxolotlMessage.hasPayload()) {
 173            final XmppAxolotlMessage.XmppAxolotlPlaintextMessage plaintextMessage;
 174            try {
 175                plaintextMessage =
 176                        service.processReceivingPayloadMessage(xmppAxolotlMessage, postpone);
 177            } catch (BrokenSessionException e) {
 178                if (checkedForDuplicates) {
 179                    if (service.trustedOrPreviouslyResponded(from.asBareJid())) {
 180                        service.reportBrokenSessionException(e, postpone);
 181                        return new Message(
 182                                conversation, "", Message.ENCRYPTION_AXOLOTL_FAILED, status);
 183                    } else {
 184                        Log.d(
 185                                Config.LOGTAG,
 186                                "ignoring broken session exception because contact was not"
 187                                        + " trusted");
 188                        return new Message(
 189                                conversation, "", Message.ENCRYPTION_AXOLOTL_FAILED, status);
 190                    }
 191                } else {
 192                    Log.d(
 193                            Config.LOGTAG,
 194                            "ignoring broken session exception because checkForDuplicates failed");
 195                    return null;
 196                }
 197            } catch (NotEncryptedForThisDeviceException e) {
 198                return new Message(
 199                        conversation, "", Message.ENCRYPTION_AXOLOTL_NOT_FOR_THIS_DEVICE, status);
 200            } catch (OutdatedSenderException e) {
 201                return new Message(conversation, "", Message.ENCRYPTION_AXOLOTL_FAILED, status);
 202            }
 203            if (plaintextMessage != null) {
 204                Message finishedMessage =
 205                        new Message(
 206                                conversation,
 207                                plaintextMessage.getPlaintext(),
 208                                Message.ENCRYPTION_AXOLOTL,
 209                                status);
 210                finishedMessage.setFingerprint(plaintextMessage.getFingerprint());
 211                Log.d(
 212                        Config.LOGTAG,
 213                        AxolotlService.getLogprefix(finishedMessage.getConversation().getAccount())
 214                                + " Received Message with session fingerprint: "
 215                                + plaintextMessage.getFingerprint());
 216                return finishedMessage;
 217            }
 218        } else {
 219            Log.d(
 220                    Config.LOGTAG,
 221                    conversation.getAccount().getJid().asBareJid()
 222                            + ": received OMEMO key transport message");
 223            service.processReceivingKeyTransportMessage(xmppAxolotlMessage, postpone);
 224        }
 225        return null;
 226    }
 227
 228    private Invite extractInvite(final Element message) {
 229        final Element mucUser = message.findChild("x", Namespace.MUC_USER);
 230        if (mucUser != null) {
 231            final Element invite = mucUser.findChild("invite");
 232            if (invite != null) {
 233                final String password = mucUser.findChildContent("password");
 234                final Jid from = Jid.Invalid.getNullForInvalid(invite.getAttributeAsJid("from"));
 235                final Jid to = Jid.Invalid.getNullForInvalid(invite.getAttributeAsJid("to"));
 236                if (to != null && from == null) {
 237                    Log.d(Config.LOGTAG, "do not parse outgoing mediated invite " + message);
 238                    return null;
 239                }
 240                final Jid room = Jid.Invalid.getNullForInvalid(message.getAttributeAsJid("from"));
 241                if (room == null) {
 242                    return null;
 243                }
 244                return new Invite(room, password, false, from);
 245            }
 246        }
 247        final Element conference = message.findChild("x", "jabber:x:conference");
 248        if (conference != null) {
 249            Jid from = Jid.Invalid.getNullForInvalid(message.getAttributeAsJid("from"));
 250            Jid room = Jid.Invalid.getNullForInvalid(conference.getAttributeAsJid("jid"));
 251            if (room == null) {
 252                return null;
 253            }
 254            return new Invite(room, conference.getAttribute("password"), true, from);
 255        }
 256        return null;
 257    }
 258
 259    private void parseEvent(final Items items, final Jid from, final Account account) {
 260        final String node = items.getNode();
 261        if ("urn:xmpp:avatar:metadata".equals(node)) {
 262            // TODO support retract
 263            final var entry = items.getFirstItemWithId(Metadata.class);
 264            final var avatar =
 265                    entry == null ? null : Avatar.parseMetadata(entry.getKey(), entry.getValue());
 266            if (avatar != null) {
 267                avatar.owner = from.asBareJid();
 268                if (mXmppConnectionService.getFileBackend().isAvatarCached(avatar)) {
 269                    if (account.getJid().asBareJid().equals(from)) {
 270                        if (account.setAvatar(avatar.getFilename())) {
 271                            mXmppConnectionService.databaseBackend.updateAccount(account);
 272                            mXmppConnectionService.notifyAccountAvatarHasChanged(account);
 273                        }
 274                        mXmppConnectionService.getAvatarService().clear(account);
 275                        mXmppConnectionService.updateConversationUi();
 276                        mXmppConnectionService.updateAccountUi();
 277                    } else {
 278                        final Contact contact = account.getRoster().getContact(from);
 279                        if (contact.setAvatar(avatar)) {
 280                            connection.getManager(RosterManager.class).writeToDatabaseAsync();
 281                            mXmppConnectionService.getAvatarService().clear(contact);
 282                            mXmppConnectionService.updateConversationUi();
 283                            mXmppConnectionService.updateRosterUi();
 284                        }
 285                    }
 286                } else if (mXmppConnectionService.isDataSaverDisabled()) {
 287                    mXmppConnectionService.fetchAvatar(account, avatar);
 288                }
 289            }
 290        } else if (Namespace.NICK.equals(node)) {
 291            final var nickItem = items.getFirstItem(Nick.class);
 292            final String nick = nickItem == null ? null : nickItem.getContent();
 293            if (nick != null) {
 294                setNick(account, from, nick);
 295            }
 296        } else if (AxolotlService.PEP_DEVICE_LIST.equals(node)) {
 297            final var deviceList = items.getFirstItem(DeviceList.class);
 298            if (deviceList != null) {
 299                final Set<Integer> deviceIds = deviceList.getDeviceIds();
 300                Log.d(
 301                        Config.LOGTAG,
 302                        AxolotlService.getLogprefix(account)
 303                                + "Received PEP device list "
 304                                + deviceIds
 305                                + " update from "
 306                                + from
 307                                + ", processing... ");
 308                final AxolotlService axolotlService = account.getAxolotlService();
 309                axolotlService.registerDevices(from, new HashSet<>(deviceIds));
 310            }
 311
 312        } else if (Namespace.BOOKMARKS.equals(node) && account.getJid().asBareJid().equals(from)) {
 313            final var connection = account.getXmppConnection();
 314            if (connection.getFeatures().bookmarksConversion()) {
 315                if (connection.getFeatures().bookmarks2()) {
 316                    Log.w(
 317                            Config.LOGTAG,
 318                            account.getJid().asBareJid()
 319                                    + ": received storage:bookmark notification even though we"
 320                                    + " opted into bookmarks:1");
 321                }
 322                final var storage = items.getFirstItem(Storage.class);
 323                final Map<Jid, Bookmark> bookmarks = Bookmark.parseFromStorage(storage, account);
 324                mXmppConnectionService.processBookmarksInitial(account, bookmarks, true);
 325                Log.d(
 326                        Config.LOGTAG,
 327                        account.getJid().asBareJid() + ": processing bookmark PEP event");
 328            } else {
 329                Log.d(
 330                        Config.LOGTAG,
 331                        account.getJid().asBareJid()
 332                                + ": ignoring bookmark PEP event because bookmark conversion was"
 333                                + " not detected");
 334            }
 335        } else if (Namespace.BOOKMARKS2.equals(node) && account.getJid().asBareJid().equals(from)) {
 336            final var retractions = items.getRetractions();
 337            for (final var item : items.getItemMap(Conference.class).entrySet()) {
 338                final Bookmark bookmark =
 339                        Bookmark.parseFromItem(item.getKey(), item.getValue(), account);
 340                if (bookmark == null) {
 341                    continue;
 342                }
 343                account.putBookmark(bookmark);
 344                mXmppConnectionService.processModifiedBookmark(bookmark);
 345                mXmppConnectionService.updateConversationUi();
 346            }
 347            for (final var retract : retractions) {
 348                final Jid id = Jid.Invalid.getNullForInvalid(retract.getAttributeAsJid("id"));
 349                if (id != null) {
 350                    account.removeBookmark(id);
 351                    Log.d(
 352                            Config.LOGTAG,
 353                            account.getJid().asBareJid() + ": deleted bookmark for " + id);
 354                    mXmppConnectionService.processDeletedBookmark(account, id);
 355                    mXmppConnectionService.updateConversationUi();
 356                }
 357            }
 358        } else if (Config.MESSAGE_DISPLAYED_SYNCHRONIZATION
 359                && Namespace.MDS_DISPLAYED.equals(node)
 360                && account.getJid().asBareJid().equals(from)) {
 361            for (final var item :
 362                    items.getItemMap(im.conversations.android.xmpp.model.mds.Displayed.class)
 363                            .entrySet()) {
 364                mXmppConnectionService.processMdsItem(account, item);
 365            }
 366        }
 367    }
 368
 369    private void parseDeleteEvent(final Delete delete, final Jid from, final Account account) {
 370        final String node = delete.getNode();
 371        if (Namespace.NICK.equals(node)) {
 372            setNick(account, from, null);
 373        } else if (Namespace.BOOKMARKS2.equals(node) && account.getJid().asBareJid().equals(from)) {
 374            Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": deleted bookmarks node");
 375            deleteAllBookmarks(account);
 376        } else if (Namespace.AVATAR_METADATA.equals(node)) {
 377            final boolean isAccount = account.getJid().asBareJid().equals(from);
 378            if (isAccount) {
 379                account.setAvatar(null);
 380                mXmppConnectionService.databaseBackend.updateAccount(account);
 381                mXmppConnectionService.getAvatarService().clear(account);
 382                Log.d(
 383                        Config.LOGTAG,
 384                        account.getJid().asBareJid() + ": deleted avatar metadata node");
 385            }
 386        }
 387    }
 388
 389    private void parsePurgeEvent(
 390            @NonNull final Purge purge, final Jid from, final Account account) {
 391        final String node = purge.getNode();
 392        if (Namespace.BOOKMARKS2.equals(node) && account.getJid().asBareJid().equals(from)) {
 393            Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": purged bookmarks");
 394            deleteAllBookmarks(account);
 395        }
 396    }
 397
 398    private void deleteAllBookmarks(final Account account) {
 399        final var previous = account.getBookmarkedJids();
 400        account.setBookmarks(Collections.emptyMap());
 401        mXmppConnectionService.processDeletedBookmarks(account, previous);
 402    }
 403
 404    private void setNick(final Account account, final Jid user, final String nick) {
 405        if (user.asBareJid().equals(account.getJid().asBareJid())) {
 406            account.setDisplayName(nick);
 407            if (QuickConversationsService.isQuicksy()) {
 408                mXmppConnectionService.getAvatarService().clear(account);
 409            }
 410            mXmppConnectionService.checkMucRequiresRename();
 411        } else {
 412            Contact contact = account.getRoster().getContact(user);
 413            if (contact.setPresenceName(nick)) {
 414                connection.getManager(RosterManager.class).writeToDatabaseAsync();
 415                mXmppConnectionService.getAvatarService().clear(contact);
 416            }
 417        }
 418        mXmppConnectionService.updateConversationUi();
 419        mXmppConnectionService.updateAccountUi();
 420    }
 421
 422    private boolean handleErrorMessage(
 423            final Account account,
 424            final im.conversations.android.xmpp.model.stanza.Message packet) {
 425        if (packet.getType() == im.conversations.android.xmpp.model.stanza.Message.Type.ERROR) {
 426            if (packet.fromServer(account)) {
 427                final var forwarded =
 428                        getForwardedMessagePacket(packet, "received", Namespace.CARBONS);
 429                if (forwarded != null) {
 430                    return handleErrorMessage(account, forwarded.first);
 431                }
 432            }
 433            final Jid from = packet.getFrom();
 434            final String id = packet.getId();
 435            if (from != null && id != null) {
 436                if (id.startsWith(JingleRtpConnection.JINGLE_MESSAGE_PROPOSE_ID_PREFIX)) {
 437                    final String sessionId =
 438                            id.substring(
 439                                    JingleRtpConnection.JINGLE_MESSAGE_PROPOSE_ID_PREFIX.length());
 440                    mXmppConnectionService
 441                            .getJingleConnectionManager()
 442                            .updateProposedSessionDiscovered(
 443                                    account,
 444                                    from,
 445                                    sessionId,
 446                                    JingleConnectionManager.DeviceDiscoveryState.FAILED);
 447                    return true;
 448                }
 449                if (id.startsWith(JingleRtpConnection.JINGLE_MESSAGE_PROCEED_ID_PREFIX)) {
 450                    final String sessionId =
 451                            id.substring(
 452                                    JingleRtpConnection.JINGLE_MESSAGE_PROCEED_ID_PREFIX.length());
 453                    final String message = extractErrorMessage(packet);
 454                    mXmppConnectionService
 455                            .getJingleConnectionManager()
 456                            .failProceed(account, from, sessionId, message);
 457                    return true;
 458                }
 459                mXmppConnectionService.markMessage(
 460                        account,
 461                        from.asBareJid(),
 462                        id,
 463                        Message.STATUS_SEND_FAILED,
 464                        extractErrorMessage(packet));
 465                final Element error = packet.findChild("error");
 466                final boolean pingWorthyError =
 467                        error != null
 468                                && (error.hasChild("not-acceptable")
 469                                        || error.hasChild("remote-server-timeout")
 470                                        || error.hasChild("remote-server-not-found"));
 471                if (pingWorthyError) {
 472                    Conversation conversation = mXmppConnectionService.find(account, from);
 473                    if (conversation != null
 474                            && conversation.getMode() == Conversational.MODE_MULTI) {
 475                        if (conversation.getMucOptions().online()) {
 476                            Log.d(
 477                                    Config.LOGTAG,
 478                                    account.getJid().asBareJid()
 479                                            + ": received ping worthy error for seemingly online"
 480                                            + " muc at "
 481                                            + from);
 482                            mXmppConnectionService.mucSelfPingAndRejoin(conversation);
 483                        }
 484                    }
 485                }
 486            }
 487            return true;
 488        }
 489        return false;
 490    }
 491
 492    @Override
 493    public void accept(final im.conversations.android.xmpp.model.stanza.Message original) {
 494        final var account = connection.getAccount();
 495        if (handleErrorMessage(account, original)) {
 496            return;
 497        }
 498        final im.conversations.android.xmpp.model.stanza.Message packet;
 499        Long timestamp = null;
 500        boolean isCarbon = false;
 501        String serverMsgId = null;
 502        final Element fin =
 503                original.findChild("fin", MessageArchiveService.Version.MAM_0.namespace);
 504        if (fin != null) {
 505            mXmppConnectionService
 506                    .getMessageArchiveService()
 507                    .processFinLegacy(fin, original.getFrom());
 508            return;
 509        }
 510        final Element result = MessageArchiveService.Version.findResult(original);
 511        final String queryId = result == null ? null : result.getAttribute("queryid");
 512        final MessageArchiveService.Query query =
 513                queryId == null
 514                        ? null
 515                        : mXmppConnectionService.getMessageArchiveService().findQuery(queryId);
 516        final boolean offlineMessagesRetrieved = connection.isOfflineMessagesRetrieved();
 517        if (query != null && query.validFrom(original.getFrom())) {
 518            final var f = getForwardedMessagePacket(original, "result", query.version.namespace);
 519            if (f == null) {
 520                return;
 521            }
 522            timestamp = f.second;
 523            packet = f.first;
 524            serverMsgId = result.getAttribute("id");
 525            query.incrementMessageCount();
 526            if (handleErrorMessage(account, packet)) {
 527                return;
 528            }
 529        } else if (query != null) {
 530            Log.d(
 531                    Config.LOGTAG,
 532                    account.getJid().asBareJid()
 533                            + ": received mam result with invalid from ("
 534                            + original.getFrom()
 535                            + ") or queryId ("
 536                            + queryId
 537                            + ")");
 538            return;
 539        } else if (original.fromServer(account)
 540                && original.getType()
 541                        != im.conversations.android.xmpp.model.stanza.Message.Type.GROUPCHAT) {
 542            Pair<im.conversations.android.xmpp.model.stanza.Message, Long> f;
 543            f = getForwardedMessagePacket(original, Received.class);
 544            f = f == null ? getForwardedMessagePacket(original, Sent.class) : f;
 545            packet = f != null ? f.first : original;
 546            if (handleErrorMessage(account, packet)) {
 547                return;
 548            }
 549            timestamp = f != null ? f.second : null;
 550            isCarbon = f != null;
 551        } else {
 552            packet = original;
 553        }
 554
 555        if (timestamp == null) {
 556            timestamp =
 557                    AbstractParser.parseTimestamp(original, AbstractParser.parseTimestamp(packet));
 558        }
 559        final LocalizedContent body = packet.getBody();
 560        final Element mucUserElement = packet.findChild("x", Namespace.MUC_USER);
 561        final boolean isTypeGroupChat =
 562                packet.getType()
 563                        == im.conversations.android.xmpp.model.stanza.Message.Type.GROUPCHAT;
 564        final var encrypted =
 565                packet.getOnlyExtension(im.conversations.android.xmpp.model.pgp.Encrypted.class);
 566        final String pgpEncrypted = encrypted == null ? null : encrypted.getContent();
 567
 568        final var oob = packet.getExtension(OutOfBandData.class);
 569        final String oobUrl = oob != null ? oob.getURL() : null;
 570        final var replace = packet.getExtension(Replace.class);
 571        final var replacementId = replace == null ? null : replace.getId();
 572        final var axolotlEncrypted = packet.getOnlyExtension(Encrypted.class);
 573        int status;
 574        final Jid counterpart;
 575        final Jid to = packet.getTo();
 576        final Jid from = packet.getFrom();
 577        final Element originId = packet.findChild("origin-id", Namespace.STANZA_IDS);
 578        final String remoteMsgId;
 579        if (originId != null && originId.getAttribute("id") != null) {
 580            remoteMsgId = originId.getAttribute("id");
 581        } else {
 582            remoteMsgId = packet.getId();
 583        }
 584        boolean notify = false;
 585
 586        if (from == null || !Jid.Invalid.isValid(from) || !Jid.Invalid.isValid(to)) {
 587            Log.e(Config.LOGTAG, "encountered invalid message from='" + from + "' to='" + to + "'");
 588            return;
 589        }
 590        if (query != null && !query.muc() && isTypeGroupChat) {
 591            Log.e(
 592                    Config.LOGTAG,
 593                    account.getJid().asBareJid()
 594                            + ": received groupchat ("
 595                            + from
 596                            + ") message on regular MAM request. skipping");
 597            return;
 598        }
 599        final Jid mucTrueCounterPart;
 600        final OccupantId occupant;
 601        if (isTypeGroupChat) {
 602            final Conversation conversation =
 603                    mXmppConnectionService.find(account, from.asBareJid());
 604            final Jid mucTrueCounterPartByPresence;
 605            if (conversation != null) {
 606                final var mucOptions = conversation.getMucOptions();
 607                occupant =
 608                        mucOptions.occupantId() ? packet.getOnlyExtension(OccupantId.class) : null;
 609                final var user =
 610                        occupant == null ? null : mucOptions.findUserByOccupantId(occupant.getId());
 611                mucTrueCounterPartByPresence = user == null ? null : user.getRealJid();
 612            } else {
 613                occupant = null;
 614                mucTrueCounterPartByPresence = null;
 615            }
 616            mucTrueCounterPart =
 617                    getTrueCounterpart(
 618                            (query != null && query.safeToExtractTrueCounterpart())
 619                                    ? mucUserElement
 620                                    : null,
 621                            mucTrueCounterPartByPresence);
 622        } else if (mucUserElement != null) {
 623            final Conversation conversation =
 624                    mXmppConnectionService.find(account, from.asBareJid());
 625            if (conversation != null) {
 626                final var mucOptions = conversation.getMucOptions();
 627                occupant =
 628                        mucOptions.occupantId() ? packet.getOnlyExtension(OccupantId.class) : null;
 629            } else {
 630                occupant = null;
 631            }
 632            mucTrueCounterPart = null;
 633        } else {
 634            mucTrueCounterPart = null;
 635            occupant = null;
 636        }
 637        boolean isMucStatusMessage =
 638                Jid.Invalid.hasValidFrom(packet)
 639                        && from.isBareJid()
 640                        && mucUserElement != null
 641                        && mucUserElement.hasChild("status");
 642        boolean selfAddressed;
 643        if (packet.fromAccount(account)) {
 644            status = Message.STATUS_SEND;
 645            selfAddressed = to == null || account.getJid().asBareJid().equals(to.asBareJid());
 646            if (selfAddressed) {
 647                counterpart = from;
 648            } else {
 649                counterpart = to;
 650            }
 651        } else {
 652            status = Message.STATUS_RECEIVED;
 653            counterpart = from;
 654            selfAddressed = false;
 655        }
 656
 657        final Invite invite = extractInvite(packet);
 658        if (invite != null) {
 659            if (invite.jid.asBareJid().equals(account.getJid().asBareJid())) {
 660                Log.d(
 661                        Config.LOGTAG,
 662                        account.getJid().asBareJid()
 663                                + ": ignore invite to "
 664                                + invite.jid
 665                                + " because it matches account");
 666            } else if (isTypeGroupChat) {
 667                Log.d(
 668                        Config.LOGTAG,
 669                        account.getJid().asBareJid()
 670                                + ": ignoring invite to "
 671                                + invite.jid
 672                                + " because it was received as group chat");
 673            } else if (invite.direct
 674                    && (mucUserElement != null
 675                            || invite.inviter == null
 676                            || mXmppConnectionService.isMuc(account, invite.inviter))) {
 677                Log.d(
 678                        Config.LOGTAG,
 679                        account.getJid().asBareJid()
 680                                + ": ignoring direct invite to "
 681                                + invite.jid
 682                                + " because it was received in MUC");
 683            } else {
 684                invite.execute(account);
 685                return;
 686            }
 687        }
 688
 689        if ((body != null
 690                        || pgpEncrypted != null
 691                        || (axolotlEncrypted != null && axolotlEncrypted.hasChild("payload"))
 692                        || oobUrl != null)
 693                && !isMucStatusMessage) {
 694            final boolean conversationIsProbablyMuc =
 695                    isTypeGroupChat
 696                            || mucUserElement != null
 697                            || connection
 698                                    .getMucServersWithholdAccount()
 699                                    .contains(counterpart.getDomain().toString());
 700            final Conversation conversation =
 701                    mXmppConnectionService.findOrCreateConversation(
 702                            account,
 703                            counterpart.asBareJid(),
 704                            conversationIsProbablyMuc,
 705                            false,
 706                            query,
 707                            false);
 708            final boolean conversationMultiMode = conversation.getMode() == Conversation.MODE_MULTI;
 709
 710            if (serverMsgId == null) {
 711                serverMsgId = extractStanzaId(packet, isTypeGroupChat, conversation);
 712            }
 713
 714            if (selfAddressed) {
 715                // don’t store serverMsgId on reflections for edits
 716                final var reflectedServerMsgId =
 717                        Strings.isNullOrEmpty(replacementId) ? serverMsgId : null;
 718                if (mXmppConnectionService.markMessage(
 719                        conversation,
 720                        remoteMsgId,
 721                        Message.STATUS_SEND_RECEIVED,
 722                        reflectedServerMsgId)) {
 723                    return;
 724                }
 725                status = Message.STATUS_RECEIVED;
 726                if (remoteMsgId != null
 727                        && conversation.findMessageWithRemoteId(remoteMsgId, counterpart) != null) {
 728                    return;
 729                }
 730            }
 731
 732            if (isTypeGroupChat) {
 733                if (conversation.getMucOptions().isSelf(counterpart)) {
 734                    status = Message.STATUS_SEND_RECEIVED;
 735                    isCarbon = true; // not really carbon but received from another resource
 736                    // don’t store serverMsgId on reflections for edits
 737                    final var reflectedServerMsgId =
 738                            Strings.isNullOrEmpty(replacementId) ? serverMsgId : null;
 739                    if (mXmppConnectionService.markMessage(
 740                            conversation, remoteMsgId, status, reflectedServerMsgId, body)) {
 741                        return;
 742                    } else if (remoteMsgId == null || Config.IGNORE_ID_REWRITE_IN_MUC) {
 743                        if (body != null) {
 744                            Message message = conversation.findSentMessageWithBody(body.content);
 745                            if (message != null) {
 746                                mXmppConnectionService.markMessage(message, status);
 747                                return;
 748                            }
 749                        }
 750                    }
 751                } else {
 752                    status = Message.STATUS_RECEIVED;
 753                }
 754            }
 755            final Message message;
 756            if (pgpEncrypted != null && Config.supportOpenPgp()) {
 757                message = new Message(conversation, pgpEncrypted, Message.ENCRYPTION_PGP, status);
 758            } else if (axolotlEncrypted != null && Config.supportOmemo()) {
 759                Jid origin;
 760                Set<Jid> fallbacksBySourceId = Collections.emptySet();
 761                if (conversationMultiMode) {
 762                    final Jid fallback =
 763                            conversation.getMucOptions().getTrueCounterpart(counterpart);
 764                    origin = getTrueCounterpart(query != null ? mucUserElement : null, fallback);
 765                    if (origin == null) {
 766                        try {
 767                            fallbacksBySourceId =
 768                                    account.getAxolotlService()
 769                                            .findCounterpartsBySourceId(
 770                                                    XmppAxolotlMessage.parseSourceId(
 771                                                            axolotlEncrypted));
 772                        } catch (IllegalArgumentException e) {
 773                            // ignoring
 774                        }
 775                    }
 776                    if (origin == null && fallbacksBySourceId.isEmpty()) {
 777                        Log.d(
 778                                Config.LOGTAG,
 779                                "axolotl message in anonymous conference received and no possible"
 780                                        + " fallbacks");
 781                        return;
 782                    }
 783                } else {
 784                    fallbacksBySourceId = Collections.emptySet();
 785                    origin = from;
 786                }
 787
 788                final boolean liveMessage =
 789                        query == null && !isTypeGroupChat && mucUserElement == null;
 790                final boolean checkedForDuplicates =
 791                        liveMessage
 792                                || (serverMsgId != null
 793                                        && remoteMsgId != null
 794                                        && !conversation.possibleDuplicate(
 795                                                serverMsgId, remoteMsgId));
 796
 797                if (origin != null) {
 798                    message =
 799                            parseAxolotlChat(
 800                                    axolotlEncrypted,
 801                                    origin,
 802                                    conversation,
 803                                    status,
 804                                    checkedForDuplicates,
 805                                    query != null);
 806                } else {
 807                    Message trial = null;
 808                    for (Jid fallback : fallbacksBySourceId) {
 809                        trial =
 810                                parseAxolotlChat(
 811                                        axolotlEncrypted,
 812                                        fallback,
 813                                        conversation,
 814                                        status,
 815                                        checkedForDuplicates && fallbacksBySourceId.size() == 1,
 816                                        query != null);
 817                        if (trial != null) {
 818                            Log.d(
 819                                    Config.LOGTAG,
 820                                    account.getJid().asBareJid()
 821                                            + ": decoded muc message using fallback");
 822                            origin = fallback;
 823                            break;
 824                        }
 825                    }
 826                    message = trial;
 827                }
 828                if (message == null) {
 829                    if (query == null
 830                            && extractChatState(
 831                                    mXmppConnectionService.find(account, counterpart.asBareJid()),
 832                                    isTypeGroupChat,
 833                                    packet)) {
 834                        mXmppConnectionService.updateConversationUi();
 835                    }
 836                    if (query != null && status == Message.STATUS_SEND && remoteMsgId != null) {
 837                        Message previouslySent = conversation.findSentMessageWithUuid(remoteMsgId);
 838                        if (previouslySent != null
 839                                && previouslySent.getServerMsgId() == null
 840                                && serverMsgId != null) {
 841                            previouslySent.setServerMsgId(serverMsgId);
 842                            mXmppConnectionService.databaseBackend.updateMessage(
 843                                    previouslySent, false);
 844                            Log.d(
 845                                    Config.LOGTAG,
 846                                    account.getJid().asBareJid()
 847                                            + ": encountered previously sent OMEMO message without"
 848                                            + " serverId. updating...");
 849                        }
 850                    }
 851                    return;
 852                }
 853                if (conversationMultiMode) {
 854                    message.setTrueCounterpart(origin);
 855                }
 856            } else if (body == null && oobUrl != null) {
 857                message = new Message(conversation, oobUrl, Message.ENCRYPTION_NONE, status);
 858                message.setOob(true);
 859                if (CryptoHelper.isPgpEncryptedUrl(oobUrl)) {
 860                    message.setEncryption(Message.ENCRYPTION_DECRYPTED);
 861                }
 862            } else {
 863                message = new Message(conversation, body.content, Message.ENCRYPTION_NONE, status);
 864                if (body.count > 1) {
 865                    message.setBodyLanguage(body.language);
 866                }
 867            }
 868
 869            message.setCounterpart(counterpart);
 870            message.setRemoteMsgId(remoteMsgId);
 871            message.setServerMsgId(serverMsgId);
 872            message.setCarbon(isCarbon);
 873            message.setTime(timestamp);
 874            if (body != null && body.content != null && body.content.equals(oobUrl)) {
 875                message.setOob(true);
 876                if (CryptoHelper.isPgpEncryptedUrl(oobUrl)) {
 877                    message.setEncryption(Message.ENCRYPTION_DECRYPTED);
 878                }
 879            }
 880            message.markable = packet.hasChild("markable", "urn:xmpp:chat-markers:0");
 881            if (conversationMultiMode) {
 882                final var mucOptions = conversation.getMucOptions();
 883                if (occupant != null) {
 884                    message.setOccupantId(occupant.getId());
 885                }
 886                message.setMucUser(mucOptions.findUserByFullJid(counterpart));
 887                final Jid fallback = mucOptions.getTrueCounterpart(counterpart);
 888                Jid trueCounterpart;
 889                if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL) {
 890                    trueCounterpart = message.getTrueCounterpart();
 891                } else if (query != null && query.safeToExtractTrueCounterpart()) {
 892                    trueCounterpart = getTrueCounterpart(mucUserElement, fallback);
 893                } else {
 894                    trueCounterpart = fallback;
 895                }
 896                if (trueCounterpart != null && isTypeGroupChat) {
 897                    if (trueCounterpart.asBareJid().equals(account.getJid().asBareJid())) {
 898                        status =
 899                                isTypeGroupChat
 900                                        ? Message.STATUS_SEND_RECEIVED
 901                                        : Message.STATUS_SEND;
 902                    } else {
 903                        status = Message.STATUS_RECEIVED;
 904                        message.setCarbon(false);
 905                    }
 906                }
 907                message.setStatus(status);
 908                message.setTrueCounterpart(trueCounterpart);
 909                if (!isTypeGroupChat) {
 910                    message.setType(Message.TYPE_PRIVATE);
 911                }
 912            } else {
 913                updateLastseen(account, from);
 914            }
 915
 916            if (replacementId != null && mXmppConnectionService.allowMessageCorrection()) {
 917                final Message replacedMessage =
 918                        conversation.findMessageWithRemoteIdAndCounterpart(
 919                                replacementId,
 920                                counterpart,
 921                                message.getStatus() == Message.STATUS_RECEIVED,
 922                                message.isCarbon());
 923                if (replacedMessage != null) {
 924                    final boolean fingerprintsMatch =
 925                            replacedMessage.getFingerprint() == null
 926                                    || replacedMessage
 927                                            .getFingerprint()
 928                                            .equals(message.getFingerprint());
 929                    final boolean trueCountersMatch =
 930                            replacedMessage.getTrueCounterpart() != null
 931                                    && message.getTrueCounterpart() != null
 932                                    && replacedMessage
 933                                            .getTrueCounterpart()
 934                                            .asBareJid()
 935                                            .equals(message.getTrueCounterpart().asBareJid());
 936                    final boolean occupantIdMatch =
 937                            replacedMessage.getOccupantId() != null
 938                                    && replacedMessage
 939                                            .getOccupantId()
 940                                            .equals(message.getOccupantId());
 941                    final boolean mucUserMatches =
 942                            query == null
 943                                    && replacedMessage.sameMucUser(
 944                                            message); // can not be checked when using mam
 945                    final boolean duplicate = conversation.hasDuplicateMessage(message);
 946                    if (fingerprintsMatch
 947                            && (trueCountersMatch
 948                                    || occupantIdMatch
 949                                    || !conversationMultiMode
 950                                    || mucUserMatches)
 951                            && !duplicate) {
 952                        synchronized (replacedMessage) {
 953                            final String uuid = replacedMessage.getUuid();
 954                            replacedMessage.setUuid(UUID.randomUUID().toString());
 955                            replacedMessage.setBody(message.getBody());
 956                            // we store the IDs of the replacing message. This is essentially unused
 957                            // today (only the fact that there are _some_ edits causes the edit icon
 958                            // to appear)
 959                            replacedMessage.putEdited(
 960                                    message.getRemoteMsgId(), message.getServerMsgId());
 961
 962                            // we used to call
 963                            // `replacedMessage.setServerMsgId(message.getServerMsgId());` so during
 964                            // catchup we could start from the edit; not the original message
 965                            // however this caused problems for things like reactions that refer to
 966                            // the serverMsgId
 967
 968                            replacedMessage.setEncryption(message.getEncryption());
 969                            if (replacedMessage.getStatus() == Message.STATUS_RECEIVED) {
 970                                replacedMessage.markUnread();
 971                            }
 972                            extractChatState(
 973                                    mXmppConnectionService.find(account, counterpart.asBareJid()),
 974                                    isTypeGroupChat,
 975                                    packet);
 976                            mXmppConnectionService.updateMessage(replacedMessage, uuid);
 977                            if (mXmppConnectionService.confirmMessages()
 978                                    && replacedMessage.getStatus() == Message.STATUS_RECEIVED
 979                                    && (replacedMessage.trusted()
 980                                            || replacedMessage
 981                                                    .isPrivateMessage()) // TODO do we really want
 982                                    // to send receipts for all
 983                                    // PMs?
 984                                    && remoteMsgId != null
 985                                    && !selfAddressed
 986                                    && !isTypeGroupChat) {
 987                                processMessageReceipts(account, packet, remoteMsgId, query);
 988                            }
 989                            if (replacedMessage.getEncryption() == Message.ENCRYPTION_PGP) {
 990                                conversation
 991                                        .getAccount()
 992                                        .getPgpDecryptionService()
 993                                        .discard(replacedMessage);
 994                                conversation
 995                                        .getAccount()
 996                                        .getPgpDecryptionService()
 997                                        .decrypt(replacedMessage, false);
 998                            }
 999                        }
1000                        mXmppConnectionService.getNotificationService().updateNotification();
1001                        return;
1002                    } else {
1003                        Log.d(
1004                                Config.LOGTAG,
1005                                account.getJid().asBareJid()
1006                                        + ": received message correction but verification didn't"
1007                                        + " check out");
1008                    }
1009                }
1010            }
1011
1012            long deletionDate = mXmppConnectionService.getAutomaticMessageDeletionDate();
1013            if (deletionDate != 0 && message.getTimeSent() < deletionDate) {
1014                Log.d(
1015                        Config.LOGTAG,
1016                        account.getJid().asBareJid()
1017                                + ": skipping message from "
1018                                + message.getCounterpart().toString()
1019                                + " because it was sent prior to our deletion date");
1020                return;
1021            }
1022
1023            boolean checkForDuplicates =
1024                    (isTypeGroupChat && packet.hasChild("delay", "urn:xmpp:delay"))
1025                            || message.isPrivateMessage()
1026                            || message.getServerMsgId() != null
1027                            || (query == null
1028                                    && mXmppConnectionService
1029                                            .getMessageArchiveService()
1030                                            .isCatchupInProgress(conversation));
1031            if (checkForDuplicates) {
1032                final Message duplicate = conversation.findDuplicateMessage(message);
1033                if (duplicate != null) {
1034                    final boolean serverMsgIdUpdated;
1035                    if (duplicate.getStatus() != Message.STATUS_RECEIVED
1036                            && duplicate.getUuid().equals(message.getRemoteMsgId())
1037                            && duplicate.getServerMsgId() == null
1038                            && message.getServerMsgId() != null) {
1039                        duplicate.setServerMsgId(message.getServerMsgId());
1040                        if (mXmppConnectionService.databaseBackend.updateMessage(
1041                                duplicate, false)) {
1042                            serverMsgIdUpdated = true;
1043                        } else {
1044                            serverMsgIdUpdated = false;
1045                            Log.e(Config.LOGTAG, "failed to update message");
1046                        }
1047                    } else {
1048                        serverMsgIdUpdated = false;
1049                    }
1050                    Log.d(
1051                            Config.LOGTAG,
1052                            "skipping duplicate message with "
1053                                    + message.getCounterpart()
1054                                    + ". serverMsgIdUpdated="
1055                                    + serverMsgIdUpdated);
1056                    return;
1057                }
1058            }
1059
1060            if (query != null
1061                    && query.getPagingOrder() == MessageArchiveService.PagingOrder.REVERSE) {
1062                conversation.prepend(query.getActualInThisQuery(), message);
1063            } else {
1064                conversation.add(message);
1065            }
1066            if (query != null) {
1067                query.incrementActualMessageCount();
1068            }
1069
1070            if (query == null || query.isCatchup()) { // either no mam or catchup
1071                if (status == Message.STATUS_SEND || status == Message.STATUS_SEND_RECEIVED) {
1072                    mXmppConnectionService.markRead(conversation);
1073                    if (query == null) {
1074                        activateGracePeriod(account);
1075                    }
1076                } else {
1077                    message.markUnread();
1078                    notify = true;
1079                }
1080            }
1081
1082            if (message.getEncryption() == Message.ENCRYPTION_PGP) {
1083                notify =
1084                        conversation
1085                                .getAccount()
1086                                .getPgpDecryptionService()
1087                                .decrypt(message, notify);
1088            } else if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL_NOT_FOR_THIS_DEVICE
1089                    || message.getEncryption() == Message.ENCRYPTION_AXOLOTL_FAILED) {
1090                notify = false;
1091            }
1092
1093            if (query == null) {
1094                extractChatState(
1095                        mXmppConnectionService.find(account, counterpart.asBareJid()),
1096                        isTypeGroupChat,
1097                        packet);
1098                mXmppConnectionService.updateConversationUi();
1099            }
1100
1101            if (mXmppConnectionService.confirmMessages()
1102                    && message.getStatus() == Message.STATUS_RECEIVED
1103                    && (message.trusted() || message.isPrivateMessage())
1104                    && remoteMsgId != null
1105                    && !selfAddressed
1106                    && !isTypeGroupChat) {
1107                processMessageReceipts(account, packet, remoteMsgId, query);
1108            }
1109
1110            mXmppConnectionService.databaseBackend.createMessage(message);
1111            final HttpConnectionManager manager =
1112                    this.mXmppConnectionService.getHttpConnectionManager();
1113            if (message.trusted()
1114                    && message.treatAsDownloadable()
1115                    && manager.getAutoAcceptFileSize() > 0) {
1116                manager.createNewDownloadConnection(message);
1117            } else if (notify) {
1118                if (query != null && query.isCatchup()) {
1119                    mXmppConnectionService.getNotificationService().pushFromBacklog(message);
1120                } else {
1121                    mXmppConnectionService.getNotificationService().push(message);
1122                }
1123            }
1124        } else if (!packet.hasChild("body")) { // no body
1125
1126            final Conversation conversation =
1127                    mXmppConnectionService.find(account, from.asBareJid());
1128            if (axolotlEncrypted != null) {
1129                Jid origin;
1130                if (conversation != null && conversation.getMode() == Conversation.MODE_MULTI) {
1131                    final Jid fallback =
1132                            conversation.getMucOptions().getTrueCounterpart(counterpart);
1133                    origin = getTrueCounterpart(query != null ? mucUserElement : null, fallback);
1134                    if (origin == null) {
1135                        Log.d(
1136                                Config.LOGTAG,
1137                                "omemo key transport message in anonymous conference received");
1138                        return;
1139                    }
1140                } else if (isTypeGroupChat) {
1141                    return;
1142                } else {
1143                    origin = from;
1144                }
1145                try {
1146                    final XmppAxolotlMessage xmppAxolotlMessage =
1147                            XmppAxolotlMessage.fromElement(axolotlEncrypted, origin.asBareJid());
1148                    account.getAxolotlService()
1149                            .processReceivingKeyTransportMessage(xmppAxolotlMessage, query != null);
1150                    Log.d(
1151                            Config.LOGTAG,
1152                            account.getJid().asBareJid()
1153                                    + ": omemo key transport message received from "
1154                                    + origin);
1155                } catch (Exception e) {
1156                    Log.d(
1157                            Config.LOGTAG,
1158                            account.getJid().asBareJid()
1159                                    + ": invalid omemo key transport message received "
1160                                    + e.getMessage());
1161                    return;
1162                }
1163            }
1164
1165            if (query == null
1166                    && extractChatState(
1167                            mXmppConnectionService.find(account, counterpart.asBareJid()),
1168                            isTypeGroupChat,
1169                            packet)) {
1170                mXmppConnectionService.updateConversationUi();
1171            }
1172
1173            if (isTypeGroupChat) {
1174                if (packet.hasChild("subject")
1175                        && !packet.hasChild("thread")) { // We already know it has no body per above
1176                    if (conversation != null && conversation.getMode() == Conversation.MODE_MULTI) {
1177                        conversation.setHasMessagesLeftOnServer(conversation.countMessages() > 0);
1178                        final LocalizedContent subject = packet.getSubject();
1179                        if (subject != null
1180                                && conversation.getMucOptions().setSubject(subject.content)) {
1181                            mXmppConnectionService.updateConversation(conversation);
1182                        }
1183                        mXmppConnectionService.updateConversationUi();
1184                        return;
1185                    }
1186                }
1187            }
1188            if (conversation != null
1189                    && mucUserElement != null
1190                    && Jid.Invalid.hasValidFrom(packet)
1191                    && from.isBareJid()) {
1192                for (Element child : mucUserElement.getChildren()) {
1193                    if ("status".equals(child.getName())) {
1194                        try {
1195                            int code = Integer.parseInt(child.getAttribute("code"));
1196                            if ((code >= 170 && code <= 174) || (code >= 102 && code <= 104)) {
1197                                mXmppConnectionService.fetchConferenceConfiguration(conversation);
1198                                break;
1199                            }
1200                        } catch (Exception e) {
1201                            // ignored
1202                        }
1203                    } else if ("item".equals(child.getName())) {
1204                        final var user = AbstractParser.parseItem(conversation, child);
1205                        Log.d(
1206                                Config.LOGTAG,
1207                                account.getJid()
1208                                        + ": changing affiliation for "
1209                                        + user.getRealJid()
1210                                        + " to "
1211                                        + user.getAffiliation()
1212                                        + " in "
1213                                        + conversation.getJid().asBareJid());
1214                        if (!user.realJidMatchesAccount()) {
1215                            final var mucOptions = conversation.getMucOptions();
1216                            final boolean isNew = mucOptions.updateUser(user);
1217                            final var avatarService = mXmppConnectionService.getAvatarService();
1218                            if (Strings.isNullOrEmpty(mucOptions.getAvatar())) {
1219                                avatarService.clear(mucOptions);
1220                            }
1221                            avatarService.clear(user);
1222                            mXmppConnectionService.updateMucRosterUi();
1223                            mXmppConnectionService.updateConversationUi();
1224                            Contact contact = user.getContact();
1225                            if (!user.getAffiliation().ranks(MucOptions.Affiliation.MEMBER)) {
1226                                Jid jid = user.getRealJid();
1227                                List<Jid> cryptoTargets = conversation.getAcceptedCryptoTargets();
1228                                if (cryptoTargets.remove(user.getRealJid())) {
1229                                    Log.d(
1230                                            Config.LOGTAG,
1231                                            account.getJid().asBareJid()
1232                                                    + ": removed "
1233                                                    + jid
1234                                                    + " from crypto targets of "
1235                                                    + conversation.getName());
1236                                    conversation.setAcceptedCryptoTargets(cryptoTargets);
1237                                    mXmppConnectionService.updateConversation(conversation);
1238                                }
1239                            } else if (isNew
1240                                    && user.getRealJid() != null
1241                                    && conversation.getMucOptions().isPrivateAndNonAnonymous()
1242                                    && (contact == null || !contact.mutualPresenceSubscription())
1243                                    && account.getAxolotlService()
1244                                            .hasEmptyDeviceList(user.getRealJid())) {
1245                                account.getAxolotlService().fetchDeviceIds(user.getRealJid());
1246                            }
1247                        }
1248                    }
1249                }
1250            }
1251            if (!isTypeGroupChat) {
1252                for (Element child : packet.getChildren()) {
1253                    if (Namespace.JINGLE_MESSAGE.equals(child.getNamespace())
1254                            && JINGLE_MESSAGE_ELEMENT_NAMES.contains(child.getName())) {
1255                        final String action = child.getName();
1256                        final String sessionId = child.getAttribute("id");
1257                        if (sessionId == null) {
1258                            break;
1259                        }
1260                        if (query == null && offlineMessagesRetrieved) {
1261                            if (serverMsgId == null) {
1262                                serverMsgId = extractStanzaId(account, packet);
1263                            }
1264                            mXmppConnectionService
1265                                    .getJingleConnectionManager()
1266                                    .deliverMessage(
1267                                            account,
1268                                            packet.getTo(),
1269                                            packet.getFrom(),
1270                                            child,
1271                                            remoteMsgId,
1272                                            serverMsgId,
1273                                            timestamp);
1274                            final Contact contact = account.getRoster().getContact(from);
1275                            // this is the same condition that is found in JingleRtpConnection for
1276                            // the 'ringing' response. Responding with delivery receipts predates
1277                            // the 'ringing' spec'd
1278                            final boolean sendReceipts =
1279                                    contact.showInContactList()
1280                                            || Config.JINGLE_MESSAGE_INIT_STRICT_OFFLINE_CHECK;
1281                            if (remoteMsgId != null && !contact.isSelf() && sendReceipts) {
1282                                processMessageReceipts(account, packet, remoteMsgId, null);
1283                            }
1284                        } else if ((query != null && query.isCatchup())
1285                                || !offlineMessagesRetrieved) {
1286                            if ("propose".equals(action)) {
1287                                final Element description = child.findChild("description");
1288                                final String namespace =
1289                                        description == null ? null : description.getNamespace();
1290                                if (Namespace.JINGLE_APPS_RTP.equals(namespace)) {
1291                                    final Conversation c =
1292                                            mXmppConnectionService.findOrCreateConversation(
1293                                                    account, counterpart.asBareJid(), false, false);
1294                                    final Message preExistingMessage =
1295                                            c.findRtpSession(sessionId, status);
1296                                    if (preExistingMessage != null) {
1297                                        preExistingMessage.setServerMsgId(serverMsgId);
1298                                        mXmppConnectionService.updateMessage(preExistingMessage);
1299                                        break;
1300                                    }
1301                                    final Message message =
1302                                            new Message(
1303                                                    c, status, Message.TYPE_RTP_SESSION, sessionId);
1304                                    message.setServerMsgId(serverMsgId);
1305                                    message.setTime(timestamp);
1306                                    message.setBody(new RtpSessionStatus(false, 0).toString());
1307                                    c.add(message);
1308                                    mXmppConnectionService.databaseBackend.createMessage(message);
1309                                }
1310                            } else if ("proceed".equals(action)) {
1311                                // status needs to be flipped to find the original propose
1312                                final Conversation c =
1313                                        mXmppConnectionService.findOrCreateConversation(
1314                                                account, counterpart.asBareJid(), false, false);
1315                                final int s =
1316                                        packet.fromAccount(account)
1317                                                ? Message.STATUS_RECEIVED
1318                                                : Message.STATUS_SEND;
1319                                final Message message = c.findRtpSession(sessionId, s);
1320                                if (message != null) {
1321                                    message.setBody(new RtpSessionStatus(true, 0).toString());
1322                                    if (serverMsgId != null) {
1323                                        message.setServerMsgId(serverMsgId);
1324                                    }
1325                                    message.setTime(timestamp);
1326                                    mXmppConnectionService.updateMessage(message, true);
1327                                } else {
1328                                    Log.d(
1329                                            Config.LOGTAG,
1330                                            "unable to find original rtp session message for"
1331                                                    + " received propose");
1332                                }
1333
1334                            } else if ("finish".equals(action)) {
1335                                Log.d(
1336                                        Config.LOGTAG,
1337                                        "received JMI 'finish' during MAM catch-up. Can be used to"
1338                                                + " update success/failure and duration");
1339                            }
1340                        } else {
1341                            // MAM reloads (non catchups
1342                            if ("propose".equals(action)) {
1343                                final Element description = child.findChild("description");
1344                                final String namespace =
1345                                        description == null ? null : description.getNamespace();
1346                                if (Namespace.JINGLE_APPS_RTP.equals(namespace)) {
1347                                    final Conversation c =
1348                                            mXmppConnectionService.findOrCreateConversation(
1349                                                    account, counterpart.asBareJid(), false, false);
1350                                    final Message preExistingMessage =
1351                                            c.findRtpSession(sessionId, status);
1352                                    if (preExistingMessage != null) {
1353                                        preExistingMessage.setServerMsgId(serverMsgId);
1354                                        mXmppConnectionService.updateMessage(preExistingMessage);
1355                                        break;
1356                                    }
1357                                    final Message message =
1358                                            new Message(
1359                                                    c, status, Message.TYPE_RTP_SESSION, sessionId);
1360                                    message.setServerMsgId(serverMsgId);
1361                                    message.setTime(timestamp);
1362                                    message.setBody(new RtpSessionStatus(true, 0).toString());
1363                                    if (query.getPagingOrder()
1364                                            == MessageArchiveService.PagingOrder.REVERSE) {
1365                                        c.prepend(query.getActualInThisQuery(), message);
1366                                    } else {
1367                                        c.add(message);
1368                                    }
1369                                    query.incrementActualMessageCount();
1370                                    mXmppConnectionService.databaseBackend.createMessage(message);
1371                                }
1372                            }
1373                        }
1374                        break;
1375                    }
1376                }
1377            }
1378
1379            final var received =
1380                    packet.getExtension(
1381                            im.conversations.android.xmpp.model.receipts.Received.class);
1382            if (received != null) {
1383                processReceived(received, packet, query, from);
1384            }
1385            final var displayed = packet.getExtension(Displayed.class);
1386            if (displayed != null) {
1387                processDisplayed(
1388                        displayed,
1389                        packet,
1390                        selfAddressed,
1391                        counterpart,
1392                        query,
1393                        isTypeGroupChat,
1394                        conversation,
1395                        mucUserElement,
1396                        from);
1397            }
1398            final Reactions reactions = packet.getExtension(Reactions.class);
1399            if (reactions != null) {
1400                processReactions(
1401                        reactions,
1402                        conversation,
1403                        isTypeGroupChat,
1404                        occupant,
1405                        counterpart,
1406                        mucTrueCounterPart,
1407                        packet);
1408            }
1409
1410            // end no body
1411        }
1412
1413        final var event = original.getExtension(Event.class);
1414        if (event != null && Jid.Invalid.hasValidFrom(original) && original.getFrom().isBareJid()) {
1415            final var action = event.getAction();
1416            final var node = action == null ? null : action.getNode();
1417            if (node == null) {
1418                Log.d(
1419                        Config.LOGTAG,
1420                        account.getJid().asBareJid()
1421                                + ": no node found in PubSub event from "
1422                                + original.getFrom());
1423            } else if (action instanceof Items items) {
1424                parseEvent(items, original.getFrom(), account);
1425            } else if (action instanceof Purge purge) {
1426                parsePurgeEvent(purge, original.getFrom(), account);
1427            } else if (action instanceof Delete delete) {
1428                parseDeleteEvent(delete, from, account);
1429            }
1430        }
1431
1432        final String nick = packet.findChildContent("nick", Namespace.NICK);
1433        if (nick != null && Jid.Invalid.hasValidFrom(original)) {
1434            if (mXmppConnectionService.isMuc(account, from)) {
1435                return;
1436            }
1437            final Contact contact = account.getRoster().getContact(from);
1438            if (contact.setPresenceName(nick)) {
1439                connection.getManager(RosterManager.class).writeToDatabaseAsync();
1440                mXmppConnectionService.getAvatarService().clear(contact);
1441            }
1442        }
1443    }
1444
1445    private void processReceived(
1446            final im.conversations.android.xmpp.model.receipts.Received received,
1447            final im.conversations.android.xmpp.model.stanza.Message packet,
1448            final MessageArchiveService.Query query,
1449            final Jid from) {
1450        final var account = this.connection.getAccount();
1451        final var id = received.getId();
1452        if (packet.fromAccount(account)) {
1453            if (query != null && id != null && packet.getTo() != null) {
1454                query.removePendingReceiptRequest(new ReceiptRequest(packet.getTo(), id));
1455            }
1456        } else if (id != null) {
1457            if (id.startsWith(JingleRtpConnection.JINGLE_MESSAGE_PROPOSE_ID_PREFIX)) {
1458                final String sessionId =
1459                        id.substring(JingleRtpConnection.JINGLE_MESSAGE_PROPOSE_ID_PREFIX.length());
1460                mXmppConnectionService
1461                        .getJingleConnectionManager()
1462                        .updateProposedSessionDiscovered(
1463                                account,
1464                                from,
1465                                sessionId,
1466                                JingleConnectionManager.DeviceDiscoveryState.DISCOVERED);
1467            } else {
1468                mXmppConnectionService.markMessage(
1469                        account, from.asBareJid(), id, Message.STATUS_SEND_RECEIVED);
1470            }
1471        }
1472    }
1473
1474    private void processDisplayed(
1475            final Displayed displayed,
1476            final im.conversations.android.xmpp.model.stanza.Message packet,
1477            final boolean selfAddressed,
1478            final Jid counterpart,
1479            final MessageArchiveService.Query query,
1480            final boolean isTypeGroupChat,
1481            final Conversation conversation,
1482            final Element mucUserElement,
1483            final Jid from) {
1484        final var account = getAccount();
1485        final var id = displayed.getId();
1486        // TODO we don’t even use 'sender' any more. Remove this!
1487        final Jid sender = Jid.Invalid.getNullForInvalid(displayed.getAttributeAsJid("sender"));
1488        if (packet.fromAccount(account) && !selfAddressed) {
1489            final Conversation c = mXmppConnectionService.find(account, counterpart.asBareJid());
1490            final Message message =
1491                    (c == null || id == null) ? null : c.findReceivedWithRemoteId(id);
1492            if (message != null && (query == null || query.isCatchup())) {
1493                mXmppConnectionService.markReadUpTo(c, message);
1494            }
1495            if (query == null) {
1496                activateGracePeriod(account);
1497            }
1498        } else if (isTypeGroupChat) {
1499            final Message message;
1500            if (conversation != null && id != null) {
1501                if (sender != null) {
1502                    message = conversation.findMessageWithRemoteId(id, sender);
1503                } else {
1504                    message = conversation.findMessageWithServerMsgId(id);
1505                }
1506            } else {
1507                message = null;
1508            }
1509            if (message != null) {
1510                // TODO use occupantId to extract true counterpart from presence
1511                final Jid fallback = conversation.getMucOptions().getTrueCounterpart(counterpart);
1512                // TODO try to externalize mucTrueCounterpart
1513                final Jid trueJid =
1514                        getTrueCounterpart(
1515                                (query != null && query.safeToExtractTrueCounterpart())
1516                                        ? mucUserElement
1517                                        : null,
1518                                fallback);
1519                final boolean trueJidMatchesAccount =
1520                        account.getJid()
1521                                .asBareJid()
1522                                .equals(trueJid == null ? null : trueJid.asBareJid());
1523                if (trueJidMatchesAccount || conversation.getMucOptions().isSelf(counterpart)) {
1524                    if (!message.isRead()
1525                            && (query == null || query.isCatchup())) { // checking if message is
1526                        // unread fixes race conditions
1527                        // with reflections
1528                        mXmppConnectionService.markReadUpTo(conversation, message);
1529                    }
1530                } else if (!counterpart.isBareJid() && trueJid != null) {
1531                    final ReadByMarker readByMarker = ReadByMarker.from(counterpart, trueJid);
1532                    if (message.addReadByMarker(readByMarker)) {
1533                        final var mucOptions = conversation.getMucOptions();
1534                        final var everyone = ImmutableSet.copyOf(mucOptions.getMembers(false));
1535                        final var readyBy = message.getReadyByTrue();
1536                        final var mStatus = message.getStatus();
1537                        if (mucOptions.isPrivateAndNonAnonymous()
1538                                && (mStatus == Message.STATUS_SEND_RECEIVED
1539                                        || mStatus == Message.STATUS_SEND)
1540                                && readyBy.containsAll(everyone)) {
1541                            message.setStatus(Message.STATUS_SEND_DISPLAYED);
1542                        }
1543                        mXmppConnectionService.updateMessage(message, false);
1544                    }
1545                }
1546            }
1547        } else {
1548            final Message displayedMessage =
1549                    mXmppConnectionService.markMessage(
1550                            account, from.asBareJid(), id, Message.STATUS_SEND_DISPLAYED);
1551            Message message = displayedMessage == null ? null : displayedMessage.prev();
1552            while (message != null
1553                    && message.getStatus() == Message.STATUS_SEND_RECEIVED
1554                    && message.getTimeSent() < displayedMessage.getTimeSent()) {
1555                mXmppConnectionService.markMessage(message, Message.STATUS_SEND_DISPLAYED);
1556                message = message.prev();
1557            }
1558            if (displayedMessage != null && selfAddressed) {
1559                dismissNotification(account, counterpart, query, id);
1560            }
1561        }
1562    }
1563
1564    private void processReactions(
1565            final Reactions reactions,
1566            final Conversation conversation,
1567            final boolean isTypeGroupChat,
1568            final OccupantId occupant,
1569            final Jid counterpart,
1570            final Jid mucTrueCounterPart,
1571            final im.conversations.android.xmpp.model.stanza.Message packet) {
1572        final var account = getAccount();
1573        final String reactingTo = reactions.getId();
1574        if (conversation != null && reactingTo != null) {
1575            if (isTypeGroupChat && conversation.getMode() == Conversational.MODE_MULTI) {
1576                final var mucOptions = conversation.getMucOptions();
1577                final var occupantId = occupant == null ? null : occupant.getId();
1578                if (occupantId != null) {
1579                    final boolean isReceived = !mucOptions.isSelf(occupantId);
1580                    final Message message;
1581                    final var inMemoryMessage = conversation.findMessageWithServerMsgId(reactingTo);
1582                    if (inMemoryMessage != null) {
1583                        message = inMemoryMessage;
1584                    } else {
1585                        message =
1586                                mXmppConnectionService.databaseBackend.getMessageWithServerMsgId(
1587                                        conversation, reactingTo);
1588                    }
1589                    if (message != null) {
1590                        final var combinedReactions =
1591                                Reaction.withOccupantId(
1592                                        message.getReactions(),
1593                                        reactions.getReactions(),
1594                                        isReceived,
1595                                        counterpart,
1596                                        mucTrueCounterPart,
1597                                        occupantId);
1598                        message.setReactions(combinedReactions);
1599                        mXmppConnectionService.updateMessage(message, false);
1600                    } else {
1601                        Log.d(Config.LOGTAG, "message with id " + reactingTo + " not found");
1602                    }
1603                } else {
1604                    Log.d(Config.LOGTAG, "received reaction in channel w/o occupant ids. ignoring");
1605                }
1606            } else {
1607                final Message message;
1608                final var inMemoryMessage = conversation.findMessageWithUuidOrRemoteId(reactingTo);
1609                if (inMemoryMessage != null) {
1610                    message = inMemoryMessage;
1611                } else {
1612                    message =
1613                            mXmppConnectionService.databaseBackend.getMessageWithUuidOrRemoteId(
1614                                    conversation, reactingTo);
1615                }
1616                if (message == null) {
1617                    Log.d(Config.LOGTAG, "message with id " + reactingTo + " not found");
1618                    return;
1619                }
1620                final boolean isReceived;
1621                final Jid reactionFrom;
1622                if (conversation.getMode() == Conversational.MODE_MULTI) {
1623                    Log.d(Config.LOGTAG, "received reaction as MUC PM. triggering validation");
1624                    final var mucOptions = conversation.getMucOptions();
1625                    final var occupantId = occupant == null ? null : occupant.getId();
1626                    if (occupantId == null) {
1627                        Log.d(
1628                                Config.LOGTAG,
1629                                "received reaction via PM channel w/o occupant ids. ignoring");
1630                        return;
1631                    }
1632                    isReceived = !mucOptions.isSelf(occupantId);
1633                    if (isReceived) {
1634                        reactionFrom = counterpart;
1635                    } else {
1636                        if (!occupantId.equals(message.getOccupantId())) {
1637                            Log.d(
1638                                    Config.LOGTAG,
1639                                    "reaction received via MUC PM did not pass validation");
1640                            return;
1641                        }
1642                        reactionFrom = account.getJid().asBareJid();
1643                    }
1644                } else {
1645                    if (packet.fromAccount(account)) {
1646                        isReceived = false;
1647                        reactionFrom = account.getJid().asBareJid();
1648                    } else {
1649                        isReceived = true;
1650                        reactionFrom = counterpart;
1651                    }
1652                }
1653                final var combinedReactions =
1654                        Reaction.withFrom(
1655                                message.getReactions(),
1656                                reactions.getReactions(),
1657                                isReceived,
1658                                reactionFrom);
1659                message.setReactions(combinedReactions);
1660                mXmppConnectionService.updateMessage(message, false);
1661            }
1662        }
1663    }
1664
1665    private static Pair<im.conversations.android.xmpp.model.stanza.Message, Long>
1666            getForwardedMessagePacket(
1667                    final im.conversations.android.xmpp.model.stanza.Message original,
1668                    Class<? extends Extension> clazz) {
1669        final var extension = original.getExtension(clazz);
1670        final var forwarded = extension == null ? null : extension.getExtension(Forwarded.class);
1671        if (forwarded == null) {
1672            return null;
1673        }
1674        final Long timestamp = AbstractParser.parseTimestamp(forwarded, null);
1675        final var forwardedMessage = forwarded.getMessage();
1676        if (forwardedMessage == null) {
1677            return null;
1678        }
1679        return new Pair<>(forwardedMessage, timestamp);
1680    }
1681
1682    private static Pair<im.conversations.android.xmpp.model.stanza.Message, Long>
1683            getForwardedMessagePacket(
1684                    final im.conversations.android.xmpp.model.stanza.Message original,
1685                    final String name,
1686                    final String namespace) {
1687        final Element wrapper = original.findChild(name, namespace);
1688        final var forwardedElement =
1689                wrapper == null ? null : wrapper.findChild("forwarded", Namespace.FORWARD);
1690        if (forwardedElement instanceof Forwarded forwarded) {
1691            final Long timestamp = AbstractParser.parseTimestamp(forwarded, null);
1692            final var forwardedMessage = forwarded.getMessage();
1693            if (forwardedMessage == null) {
1694                return null;
1695            }
1696            return new Pair<>(forwardedMessage, timestamp);
1697        }
1698        return null;
1699    }
1700
1701    private void dismissNotification(
1702            Account account, Jid counterpart, MessageArchiveService.Query query, final String id) {
1703        final Conversation conversation =
1704                mXmppConnectionService.find(account, counterpart.asBareJid());
1705        if (conversation != null && (query == null || query.isCatchup())) {
1706            final String displayableId = conversation.findMostRecentRemoteDisplayableId();
1707            if (displayableId != null && displayableId.equals(id)) {
1708                mXmppConnectionService.markRead(conversation);
1709            } else {
1710                Log.w(
1711                        Config.LOGTAG,
1712                        account.getJid().asBareJid()
1713                                + ": received dismissing display marker that did not match our last"
1714                                + " id in that conversation");
1715            }
1716        }
1717    }
1718
1719    private void processMessageReceipts(
1720            final Account account,
1721            final im.conversations.android.xmpp.model.stanza.Message packet,
1722            final String remoteMsgId,
1723            final MessageArchiveService.Query query) {
1724        final var request = packet.hasExtension(Request.class);
1725        if (query == null) {
1726            if (request) {
1727                final var receipt =
1728                        mXmppConnectionService
1729                                .getMessageGenerator()
1730                                .received(packet.getFrom(), remoteMsgId, packet.getType());
1731                mXmppConnectionService.sendMessagePacket(account, receipt);
1732            }
1733        } else if (query.isCatchup()) {
1734            if (request) {
1735                query.addPendingReceiptRequest(new ReceiptRequest(packet.getFrom(), remoteMsgId));
1736            }
1737        }
1738    }
1739
1740    private void activateGracePeriod(Account account) {
1741        long duration =
1742                mXmppConnectionService.getLongPreference(
1743                                "grace_period_length", R.integer.grace_period)
1744                        * 1000;
1745        Log.d(
1746                Config.LOGTAG,
1747                account.getJid().asBareJid()
1748                        + ": activating grace period till "
1749                        + TIME_FORMAT.format(new Date(System.currentTimeMillis() + duration)));
1750        account.activateGracePeriod(duration);
1751    }
1752
1753    private class Invite {
1754        final Jid jid;
1755        final String password;
1756        final boolean direct;
1757        final Jid inviter;
1758
1759        Invite(Jid jid, String password, boolean direct, Jid inviter) {
1760            this.jid = jid;
1761            this.password = password;
1762            this.direct = direct;
1763            this.inviter = inviter;
1764        }
1765
1766        public boolean execute(final Account account) {
1767            if (this.jid == null) {
1768                return false;
1769            }
1770            final Contact contact =
1771                    this.inviter != null ? account.getRoster().getContact(this.inviter) : null;
1772            if (contact != null && contact.isBlocked()) {
1773                Log.d(
1774                        Config.LOGTAG,
1775                        account.getJid().asBareJid()
1776                                + ": ignore invite from "
1777                                + contact.getJid()
1778                                + " because contact is blocked");
1779                return false;
1780            }
1781            final AppSettings appSettings = new AppSettings(mXmppConnectionService);
1782            if ((contact != null && contact.showInContactList())
1783                    || appSettings.isAcceptInvitesFromStrangers()) {
1784                final Conversation conversation =
1785                        mXmppConnectionService.findOrCreateConversation(account, jid, true, false);
1786                if (conversation.getMucOptions().online()) {
1787                    Log.d(
1788                            Config.LOGTAG,
1789                            account.getJid().asBareJid()
1790                                    + ": received invite to "
1791                                    + jid
1792                                    + " but muc is considered to be online");
1793                    mXmppConnectionService.mucSelfPingAndRejoin(conversation);
1794                } else {
1795                    conversation.getMucOptions().setPassword(password);
1796                    mXmppConnectionService.databaseBackend.updateConversation(conversation);
1797                    mXmppConnectionService.joinMuc(
1798                            conversation, contact != null && contact.showInContactList());
1799                    mXmppConnectionService.updateConversationUi();
1800                }
1801                return true;
1802            } else {
1803                Log.d(
1804                        Config.LOGTAG,
1805                        account.getJid().asBareJid()
1806                                + ": ignoring invite from "
1807                                + this.inviter
1808                                + " because we are not accepting invites from strangers. direct="
1809                                + direct);
1810                return false;
1811            }
1812        }
1813    }
1814}