MessageParser.java

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