MessageParser.java

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