MessageParser.java

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