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 =
1161                                packet.findInternationalizedChildContentInDefaultNamespace(
1162                                        "subject");
1163                        if (subject != null
1164                                && conversation.getMucOptions().setSubject(subject.content)) {
1165                            mXmppConnectionService.updateConversation(conversation);
1166                        }
1167                        mXmppConnectionService.updateConversationUi();
1168                        return;
1169                    }
1170                }
1171            }
1172            if (conversation != null
1173                    && mucUserElement != null
1174                    && Jid.Invalid.hasValidFrom(packet)
1175                    && from.isBareJid()) {
1176                for (Element child : mucUserElement.getChildren()) {
1177                    if ("status".equals(child.getName())) {
1178                        try {
1179                            int code = Integer.parseInt(child.getAttribute("code"));
1180                            if ((code >= 170 && code <= 174) || (code >= 102 && code <= 104)) {
1181                                mXmppConnectionService.fetchConferenceConfiguration(conversation);
1182                                break;
1183                            }
1184                        } catch (Exception e) {
1185                            // ignored
1186                        }
1187                    } else if ("item".equals(child.getName())) {
1188                        final var user = AbstractParser.parseItem(conversation, child);
1189                        Log.d(
1190                                Config.LOGTAG,
1191                                account.getJid()
1192                                        + ": changing affiliation for "
1193                                        + user.getRealJid()
1194                                        + " to "
1195                                        + user.getAffiliation()
1196                                        + " in "
1197                                        + conversation.getJid().asBareJid());
1198                        if (!user.realJidMatchesAccount()) {
1199                            final var mucOptions = conversation.getMucOptions();
1200                            final boolean isNew = mucOptions.updateUser(user);
1201                            final var avatarService = mXmppConnectionService.getAvatarService();
1202                            if (Strings.isNullOrEmpty(mucOptions.getAvatar())) {
1203                                avatarService.clear(mucOptions);
1204                            }
1205                            avatarService.clear(user);
1206                            mXmppConnectionService.updateMucRosterUi();
1207                            mXmppConnectionService.updateConversationUi();
1208                            Contact contact = user.getContact();
1209                            if (!user.getAffiliation().ranks(MucOptions.Affiliation.MEMBER)) {
1210                                Jid jid = user.getRealJid();
1211                                List<Jid> cryptoTargets = conversation.getAcceptedCryptoTargets();
1212                                if (cryptoTargets.remove(user.getRealJid())) {
1213                                    Log.d(
1214                                            Config.LOGTAG,
1215                                            account.getJid().asBareJid()
1216                                                    + ": removed "
1217                                                    + jid
1218                                                    + " from crypto targets of "
1219                                                    + conversation.getName());
1220                                    conversation.setAcceptedCryptoTargets(cryptoTargets);
1221                                    mXmppConnectionService.updateConversation(conversation);
1222                                }
1223                            } else if (isNew
1224                                    && user.getRealJid() != null
1225                                    && conversation.getMucOptions().isPrivateAndNonAnonymous()
1226                                    && (contact == null || !contact.mutualPresenceSubscription())
1227                                    && account.getAxolotlService()
1228                                            .hasEmptyDeviceList(user.getRealJid())) {
1229                                account.getAxolotlService().fetchDeviceIds(user.getRealJid());
1230                            }
1231                        }
1232                    }
1233                }
1234            }
1235            if (!isTypeGroupChat) {
1236                for (Element child : packet.getChildren()) {
1237                    if (Namespace.JINGLE_MESSAGE.equals(child.getNamespace())
1238                            && JINGLE_MESSAGE_ELEMENT_NAMES.contains(child.getName())) {
1239                        final String action = child.getName();
1240                        final String sessionId = child.getAttribute("id");
1241                        if (sessionId == null) {
1242                            break;
1243                        }
1244                        if (query == null && offlineMessagesRetrieved) {
1245                            if (serverMsgId == null) {
1246                                serverMsgId = extractStanzaId(account, packet);
1247                            }
1248                            mXmppConnectionService
1249                                    .getJingleConnectionManager()
1250                                    .deliverMessage(
1251                                            account,
1252                                            packet.getTo(),
1253                                            packet.getFrom(),
1254                                            child,
1255                                            remoteMsgId,
1256                                            serverMsgId,
1257                                            timestamp);
1258                            final Contact contact = account.getRoster().getContact(from);
1259                            // this is the same condition that is found in JingleRtpConnection for
1260                            // the 'ringing' response. Responding with delivery receipts predates
1261                            // the 'ringing' spec'd
1262                            final boolean sendReceipts =
1263                                    contact.showInContactList()
1264                                            || Config.JINGLE_MESSAGE_INIT_STRICT_OFFLINE_CHECK;
1265                            if (remoteMsgId != null && !contact.isSelf() && sendReceipts) {
1266                                processMessageReceipts(account, packet, remoteMsgId, null);
1267                            }
1268                        } else if ((query != null && query.isCatchup())
1269                                || !offlineMessagesRetrieved) {
1270                            if ("propose".equals(action)) {
1271                                final Element description = child.findChild("description");
1272                                final String namespace =
1273                                        description == null ? null : description.getNamespace();
1274                                if (Namespace.JINGLE_APPS_RTP.equals(namespace)) {
1275                                    final Conversation c =
1276                                            mXmppConnectionService.findOrCreateConversation(
1277                                                    account, counterpart.asBareJid(), false, false);
1278                                    final Message preExistingMessage =
1279                                            c.findRtpSession(sessionId, status);
1280                                    if (preExistingMessage != null) {
1281                                        preExistingMessage.setServerMsgId(serverMsgId);
1282                                        mXmppConnectionService.updateMessage(preExistingMessage);
1283                                        break;
1284                                    }
1285                                    final Message message =
1286                                            new Message(
1287                                                    c, status, Message.TYPE_RTP_SESSION, sessionId);
1288                                    message.setServerMsgId(serverMsgId);
1289                                    message.setTime(timestamp);
1290                                    message.setBody(new RtpSessionStatus(false, 0).toString());
1291                                    c.add(message);
1292                                    mXmppConnectionService.databaseBackend.createMessage(message);
1293                                }
1294                            } else if ("proceed".equals(action)) {
1295                                // status needs to be flipped to find the original propose
1296                                final Conversation c =
1297                                        mXmppConnectionService.findOrCreateConversation(
1298                                                account, counterpart.asBareJid(), false, false);
1299                                final int s =
1300                                        packet.fromAccount(account)
1301                                                ? Message.STATUS_RECEIVED
1302                                                : Message.STATUS_SEND;
1303                                final Message message = c.findRtpSession(sessionId, s);
1304                                if (message != null) {
1305                                    message.setBody(new RtpSessionStatus(true, 0).toString());
1306                                    if (serverMsgId != null) {
1307                                        message.setServerMsgId(serverMsgId);
1308                                    }
1309                                    message.setTime(timestamp);
1310                                    mXmppConnectionService.updateMessage(message, true);
1311                                } else {
1312                                    Log.d(
1313                                            Config.LOGTAG,
1314                                            "unable to find original rtp session message for"
1315                                                    + " received propose");
1316                                }
1317
1318                            } else if ("finish".equals(action)) {
1319                                Log.d(
1320                                        Config.LOGTAG,
1321                                        "received JMI 'finish' during MAM catch-up. Can be used to"
1322                                                + " update success/failure and duration");
1323                            }
1324                        } else {
1325                            // MAM reloads (non catchups
1326                            if ("propose".equals(action)) {
1327                                final Element description = child.findChild("description");
1328                                final String namespace =
1329                                        description == null ? null : description.getNamespace();
1330                                if (Namespace.JINGLE_APPS_RTP.equals(namespace)) {
1331                                    final Conversation c =
1332                                            mXmppConnectionService.findOrCreateConversation(
1333                                                    account, counterpart.asBareJid(), false, false);
1334                                    final Message preExistingMessage =
1335                                            c.findRtpSession(sessionId, status);
1336                                    if (preExistingMessage != null) {
1337                                        preExistingMessage.setServerMsgId(serverMsgId);
1338                                        mXmppConnectionService.updateMessage(preExistingMessage);
1339                                        break;
1340                                    }
1341                                    final Message message =
1342                                            new Message(
1343                                                    c, status, Message.TYPE_RTP_SESSION, sessionId);
1344                                    message.setServerMsgId(serverMsgId);
1345                                    message.setTime(timestamp);
1346                                    message.setBody(new RtpSessionStatus(true, 0).toString());
1347                                    if (query.getPagingOrder()
1348                                            == MessageArchiveService.PagingOrder.REVERSE) {
1349                                        c.prepend(query.getActualInThisQuery(), message);
1350                                    } else {
1351                                        c.add(message);
1352                                    }
1353                                    query.incrementActualMessageCount();
1354                                    mXmppConnectionService.databaseBackend.createMessage(message);
1355                                }
1356                            }
1357                        }
1358                        break;
1359                    }
1360                }
1361            }
1362
1363            final var received =
1364                    packet.getExtension(
1365                            im.conversations.android.xmpp.model.receipts.Received.class);
1366            if (received != null) {
1367                processReceived(received, packet, query, from);
1368            }
1369            final var displayed = packet.getExtension(Displayed.class);
1370            if (displayed != null) {
1371                processDisplayed(
1372                        displayed,
1373                        packet,
1374                        selfAddressed,
1375                        counterpart,
1376                        query,
1377                        isTypeGroupChat,
1378                        conversation,
1379                        mucUserElement,
1380                        from);
1381            }
1382            final Reactions reactions = packet.getExtension(Reactions.class);
1383            if (reactions != null) {
1384                processReactions(
1385                        reactions,
1386                        conversation,
1387                        isTypeGroupChat,
1388                        occupant,
1389                        counterpart,
1390                        mucTrueCounterPart,
1391                        packet);
1392            }
1393
1394            // end no body
1395        }
1396
1397        final Element event =
1398                original.findChild("event", "http://jabber.org/protocol/pubsub#event");
1399        if (event != null && Jid.Invalid.hasValidFrom(original) && original.getFrom().isBareJid()) {
1400            if (event.hasChild("items")) {
1401                parseEvent(event, original.getFrom(), account);
1402            } else if (event.hasChild("delete")) {
1403                parseDeleteEvent(event, original.getFrom(), account);
1404            } else if (event.hasChild("purge")) {
1405                parsePurgeEvent(event, original.getFrom(), account);
1406            }
1407        }
1408
1409        final String nick = packet.findChildContent("nick", Namespace.NICK);
1410        if (nick != null && Jid.Invalid.hasValidFrom(original)) {
1411            if (mXmppConnectionService.isMuc(account, from)) {
1412                return;
1413            }
1414            final Contact contact = account.getRoster().getContact(from);
1415            if (contact.setPresenceName(nick)) {
1416                mXmppConnectionService.syncRoster(account);
1417                mXmppConnectionService.getAvatarService().clear(contact);
1418            }
1419        }
1420    }
1421
1422    private void processReceived(
1423            final im.conversations.android.xmpp.model.receipts.Received received,
1424            final im.conversations.android.xmpp.model.stanza.Message packet,
1425            final MessageArchiveService.Query query,
1426            final Jid from) {
1427        final var id = received.getId();
1428        if (packet.fromAccount(account)) {
1429            if (query != null && id != null && packet.getTo() != null) {
1430                query.removePendingReceiptRequest(new ReceiptRequest(packet.getTo(), id));
1431            }
1432        } else if (id != null) {
1433            if (id.startsWith(JingleRtpConnection.JINGLE_MESSAGE_PROPOSE_ID_PREFIX)) {
1434                final String sessionId =
1435                        id.substring(JingleRtpConnection.JINGLE_MESSAGE_PROPOSE_ID_PREFIX.length());
1436                mXmppConnectionService
1437                        .getJingleConnectionManager()
1438                        .updateProposedSessionDiscovered(
1439                                account,
1440                                from,
1441                                sessionId,
1442                                JingleConnectionManager.DeviceDiscoveryState.DISCOVERED);
1443            } else {
1444                mXmppConnectionService.markMessage(
1445                        account, from.asBareJid(), id, Message.STATUS_SEND_RECEIVED);
1446            }
1447        }
1448    }
1449
1450    private void processDisplayed(
1451            final Displayed displayed,
1452            final im.conversations.android.xmpp.model.stanza.Message packet,
1453            final boolean selfAddressed,
1454            final Jid counterpart,
1455            final MessageArchiveService.Query query,
1456            final boolean isTypeGroupChat,
1457            Conversation conversation,
1458            Element mucUserElement,
1459            Jid from) {
1460        final var id = displayed.getId();
1461        // TODO we don’t even use 'sender' any more. Remove this!
1462        final Jid sender = Jid.Invalid.getNullForInvalid(displayed.getAttributeAsJid("sender"));
1463        if (packet.fromAccount(account) && !selfAddressed) {
1464            final Conversation c = mXmppConnectionService.find(account, counterpart.asBareJid());
1465            final Message message =
1466                    (c == null || id == null) ? null : c.findReceivedWithRemoteId(id);
1467            if (message != null && (query == null || query.isCatchup())) {
1468                mXmppConnectionService.markReadUpTo(c, message);
1469            }
1470            if (query == null) {
1471                activateGracePeriod(account);
1472            }
1473        } else if (isTypeGroupChat) {
1474            final Message message;
1475            if (conversation != null && id != null) {
1476                if (sender != null) {
1477                    message = conversation.findMessageWithRemoteId(id, sender);
1478                } else {
1479                    message = conversation.findMessageWithServerMsgId(id);
1480                }
1481            } else {
1482                message = null;
1483            }
1484            if (message != null) {
1485                // TODO use occupantId to extract true counterpart from presence
1486                final Jid fallback = conversation.getMucOptions().getTrueCounterpart(counterpart);
1487                // TODO try to externalize mucTrueCounterpart
1488                final Jid trueJid =
1489                        getTrueCounterpart(
1490                                (query != null && query.safeToExtractTrueCounterpart())
1491                                        ? mucUserElement
1492                                        : null,
1493                                fallback);
1494                final boolean trueJidMatchesAccount =
1495                        account.getJid()
1496                                .asBareJid()
1497                                .equals(trueJid == null ? null : trueJid.asBareJid());
1498                if (trueJidMatchesAccount || conversation.getMucOptions().isSelf(counterpart)) {
1499                    if (!message.isRead()
1500                            && (query == null || query.isCatchup())) { // checking if message is
1501                        // unread fixes race conditions
1502                        // with reflections
1503                        mXmppConnectionService.markReadUpTo(conversation, message);
1504                    }
1505                } else if (!counterpart.isBareJid() && trueJid != null) {
1506                    final ReadByMarker readByMarker = ReadByMarker.from(counterpart, trueJid);
1507                    if (message.addReadByMarker(readByMarker)) {
1508                        final var mucOptions = conversation.getMucOptions();
1509                        final var everyone = ImmutableSet.copyOf(mucOptions.getMembers(false));
1510                        final var readyBy = message.getReadyByTrue();
1511                        final var mStatus = message.getStatus();
1512                        if (mucOptions.isPrivateAndNonAnonymous()
1513                                && (mStatus == Message.STATUS_SEND_RECEIVED
1514                                        || mStatus == Message.STATUS_SEND)
1515                                && readyBy.containsAll(everyone)) {
1516                            message.setStatus(Message.STATUS_SEND_DISPLAYED);
1517                        }
1518                        mXmppConnectionService.updateMessage(message, false);
1519                    }
1520                }
1521            }
1522        } else {
1523            final Message displayedMessage =
1524                    mXmppConnectionService.markMessage(
1525                            account, from.asBareJid(), id, Message.STATUS_SEND_DISPLAYED);
1526            Message message = displayedMessage == null ? null : displayedMessage.prev();
1527            while (message != null
1528                    && message.getStatus() == Message.STATUS_SEND_RECEIVED
1529                    && message.getTimeSent() < displayedMessage.getTimeSent()) {
1530                mXmppConnectionService.markMessage(message, Message.STATUS_SEND_DISPLAYED);
1531                message = message.prev();
1532            }
1533            if (displayedMessage != null && selfAddressed) {
1534                dismissNotification(account, counterpart, query, id);
1535            }
1536        }
1537    }
1538
1539    private void processReactions(
1540            final Reactions reactions,
1541            final Conversation conversation,
1542            final boolean isTypeGroupChat,
1543            final OccupantId occupant,
1544            final Jid counterpart,
1545            final Jid mucTrueCounterPart,
1546            final im.conversations.android.xmpp.model.stanza.Message packet) {
1547        final String reactingTo = reactions.getId();
1548        if (conversation != null && reactingTo != null) {
1549            if (isTypeGroupChat && conversation.getMode() == Conversational.MODE_MULTI) {
1550                final var mucOptions = conversation.getMucOptions();
1551                final var occupantId = occupant == null ? null : occupant.getId();
1552                if (occupantId != null) {
1553                    final boolean isReceived = !mucOptions.isSelf(occupantId);
1554                    final Message message;
1555                    final var inMemoryMessage = conversation.findMessageWithServerMsgId(reactingTo);
1556                    if (inMemoryMessage != null) {
1557                        message = inMemoryMessage;
1558                    } else {
1559                        message =
1560                                mXmppConnectionService.databaseBackend.getMessageWithServerMsgId(
1561                                        conversation, reactingTo);
1562                    }
1563                    if (message != null) {
1564                        final var combinedReactions =
1565                                Reaction.withOccupantId(
1566                                        message.getReactions(),
1567                                        reactions.getReactions(),
1568                                        isReceived,
1569                                        counterpart,
1570                                        mucTrueCounterPart,
1571                                        occupantId);
1572                        message.setReactions(combinedReactions);
1573                        mXmppConnectionService.updateMessage(message, false);
1574                    } else {
1575                        Log.d(Config.LOGTAG, "message with id " + reactingTo + " not found");
1576                    }
1577                } else {
1578                    Log.d(Config.LOGTAG, "received reaction in channel w/o occupant ids. ignoring");
1579                }
1580            } else {
1581                final Message message;
1582                final var inMemoryMessage = conversation.findMessageWithUuidOrRemoteId(reactingTo);
1583                if (inMemoryMessage != null) {
1584                    message = inMemoryMessage;
1585                } else {
1586                    message =
1587                            mXmppConnectionService.databaseBackend.getMessageWithUuidOrRemoteId(
1588                                    conversation, reactingTo);
1589                }
1590                if (message == null) {
1591                    Log.d(Config.LOGTAG, "message with id " + reactingTo + " not found");
1592                    return;
1593                }
1594                final boolean isReceived;
1595                final Jid reactionFrom;
1596                if (conversation.getMode() == Conversational.MODE_MULTI) {
1597                    Log.d(Config.LOGTAG, "received reaction as MUC PM. triggering validation");
1598                    final var mucOptions = conversation.getMucOptions();
1599                    final var occupantId = occupant == null ? null : occupant.getId();
1600                    if (occupantId == null) {
1601                        Log.d(
1602                                Config.LOGTAG,
1603                                "received reaction via PM channel w/o occupant ids. ignoring");
1604                        return;
1605                    }
1606                    isReceived = !mucOptions.isSelf(occupantId);
1607                    if (isReceived) {
1608                        reactionFrom = counterpart;
1609                    } else {
1610                        if (!occupantId.equals(message.getOccupantId())) {
1611                            Log.d(
1612                                    Config.LOGTAG,
1613                                    "reaction received via MUC PM did not pass validation");
1614                            return;
1615                        }
1616                        reactionFrom = account.getJid().asBareJid();
1617                    }
1618                } else {
1619                    if (packet.fromAccount(account)) {
1620                        isReceived = false;
1621                        reactionFrom = account.getJid().asBareJid();
1622                    } else {
1623                        isReceived = true;
1624                        reactionFrom = counterpart;
1625                    }
1626                }
1627                final var combinedReactions =
1628                        Reaction.withFrom(
1629                                message.getReactions(),
1630                                reactions.getReactions(),
1631                                isReceived,
1632                                reactionFrom);
1633                message.setReactions(combinedReactions);
1634                mXmppConnectionService.updateMessage(message, false);
1635            }
1636        }
1637    }
1638
1639    private static Pair<im.conversations.android.xmpp.model.stanza.Message, Long>
1640            getForwardedMessagePacket(
1641                    final im.conversations.android.xmpp.model.stanza.Message original,
1642                    Class<? extends Extension> clazz) {
1643        final var extension = original.getExtension(clazz);
1644        final var forwarded = extension == null ? null : extension.getExtension(Forwarded.class);
1645        if (forwarded == null) {
1646            return null;
1647        }
1648        final Long timestamp = AbstractParser.parseTimestamp(forwarded, null);
1649        final var forwardedMessage = forwarded.getMessage();
1650        if (forwardedMessage == null) {
1651            return null;
1652        }
1653        return new Pair<>(forwardedMessage, timestamp);
1654    }
1655
1656    private static Pair<im.conversations.android.xmpp.model.stanza.Message, Long>
1657            getForwardedMessagePacket(
1658                    final im.conversations.android.xmpp.model.stanza.Message original,
1659                    final String name,
1660                    final String namespace) {
1661        final Element wrapper = original.findChild(name, namespace);
1662        final var forwardedElement =
1663                wrapper == null ? null : wrapper.findChild("forwarded", Namespace.FORWARD);
1664        if (forwardedElement instanceof Forwarded forwarded) {
1665            final Long timestamp = AbstractParser.parseTimestamp(forwarded, null);
1666            final var forwardedMessage = forwarded.getMessage();
1667            if (forwardedMessage == null) {
1668                return null;
1669            }
1670            return new Pair<>(forwardedMessage, timestamp);
1671        }
1672        return null;
1673    }
1674
1675    private void dismissNotification(
1676            Account account, Jid counterpart, MessageArchiveService.Query query, final String id) {
1677        final Conversation conversation =
1678                mXmppConnectionService.find(account, counterpart.asBareJid());
1679        if (conversation != null && (query == null || query.isCatchup())) {
1680            final String displayableId = conversation.findMostRecentRemoteDisplayableId();
1681            if (displayableId != null && displayableId.equals(id)) {
1682                mXmppConnectionService.markRead(conversation);
1683            } else {
1684                Log.w(
1685                        Config.LOGTAG,
1686                        account.getJid().asBareJid()
1687                                + ": received dismissing display marker that did not match our last"
1688                                + " id in that conversation");
1689            }
1690        }
1691    }
1692
1693    private void processMessageReceipts(
1694            final Account account,
1695            final im.conversations.android.xmpp.model.stanza.Message packet,
1696            final String remoteMsgId,
1697            MessageArchiveService.Query query) {
1698        final boolean markable = packet.hasChild("markable", "urn:xmpp:chat-markers:0");
1699        final boolean request = packet.hasChild("request", "urn:xmpp:receipts");
1700        if (query == null) {
1701            final ArrayList<String> receiptsNamespaces = new ArrayList<>();
1702            if (markable) {
1703                receiptsNamespaces.add("urn:xmpp:chat-markers:0");
1704            }
1705            if (request) {
1706                receiptsNamespaces.add("urn:xmpp:receipts");
1707            }
1708            if (receiptsNamespaces.size() > 0) {
1709                final var receipt =
1710                        mXmppConnectionService
1711                                .getMessageGenerator()
1712                                .received(
1713                                        account,
1714                                        packet.getFrom(),
1715                                        remoteMsgId,
1716                                        receiptsNamespaces,
1717                                        packet.getType());
1718                mXmppConnectionService.sendMessagePacket(account, receipt);
1719            }
1720        } else if (query.isCatchup()) {
1721            if (request) {
1722                query.addPendingReceiptRequest(new ReceiptRequest(packet.getFrom(), remoteMsgId));
1723            }
1724        }
1725    }
1726
1727    private void activateGracePeriod(Account account) {
1728        long duration =
1729                mXmppConnectionService.getLongPreference(
1730                                "grace_period_length", R.integer.grace_period)
1731                        * 1000;
1732        Log.d(
1733                Config.LOGTAG,
1734                account.getJid().asBareJid()
1735                        + ": activating grace period till "
1736                        + TIME_FORMAT.format(new Date(System.currentTimeMillis() + duration)));
1737        account.activateGracePeriod(duration);
1738    }
1739
1740    private class Invite {
1741        final Jid jid;
1742        final String password;
1743        final boolean direct;
1744        final Jid inviter;
1745
1746        Invite(Jid jid, String password, boolean direct, Jid inviter) {
1747            this.jid = jid;
1748            this.password = password;
1749            this.direct = direct;
1750            this.inviter = inviter;
1751        }
1752
1753        public boolean execute(final Account account) {
1754            if (this.jid == null) {
1755                return false;
1756            }
1757            final Contact contact =
1758                    this.inviter != null ? account.getRoster().getContact(this.inviter) : null;
1759            if (contact != null && contact.isBlocked()) {
1760                Log.d(
1761                        Config.LOGTAG,
1762                        account.getJid().asBareJid()
1763                                + ": ignore invite from "
1764                                + contact.getJid()
1765                                + " because contact is blocked");
1766                return false;
1767            }
1768            final AppSettings appSettings = new AppSettings(mXmppConnectionService);
1769            if ((contact != null && contact.showInContactList())
1770                    || appSettings.isAcceptInvitesFromStrangers()) {
1771                final Conversation conversation =
1772                        mXmppConnectionService.findOrCreateConversation(account, jid, true, false);
1773                if (conversation.getMucOptions().online()) {
1774                    Log.d(
1775                            Config.LOGTAG,
1776                            account.getJid().asBareJid()
1777                                    + ": received invite to "
1778                                    + jid
1779                                    + " but muc is considered to be online");
1780                    mXmppConnectionService.mucSelfPingAndRejoin(conversation);
1781                } else {
1782                    conversation.getMucOptions().setPassword(password);
1783                    mXmppConnectionService.databaseBackend.updateConversation(conversation);
1784                    mXmppConnectionService.joinMuc(
1785                            conversation, contact != null && contact.showInContactList());
1786                    mXmppConnectionService.updateConversationUi();
1787                }
1788                return true;
1789            } else {
1790                Log.d(
1791                        Config.LOGTAG,
1792                        account.getJid().asBareJid()
1793                                + ": ignoring invite from "
1794                                + this.inviter
1795                                + " because we are not accepting invites from strangers. direct="
1796                                + direct);
1797                return false;
1798            }
1799        }
1800    }
1801}