MessageParser.java

   1package eu.siacs.conversations.parser;
   2
   3import android.util.Log;
   4import android.util.Pair;
   5
   6import java.text.SimpleDateFormat;
   7import java.util.ArrayList;
   8import java.util.Arrays;
   9import java.util.Collections;
  10import java.util.Date;
  11import java.util.List;
  12import java.util.Locale;
  13import java.util.Map;
  14import java.util.Set;
  15import java.util.UUID;
  16
  17import eu.siacs.conversations.AppSettings;
  18import eu.siacs.conversations.Config;
  19import eu.siacs.conversations.R;
  20import eu.siacs.conversations.crypto.axolotl.AxolotlService;
  21import eu.siacs.conversations.crypto.axolotl.BrokenSessionException;
  22import eu.siacs.conversations.crypto.axolotl.NotEncryptedForThisDeviceException;
  23import eu.siacs.conversations.crypto.axolotl.OutdatedSenderException;
  24import eu.siacs.conversations.crypto.axolotl.XmppAxolotlMessage;
  25import eu.siacs.conversations.entities.Account;
  26import eu.siacs.conversations.entities.Bookmark;
  27import eu.siacs.conversations.entities.Contact;
  28import eu.siacs.conversations.entities.Conversation;
  29import eu.siacs.conversations.entities.Conversational;
  30import eu.siacs.conversations.entities.Message;
  31import eu.siacs.conversations.entities.MucOptions;
  32import eu.siacs.conversations.entities.ReadByMarker;
  33import eu.siacs.conversations.entities.ReceiptRequest;
  34import eu.siacs.conversations.entities.RtpSessionStatus;
  35import eu.siacs.conversations.http.HttpConnectionManager;
  36import eu.siacs.conversations.services.MessageArchiveService;
  37import eu.siacs.conversations.services.QuickConversationsService;
  38import eu.siacs.conversations.services.XmppConnectionService;
  39import eu.siacs.conversations.utils.CryptoHelper;
  40import eu.siacs.conversations.xml.Element;
  41import eu.siacs.conversations.xml.LocalizedContent;
  42import eu.siacs.conversations.xml.Namespace;
  43import eu.siacs.conversations.xmpp.InvalidJid;
  44import eu.siacs.conversations.xmpp.Jid;
  45import eu.siacs.conversations.xmpp.OnMessagePacketReceived;
  46import eu.siacs.conversations.xmpp.chatstate.ChatState;
  47import eu.siacs.conversations.xmpp.jingle.JingleConnectionManager;
  48import eu.siacs.conversations.xmpp.jingle.JingleRtpConnection;
  49import eu.siacs.conversations.xmpp.pep.Avatar;
  50import eu.siacs.conversations.xmpp.stanzas.MessagePacket;
  51
  52public class MessageParser extends AbstractParser implements OnMessagePacketReceived {
  53
  54    private static final SimpleDateFormat TIME_FORMAT = new SimpleDateFormat("HH:mm:ss", Locale.ENGLISH);
  55
  56    private static final List<String> JINGLE_MESSAGE_ELEMENT_NAMES =
  57            Arrays.asList("accept", "propose", "proceed", "reject", "retract", "ringing", "finish");
  58
  59    public MessageParser(XmppConnectionService service) {
  60        super(service);
  61    }
  62
  63    private static String extractStanzaId(Element packet, boolean isTypeGroupChat, Conversation conversation) {
  64        final Jid by;
  65        final boolean safeToExtract;
  66        if (isTypeGroupChat) {
  67            by = conversation.getJid().asBareJid();
  68            safeToExtract = conversation.getMucOptions().hasFeature(Namespace.STANZA_IDS);
  69        } else {
  70            Account account = conversation.getAccount();
  71            by = account.getJid().asBareJid();
  72            safeToExtract = account.getXmppConnection().getFeatures().stanzaIds();
  73        }
  74        return safeToExtract ? extractStanzaId(packet, by) : null;
  75    }
  76
  77    private static String extractStanzaId(Account account, Element packet) {
  78        final boolean safeToExtract = account.getXmppConnection().getFeatures().stanzaIds();
  79        return safeToExtract ? extractStanzaId(packet, account.getJid().asBareJid()) : null;
  80    }
  81
  82    private static String extractStanzaId(Element packet, Jid by) {
  83        for (Element child : packet.getChildren()) {
  84            if (child.getName().equals("stanza-id")
  85                    && Namespace.STANZA_IDS.equals(child.getNamespace())
  86                    && by.equals(InvalidJid.getNullForInvalid(child.getAttributeAsJid("by")))) {
  87                return child.getAttribute("id");
  88            }
  89        }
  90        return null;
  91    }
  92
  93    private static Jid getTrueCounterpart(Element mucUserElement, Jid fallback) {
  94        final Element item = mucUserElement == null ? null : mucUserElement.findChild("item");
  95        Jid result = item == null ? null : InvalidJid.getNullForInvalid(item.getAttributeAsJid("jid"));
  96        return result != null ? result : fallback;
  97    }
  98
  99    private boolean extractChatState(Conversation c, final boolean isTypeGroupChat, final MessagePacket packet) {
 100        ChatState state = ChatState.parse(packet);
 101        if (state != null && c != null) {
 102            final Account account = c.getAccount();
 103            final Jid from = packet.getFrom();
 104            if (from.asBareJid().equals(account.getJid().asBareJid())) {
 105                c.setOutgoingChatState(state);
 106                if (state == ChatState.ACTIVE || state == ChatState.COMPOSING) {
 107                    if (c.getContact().isSelf()) {
 108                        return false;
 109                    }
 110                    mXmppConnectionService.markRead(c);
 111                    activateGracePeriod(account);
 112                }
 113                return false;
 114            } else {
 115                if (isTypeGroupChat) {
 116                    MucOptions.User user = c.getMucOptions().findUserByFullJid(from);
 117                    if (user != null) {
 118                        return user.setChatState(state);
 119                    } else {
 120                        return false;
 121                    }
 122                } else {
 123                    return c.setIncomingChatState(state);
 124                }
 125            }
 126        }
 127        return false;
 128    }
 129
 130    private Message parseAxolotlChat(Element axolotlMessage, Jid from, Conversation conversation, int status, final boolean checkedForDuplicates, boolean postpone) {
 131        final AxolotlService service = conversation.getAccount().getAxolotlService();
 132        final XmppAxolotlMessage xmppAxolotlMessage;
 133        try {
 134            xmppAxolotlMessage = XmppAxolotlMessage.fromElement(axolotlMessage, from.asBareJid());
 135        } catch (Exception e) {
 136            Log.d(Config.LOGTAG, conversation.getAccount().getJid().asBareJid() + ": invalid omemo message received " + e.getMessage());
 137            return null;
 138        }
 139        if (xmppAxolotlMessage.hasPayload()) {
 140            final XmppAxolotlMessage.XmppAxolotlPlaintextMessage plaintextMessage;
 141            try {
 142                plaintextMessage = service.processReceivingPayloadMessage(xmppAxolotlMessage, postpone);
 143            } catch (BrokenSessionException e) {
 144                if (checkedForDuplicates) {
 145                    if (service.trustedOrPreviouslyResponded(from.asBareJid())) {
 146                        service.reportBrokenSessionException(e, postpone);
 147                        return new Message(conversation, "", Message.ENCRYPTION_AXOLOTL_FAILED, status);
 148                    } else {
 149                        Log.d(Config.LOGTAG, "ignoring broken session exception because contact was not trusted");
 150                        return new Message(conversation, "", Message.ENCRYPTION_AXOLOTL_FAILED, status);
 151                    }
 152                } else {
 153                    Log.d(Config.LOGTAG, "ignoring broken session exception because checkForDuplicates failed");
 154                    return null;
 155                }
 156            } catch (NotEncryptedForThisDeviceException e) {
 157                return new Message(conversation, "", Message.ENCRYPTION_AXOLOTL_NOT_FOR_THIS_DEVICE, status);
 158            } catch (OutdatedSenderException e) {
 159                return new Message(conversation, "", Message.ENCRYPTION_AXOLOTL_FAILED, status);
 160            }
 161            if (plaintextMessage != null) {
 162                Message finishedMessage = new Message(conversation, plaintextMessage.getPlaintext(), Message.ENCRYPTION_AXOLOTL, status);
 163                finishedMessage.setFingerprint(plaintextMessage.getFingerprint());
 164                Log.d(Config.LOGTAG, AxolotlService.getLogprefix(finishedMessage.getConversation().getAccount()) + " Received Message with session fingerprint: " + plaintextMessage.getFingerprint());
 165                return finishedMessage;
 166            }
 167        } else {
 168            Log.d(Config.LOGTAG, conversation.getAccount().getJid().asBareJid() + ": received OMEMO key transport message");
 169            service.processReceivingKeyTransportMessage(xmppAxolotlMessage, postpone);
 170        }
 171        return null;
 172    }
 173
 174    private Invite extractInvite(final Element message) {
 175        final Element mucUser = message.findChild("x", Namespace.MUC_USER);
 176        if (mucUser != null) {
 177            final Element invite = mucUser.findChild("invite");
 178            if (invite != null) {
 179                final String password = mucUser.findChildContent("password");
 180                final Jid from = InvalidJid.getNullForInvalid(invite.getAttributeAsJid("from"));
 181                final Jid to = InvalidJid.getNullForInvalid(invite.getAttributeAsJid("to"));
 182                if (to != null && from == null) {
 183                    Log.d(Config.LOGTAG,"do not parse outgoing mediated invite "+message);
 184                    return null;
 185                }
 186                final Jid room = InvalidJid.getNullForInvalid(message.getAttributeAsJid("from"));
 187                if (room == null) {
 188                    return null;
 189                }
 190                return new Invite(room, password, false, from);
 191            }
 192        }
 193        final Element conference = message.findChild("x", "jabber:x:conference");
 194        if (conference != null) {
 195            Jid from = InvalidJid.getNullForInvalid(message.getAttributeAsJid("from"));
 196            Jid room = InvalidJid.getNullForInvalid(conference.getAttributeAsJid("jid"));
 197            if (room == null) {
 198                return null;
 199            }
 200            return new Invite(room, conference.getAttribute("password"), true, from);
 201        }
 202        return null;
 203    }
 204
 205    private void parseEvent(final Element event, final Jid from, final Account account) {
 206        final Element items = event.findChild("items");
 207        final String node = items == null ? null : items.getAttribute("node");
 208        if ("urn:xmpp:avatar:metadata".equals(node)) {
 209            Avatar avatar = Avatar.parseMetadata(items);
 210            if (avatar != null) {
 211                avatar.owner = from.asBareJid();
 212                if (mXmppConnectionService.getFileBackend().isAvatarCached(avatar)) {
 213                    if (account.getJid().asBareJid().equals(from)) {
 214                        if (account.setAvatar(avatar.getFilename())) {
 215                            mXmppConnectionService.databaseBackend.updateAccount(account);
 216                            mXmppConnectionService.notifyAccountAvatarHasChanged(account);
 217                        }
 218                        mXmppConnectionService.getAvatarService().clear(account);
 219                        mXmppConnectionService.updateConversationUi();
 220                        mXmppConnectionService.updateAccountUi();
 221                    } else {
 222                        final Contact contact = account.getRoster().getContact(from);
 223                        contact.setAvatar(avatar);
 224                        mXmppConnectionService.syncRoster(account);
 225                        mXmppConnectionService.getAvatarService().clear(contact);
 226                        mXmppConnectionService.updateConversationUi();
 227                        mXmppConnectionService.updateRosterUi();
 228                    }
 229                } else if (mXmppConnectionService.isDataSaverDisabled()) {
 230                    mXmppConnectionService.fetchAvatar(account, avatar);
 231                }
 232            }
 233        } else if (Namespace.NICK.equals(node)) {
 234            final Element i = items.findChild("item");
 235            final String nick = i == null ? null : i.findChildContent("nick", Namespace.NICK);
 236            if (nick != null) {
 237                setNick(account, from, nick);
 238            }
 239        } else if (AxolotlService.PEP_DEVICE_LIST.equals(node)) {
 240            Element item = items.findChild("item");
 241            Set<Integer> deviceIds = mXmppConnectionService.getIqParser().deviceIds(item);
 242            Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Received PEP device list " + deviceIds + " update from " + from + ", processing... ");
 243            final AxolotlService axolotlService = account.getAxolotlService();
 244            axolotlService.registerDevices(from, deviceIds);
 245        } else if (Namespace.BOOKMARKS.equals(node) && account.getJid().asBareJid().equals(from)) {
 246            if (account.getXmppConnection().getFeatures().bookmarksConversion()) {
 247                final Element i = items.findChild("item");
 248                final Element storage = i == null ? null : i.findChild("storage", Namespace.BOOKMARKS);
 249                Map<Jid, Bookmark> bookmarks = Bookmark.parseFromStorage(storage, account);
 250                mXmppConnectionService.processBookmarksInitial(account, bookmarks, true);
 251                Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": processing bookmark PEP event");
 252            } else {
 253                Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": ignoring bookmark PEP event because bookmark conversion was not detected");
 254            }
 255        } else if (Namespace.BOOKMARKS2.equals(node) && account.getJid().asBareJid().equals(from)) {
 256            final Element item = items.findChild("item");
 257            final Element retract = items.findChild("retract");
 258            if (item != null) {
 259                final Bookmark bookmark = Bookmark.parseFromItem(item, account);
 260                if (bookmark != null) {
 261                    account.putBookmark(bookmark);
 262                    mXmppConnectionService.processModifiedBookmark(bookmark);
 263                    mXmppConnectionService.updateConversationUi();
 264                }
 265            }
 266            if (retract != null) {
 267                final Jid id = InvalidJid.getNullForInvalid(retract.getAttributeAsJid("id"));
 268                if (id != null) {
 269                    account.removeBookmark(id);
 270                    Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": deleted bookmark for " + id);
 271                    mXmppConnectionService.processDeletedBookmark(account, id);
 272                    mXmppConnectionService.updateConversationUi();
 273                }
 274            }
 275        } else if (Config.MESSAGE_DISPLAYED_SYNCHRONIZATION
 276                && Namespace.MDS_DISPLAYED.equals(node)
 277                && account.getJid().asBareJid().equals(from)) {
 278            final Element item = items.findChild("item");
 279            mXmppConnectionService.processMdsItem(account, item);
 280        } else {
 281            Log.d(
 282                    Config.LOGTAG,
 283                    account.getJid().asBareJid()
 284                            + " received pubsub notification for node="
 285                            + node);
 286        }
 287    }
 288
 289    private void parseDeleteEvent(final Element event, final Jid from, final Account account) {
 290        final Element delete = event.findChild("delete");
 291        final String node = delete == null ? null : delete.getAttribute("node");
 292        if (Namespace.NICK.equals(node)) {
 293            Log.d(Config.LOGTAG, "parsing nick delete event from " + from);
 294            setNick(account, from, null);
 295        } else if (Namespace.BOOKMARKS2.equals(node) && account.getJid().asBareJid().equals(from)) {
 296            account.setBookmarks(Collections.emptyMap());
 297            Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": deleted bookmarks node");
 298        } else if (Namespace.AVATAR_METADATA.equals(node) && account.getJid().asBareJid().equals(from)) {
 299            Log.d(Config.LOGTAG,account.getJid().asBareJid()+": deleted avatar metadata node");
 300        }
 301    }
 302
 303    private void parsePurgeEvent(final Element event, final Jid from, final Account account) {
 304        final Element purge = event.findChild("purge");
 305        final String node = purge == null ? null : purge.getAttribute("node");
 306        if (Namespace.BOOKMARKS2.equals(node) && account.getJid().asBareJid().equals(from)) {
 307            account.setBookmarks(Collections.emptyMap());
 308            Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": purged bookmarks");
 309        }
 310    }
 311
 312    private void setNick(Account account, Jid user, String nick) {
 313        if (user.asBareJid().equals(account.getJid().asBareJid())) {
 314            account.setDisplayName(nick);
 315            if (QuickConversationsService.isQuicksy()) {
 316                mXmppConnectionService.getAvatarService().clear(account);
 317            }
 318        } else {
 319            Contact contact = account.getRoster().getContact(user);
 320            if (contact.setPresenceName(nick)) {
 321                mXmppConnectionService.syncRoster(account);
 322                mXmppConnectionService.getAvatarService().clear(contact);
 323            }
 324        }
 325        mXmppConnectionService.updateConversationUi();
 326        mXmppConnectionService.updateAccountUi();
 327    }
 328
 329    private boolean handleErrorMessage(final Account account, final MessagePacket packet) {
 330        if (packet.getType() == MessagePacket.TYPE_ERROR) {
 331            if (packet.fromServer(account)) {
 332                final Pair<MessagePacket, Long> forwarded = packet.getForwardedMessagePacket("received", Namespace.CARBONS);
 333                if (forwarded != null) {
 334                    return handleErrorMessage(account, forwarded.first);
 335                }
 336            }
 337            final Jid from = packet.getFrom();
 338            final String id = packet.getId();
 339            if (from != null && id != null) {
 340                if (id.startsWith(JingleRtpConnection.JINGLE_MESSAGE_PROPOSE_ID_PREFIX)) {
 341                    final String sessionId = id.substring(JingleRtpConnection.JINGLE_MESSAGE_PROPOSE_ID_PREFIX.length());
 342                    mXmppConnectionService.getJingleConnectionManager()
 343                            .updateProposedSessionDiscovered(account, from, sessionId, JingleConnectionManager.DeviceDiscoveryState.FAILED);
 344                    return true;
 345                }
 346                if (id.startsWith(JingleRtpConnection.JINGLE_MESSAGE_PROCEED_ID_PREFIX)) {
 347                    final String sessionId = id.substring(JingleRtpConnection.JINGLE_MESSAGE_PROCEED_ID_PREFIX.length());
 348                    final String message = extractErrorMessage(packet);
 349                    mXmppConnectionService.getJingleConnectionManager().failProceed(account, from, sessionId, message);
 350                    return true;
 351                }
 352                mXmppConnectionService.markMessage(account,
 353                        from.asBareJid(),
 354                        id,
 355                        Message.STATUS_SEND_FAILED,
 356                        extractErrorMessage(packet));
 357                final Element error = packet.findChild("error");
 358                final boolean pingWorthyError = error != null && (error.hasChild("not-acceptable") || error.hasChild("remote-server-timeout") || error.hasChild("remote-server-not-found"));
 359                if (pingWorthyError) {
 360                    Conversation conversation = mXmppConnectionService.find(account, from);
 361                    if (conversation != null && conversation.getMode() == Conversational.MODE_MULTI) {
 362                        if (conversation.getMucOptions().online()) {
 363                            Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": received ping worthy error for seemingly online muc at " + from);
 364                            mXmppConnectionService.mucSelfPingAndRejoin(conversation);
 365                        }
 366                    }
 367                }
 368            }
 369            return true;
 370        }
 371        return false;
 372    }
 373
 374    @Override
 375    public void onMessagePacketReceived(Account account, MessagePacket original) {
 376        if (handleErrorMessage(account, original)) {
 377            return;
 378        }
 379        final MessagePacket packet;
 380        Long timestamp = null;
 381        boolean isCarbon = false;
 382        String serverMsgId = null;
 383        final Element fin = original.findChild("fin", MessageArchiveService.Version.MAM_0.namespace);
 384        if (fin != null) {
 385            mXmppConnectionService.getMessageArchiveService().processFinLegacy(fin, original.getFrom());
 386            return;
 387        }
 388        final Element result = MessageArchiveService.Version.findResult(original);
 389        final String queryId = result == null ? null : result.getAttribute("queryid");
 390        final MessageArchiveService.Query query = queryId == null ? null : mXmppConnectionService.getMessageArchiveService().findQuery(queryId);
 391        final boolean offlineMessagesRetrieved = account.getXmppConnection().isOfflineMessagesRetrieved();
 392        if (query != null && query.validFrom(original.getFrom())) {
 393            final Pair<MessagePacket, Long> f = original.getForwardedMessagePacket("result", query.version.namespace);
 394            if (f == null) {
 395                return;
 396            }
 397            timestamp = f.second;
 398            packet = f.first;
 399            serverMsgId = result.getAttribute("id");
 400            query.incrementMessageCount();
 401            if (handleErrorMessage(account, packet)) {
 402                return;
 403            }
 404        } else if (query != null) {
 405            Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": received mam result with invalid from (" + original.getFrom() + ") or queryId (" + queryId + ")");
 406            return;
 407        } else if (original.fromServer(account)) {
 408            Pair<MessagePacket, Long> f;
 409            f = original.getForwardedMessagePacket("received", Namespace.CARBONS);
 410            f = f == null ? original.getForwardedMessagePacket("sent", Namespace.CARBONS) : f;
 411            packet = f != null ? f.first : original;
 412            if (handleErrorMessage(account, packet)) {
 413                return;
 414            }
 415            timestamp = f != null ? f.second : null;
 416            isCarbon = f != null;
 417        } else {
 418            packet = original;
 419        }
 420
 421        if (timestamp == null) {
 422            timestamp = AbstractParser.parseTimestamp(original, AbstractParser.parseTimestamp(packet));
 423        }
 424        final LocalizedContent body = packet.getBody();
 425        final Element mucUserElement = packet.findChild("x", Namespace.MUC_USER);
 426        final String pgpEncrypted = packet.findChildContent("x", "jabber:x:encrypted");
 427        final Element replaceElement = packet.findChild("replace", "urn:xmpp:message-correct:0");
 428        final Element oob = packet.findChild("x", Namespace.OOB);
 429        final String oobUrl = oob != null ? oob.findChildContent("url") : null;
 430        final String replacementId = replaceElement == null ? null : replaceElement.getAttribute("id");
 431        final Element axolotlEncrypted = packet.findChildEnsureSingle(XmppAxolotlMessage.CONTAINERTAG, AxolotlService.PEP_PREFIX);
 432        int status;
 433        final Jid counterpart;
 434        final Jid to = packet.getTo();
 435        final Jid from = packet.getFrom();
 436        final Element originId = packet.findChild("origin-id", Namespace.STANZA_IDS);
 437        final String remoteMsgId;
 438        if (originId != null && originId.getAttribute("id") != null) {
 439            remoteMsgId = originId.getAttribute("id");
 440        } else {
 441            remoteMsgId = packet.getId();
 442        }
 443        boolean notify = false;
 444
 445        if (from == null || !InvalidJid.isValid(from) || !InvalidJid.isValid(to)) {
 446            Log.e(Config.LOGTAG, "encountered invalid message from='" + from + "' to='" + to + "'");
 447            return;
 448        }
 449
 450        boolean isTypeGroupChat = packet.getType() == MessagePacket.TYPE_GROUPCHAT;
 451        if (query != null && !query.muc() && isTypeGroupChat) {
 452            Log.e(Config.LOGTAG, account.getJid().asBareJid() + ": received groupchat (" + from + ") message on regular MAM request. skipping");
 453            return;
 454        }
 455        boolean isMucStatusMessage = InvalidJid.hasValidFrom(packet) && from.isBareJid() && mucUserElement != null && mucUserElement.hasChild("status");
 456        boolean selfAddressed;
 457        if (packet.fromAccount(account)) {
 458            status = Message.STATUS_SEND;
 459            selfAddressed = to == null || account.getJid().asBareJid().equals(to.asBareJid());
 460            if (selfAddressed) {
 461                counterpart = from;
 462            } else {
 463                counterpart = to;
 464            }
 465        } else {
 466            status = Message.STATUS_RECEIVED;
 467            counterpart = from;
 468            selfAddressed = false;
 469        }
 470
 471        final Invite invite = extractInvite(packet);
 472        if (invite != null) {
 473            if (invite.jid.asBareJid().equals(account.getJid().asBareJid())) {
 474                Log.d(Config.LOGTAG,account.getJid().asBareJid()+": ignore invite to "+invite.jid+" because it matches account");
 475            } else if (isTypeGroupChat) {
 476                Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": ignoring invite to " + invite.jid + " because it was received as group chat");
 477            } else if (invite.direct && (mucUserElement != null || invite.inviter == null || mXmppConnectionService.isMuc(account, invite.inviter))) {
 478                Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": ignoring direct invite to " + invite.jid + " because it was received in MUC");
 479            } else {
 480                invite.execute(account);
 481                return;
 482            }
 483        }
 484
 485        if ((body != null || pgpEncrypted != null || (axolotlEncrypted != null && axolotlEncrypted.hasChild("payload")) || oobUrl != null) && !isMucStatusMessage) {
 486            final boolean conversationIsProbablyMuc = isTypeGroupChat || mucUserElement != null || account.getXmppConnection().getMucServersWithholdAccount().contains(counterpart.getDomain().toEscapedString());
 487            final Conversation conversation = mXmppConnectionService.findOrCreateConversation(account, counterpart.asBareJid(), conversationIsProbablyMuc, false, query, false);
 488            final boolean conversationMultiMode = conversation.getMode() == Conversation.MODE_MULTI;
 489
 490            if (serverMsgId == null) {
 491                serverMsgId = extractStanzaId(packet, isTypeGroupChat, conversation);
 492            }
 493
 494
 495            if (selfAddressed) {
 496                if (mXmppConnectionService.markMessage(conversation, remoteMsgId, Message.STATUS_SEND_RECEIVED, serverMsgId)) {
 497                    return;
 498                }
 499                status = Message.STATUS_RECEIVED;
 500                if (remoteMsgId != null && conversation.findMessageWithRemoteId(remoteMsgId, counterpart) != null) {
 501                    return;
 502                }
 503            }
 504
 505            if (isTypeGroupChat) {
 506                if (conversation.getMucOptions().isSelf(counterpart)) {
 507                    status = Message.STATUS_SEND_RECEIVED;
 508                    isCarbon = true; //not really carbon but received from another resource
 509                    if (mXmppConnectionService.markMessage(conversation, remoteMsgId, status, serverMsgId, body)) {
 510                        return;
 511                    } else if (remoteMsgId == null || Config.IGNORE_ID_REWRITE_IN_MUC) {
 512                        if (body != null) {
 513                            Message message = conversation.findSentMessageWithBody(body.content);
 514                            if (message != null) {
 515                                mXmppConnectionService.markMessage(message, status);
 516                                return;
 517                            }
 518                        }
 519                    }
 520                } else {
 521                    status = Message.STATUS_RECEIVED;
 522                }
 523            }
 524            final Message message;
 525            if (pgpEncrypted != null && Config.supportOpenPgp()) {
 526                message = new Message(conversation, pgpEncrypted, Message.ENCRYPTION_PGP, status);
 527            } else if (axolotlEncrypted != null && Config.supportOmemo()) {
 528                Jid origin;
 529                Set<Jid> fallbacksBySourceId = Collections.emptySet();
 530                if (conversationMultiMode) {
 531                    final Jid fallback = conversation.getMucOptions().getTrueCounterpart(counterpart);
 532                    origin = getTrueCounterpart(query != null ? mucUserElement : null, fallback);
 533                    if (origin == null) {
 534                        try {
 535                            fallbacksBySourceId = account.getAxolotlService().findCounterpartsBySourceId(XmppAxolotlMessage.parseSourceId(axolotlEncrypted));
 536                        } catch (IllegalArgumentException e) {
 537                            //ignoring
 538                        }
 539                    }
 540                    if (origin == null && fallbacksBySourceId.size() == 0) {
 541                        Log.d(Config.LOGTAG, "axolotl message in anonymous conference received and no possible fallbacks");
 542                        return;
 543                    }
 544                } else {
 545                    fallbacksBySourceId = Collections.emptySet();
 546                    origin = from;
 547                }
 548
 549                final boolean liveMessage = query == null && !isTypeGroupChat && mucUserElement == null;
 550                final boolean checkedForDuplicates = liveMessage || (serverMsgId != null && remoteMsgId != null && !conversation.possibleDuplicate(serverMsgId, remoteMsgId));
 551
 552                if (origin != null) {
 553                    message = parseAxolotlChat(axolotlEncrypted, origin, conversation, status, checkedForDuplicates, query != null);
 554                } else {
 555                    Message trial = null;
 556                    for (Jid fallback : fallbacksBySourceId) {
 557                        trial = parseAxolotlChat(axolotlEncrypted, fallback, conversation, status, checkedForDuplicates && fallbacksBySourceId.size() == 1, query != null);
 558                        if (trial != null) {
 559                            Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": decoded muc message using fallback");
 560                            origin = fallback;
 561                            break;
 562                        }
 563                    }
 564                    message = trial;
 565                }
 566                if (message == null) {
 567                    if (query == null && extractChatState(mXmppConnectionService.find(account, counterpart.asBareJid()), isTypeGroupChat, packet)) {
 568                        mXmppConnectionService.updateConversationUi();
 569                    }
 570                    if (query != null && status == Message.STATUS_SEND && remoteMsgId != null) {
 571                        Message previouslySent = conversation.findSentMessageWithUuid(remoteMsgId);
 572                        if (previouslySent != null && previouslySent.getServerMsgId() == null && serverMsgId != null) {
 573                            previouslySent.setServerMsgId(serverMsgId);
 574                            mXmppConnectionService.databaseBackend.updateMessage(previouslySent, false);
 575                            Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": encountered previously sent OMEMO message without serverId. updating...");
 576                        }
 577                    }
 578                    return;
 579                }
 580                if (conversationMultiMode) {
 581                    message.setTrueCounterpart(origin);
 582                }
 583            } else if (body == null && oobUrl != null) {
 584                message = new Message(conversation, oobUrl, Message.ENCRYPTION_NONE, status);
 585                message.setOob(true);
 586                if (CryptoHelper.isPgpEncryptedUrl(oobUrl)) {
 587                    message.setEncryption(Message.ENCRYPTION_DECRYPTED);
 588                }
 589            } else {
 590                message = new Message(conversation, body.content, Message.ENCRYPTION_NONE, status);
 591                if (body.count > 1) {
 592                    message.setBodyLanguage(body.language);
 593                }
 594            }
 595
 596            message.setCounterpart(counterpart);
 597            message.setRemoteMsgId(remoteMsgId);
 598            message.setServerMsgId(serverMsgId);
 599            message.setCarbon(isCarbon);
 600            message.setTime(timestamp);
 601            if (body != null && body.content != null && body.content.equals(oobUrl)) {
 602                message.setOob(true);
 603                if (CryptoHelper.isPgpEncryptedUrl(oobUrl)) {
 604                    message.setEncryption(Message.ENCRYPTION_DECRYPTED);
 605                }
 606            }
 607            message.markable = packet.hasChild("markable", "urn:xmpp:chat-markers:0");
 608            if (conversationMultiMode) {
 609                message.setMucUser(conversation.getMucOptions().findUserByFullJid(counterpart));
 610                final Jid fallback = conversation.getMucOptions().getTrueCounterpart(counterpart);
 611                Jid trueCounterpart;
 612                if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL) {
 613                    trueCounterpart = message.getTrueCounterpart();
 614                } else if (query != null && query.safeToExtractTrueCounterpart()) {
 615                    trueCounterpart = getTrueCounterpart(mucUserElement, fallback);
 616                } else {
 617                    trueCounterpart = fallback;
 618                }
 619                if (trueCounterpart != null && isTypeGroupChat) {
 620                    if (trueCounterpart.asBareJid().equals(account.getJid().asBareJid())) {
 621                        status = isTypeGroupChat ? Message.STATUS_SEND_RECEIVED : Message.STATUS_SEND;
 622                    } else {
 623                        status = Message.STATUS_RECEIVED;
 624                        message.setCarbon(false);
 625                    }
 626                }
 627                message.setStatus(status);
 628                message.setTrueCounterpart(trueCounterpart);
 629                if (!isTypeGroupChat) {
 630                    message.setType(Message.TYPE_PRIVATE);
 631                }
 632            } else {
 633                updateLastseen(account, from);
 634            }
 635
 636            if (replacementId != null && mXmppConnectionService.allowMessageCorrection()) {
 637                final Message replacedMessage = conversation.findMessageWithRemoteIdAndCounterpart(replacementId,
 638                        counterpart,
 639                        message.getStatus() == Message.STATUS_RECEIVED,
 640                        message.isCarbon());
 641                if (replacedMessage != null) {
 642                    final boolean fingerprintsMatch = replacedMessage.getFingerprint() == null
 643                            || replacedMessage.getFingerprint().equals(message.getFingerprint());
 644                    final boolean trueCountersMatch = replacedMessage.getTrueCounterpart() != null
 645                            && message.getTrueCounterpart() != null
 646                            && replacedMessage.getTrueCounterpart().asBareJid().equals(message.getTrueCounterpart().asBareJid());
 647                    final boolean mucUserMatches = query == null && replacedMessage.sameMucUser(message); //can not be checked when using mam
 648                    final boolean duplicate = conversation.hasDuplicateMessage(message);
 649                    if (fingerprintsMatch && (trueCountersMatch || !conversationMultiMode || mucUserMatches) && !duplicate) {
 650                        Log.d(Config.LOGTAG, "replaced message '" + replacedMessage.getBody() + "' with '" + message.getBody() + "'");
 651                        synchronized (replacedMessage) {
 652                            final String uuid = replacedMessage.getUuid();
 653                            replacedMessage.setUuid(UUID.randomUUID().toString());
 654                            replacedMessage.setBody(message.getBody());
 655                            replacedMessage.putEdited(replacedMessage.getRemoteMsgId(), replacedMessage.getServerMsgId());
 656                            replacedMessage.setRemoteMsgId(remoteMsgId);
 657                            if (replacedMessage.getServerMsgId() == null || message.getServerMsgId() != null) {
 658                                replacedMessage.setServerMsgId(message.getServerMsgId());
 659                            }
 660                            replacedMessage.setEncryption(message.getEncryption());
 661                            if (replacedMessage.getStatus() == Message.STATUS_RECEIVED) {
 662                                replacedMessage.markUnread();
 663                            }
 664                            extractChatState(mXmppConnectionService.find(account, counterpart.asBareJid()), isTypeGroupChat, packet);
 665                            mXmppConnectionService.updateMessage(replacedMessage, uuid);
 666                            if (mXmppConnectionService.confirmMessages()
 667                                    && replacedMessage.getStatus() == Message.STATUS_RECEIVED
 668                                    && (replacedMessage.trusted() || replacedMessage.isPrivateMessage()) //TODO do we really want to send receipts for all PMs?
 669                                    && remoteMsgId != null
 670                                    && !selfAddressed
 671                                    && !isTypeGroupChat) {
 672                                processMessageReceipts(account, packet, remoteMsgId, query);
 673                            }
 674                            if (replacedMessage.getEncryption() == Message.ENCRYPTION_PGP) {
 675                                conversation.getAccount().getPgpDecryptionService().discard(replacedMessage);
 676                                conversation.getAccount().getPgpDecryptionService().decrypt(replacedMessage, false);
 677                            }
 678                        }
 679                        mXmppConnectionService.getNotificationService().updateNotification();
 680                        return;
 681                    } else {
 682                        Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": received message correction but verification didn't check out");
 683                    }
 684                }
 685            }
 686
 687            long deletionDate = mXmppConnectionService.getAutomaticMessageDeletionDate();
 688            if (deletionDate != 0 && message.getTimeSent() < deletionDate) {
 689                Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": skipping message from " + message.getCounterpart().toString() + " because it was sent prior to our deletion date");
 690                return;
 691            }
 692
 693            boolean checkForDuplicates = (isTypeGroupChat && packet.hasChild("delay", "urn:xmpp:delay"))
 694                    || message.isPrivateMessage()
 695                    || message.getServerMsgId() != null
 696                    || (query == null && mXmppConnectionService.getMessageArchiveService().isCatchupInProgress(conversation));
 697            if (checkForDuplicates) {
 698                final Message duplicate = conversation.findDuplicateMessage(message);
 699                if (duplicate != null) {
 700                    final boolean serverMsgIdUpdated;
 701                    if (duplicate.getStatus() != Message.STATUS_RECEIVED
 702                            && duplicate.getUuid().equals(message.getRemoteMsgId())
 703                            && duplicate.getServerMsgId() == null
 704                            && message.getServerMsgId() != null) {
 705                        duplicate.setServerMsgId(message.getServerMsgId());
 706                        if (mXmppConnectionService.databaseBackend.updateMessage(duplicate, false)) {
 707                            serverMsgIdUpdated = true;
 708                        } else {
 709                            serverMsgIdUpdated = false;
 710                            Log.e(Config.LOGTAG, "failed to update message");
 711                        }
 712                    } else {
 713                        serverMsgIdUpdated = false;
 714                    }
 715                    Log.d(Config.LOGTAG, "skipping duplicate message with " + message.getCounterpart() + ". serverMsgIdUpdated=" + serverMsgIdUpdated);
 716                    return;
 717                }
 718            }
 719
 720            if (query != null && query.getPagingOrder() == MessageArchiveService.PagingOrder.REVERSE) {
 721                conversation.prepend(query.getActualInThisQuery(), message);
 722            } else {
 723                conversation.add(message);
 724            }
 725            if (query != null) {
 726                query.incrementActualMessageCount();
 727            }
 728
 729            if (query == null || query.isCatchup()) { //either no mam or catchup
 730                if (status == Message.STATUS_SEND || status == Message.STATUS_SEND_RECEIVED) {
 731                    mXmppConnectionService.markRead(conversation);
 732                    if (query == null) {
 733                        activateGracePeriod(account);
 734                    }
 735                } else {
 736                    message.markUnread();
 737                    notify = true;
 738                }
 739            }
 740
 741            if (message.getEncryption() == Message.ENCRYPTION_PGP) {
 742                notify = conversation.getAccount().getPgpDecryptionService().decrypt(message, notify);
 743            } else if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL_NOT_FOR_THIS_DEVICE || message.getEncryption() == Message.ENCRYPTION_AXOLOTL_FAILED) {
 744                notify = false;
 745            }
 746
 747            if (query == null) {
 748                extractChatState(mXmppConnectionService.find(account, counterpart.asBareJid()), isTypeGroupChat, packet);
 749                mXmppConnectionService.updateConversationUi();
 750            }
 751
 752            if (mXmppConnectionService.confirmMessages()
 753                    && message.getStatus() == Message.STATUS_RECEIVED
 754                    && (message.trusted() || message.isPrivateMessage())
 755                    && remoteMsgId != null
 756                    && !selfAddressed
 757                    && !isTypeGroupChat) {
 758                processMessageReceipts(account, packet, remoteMsgId, query);
 759            }
 760
 761            mXmppConnectionService.databaseBackend.createMessage(message);
 762            final HttpConnectionManager manager = this.mXmppConnectionService.getHttpConnectionManager();
 763            if (message.trusted() && message.treatAsDownloadable() && manager.getAutoAcceptFileSize() > 0) {
 764                manager.createNewDownloadConnection(message);
 765            } else if (notify) {
 766                if (query != null && query.isCatchup()) {
 767                    mXmppConnectionService.getNotificationService().pushFromBacklog(message);
 768                } else {
 769                    mXmppConnectionService.getNotificationService().push(message);
 770                }
 771            }
 772        } else if (!packet.hasChild("body")) { //no body
 773
 774            final Conversation conversation = mXmppConnectionService.find(account, from.asBareJid());
 775            if (axolotlEncrypted != null) {
 776                Jid origin;
 777                if (conversation != null && conversation.getMode() == Conversation.MODE_MULTI) {
 778                    final Jid fallback = conversation.getMucOptions().getTrueCounterpart(counterpart);
 779                    origin = getTrueCounterpart(query != null ? mucUserElement : null, fallback);
 780                    if (origin == null) {
 781                        Log.d(Config.LOGTAG, "omemo key transport message in anonymous conference received");
 782                        return;
 783                    }
 784                } else if (isTypeGroupChat) {
 785                    return;
 786                } else {
 787                    origin = from;
 788                }
 789                try {
 790                    final XmppAxolotlMessage xmppAxolotlMessage = XmppAxolotlMessage.fromElement(axolotlEncrypted, origin.asBareJid());
 791                    account.getAxolotlService().processReceivingKeyTransportMessage(xmppAxolotlMessage, query != null);
 792                    Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": omemo key transport message received from " + origin);
 793                } catch (Exception e) {
 794                    Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": invalid omemo key transport message received " + e.getMessage());
 795                    return;
 796                }
 797            }
 798
 799            if (query == null && extractChatState(mXmppConnectionService.find(account, counterpart.asBareJid()), isTypeGroupChat, packet)) {
 800                mXmppConnectionService.updateConversationUi();
 801            }
 802
 803            if (isTypeGroupChat) {
 804                if (packet.hasChild("subject") && !packet.hasChild("thread")) { // We already know it has no body per above
 805                    if (conversation != null && conversation.getMode() == Conversation.MODE_MULTI) {
 806                        conversation.setHasMessagesLeftOnServer(conversation.countMessages() > 0);
 807                        final LocalizedContent subject = packet.findInternationalizedChildContentInDefaultNamespace("subject");
 808                        if (subject != null && conversation.getMucOptions().setSubject(subject.content)) {
 809                            mXmppConnectionService.updateConversation(conversation);
 810                        }
 811                        mXmppConnectionService.updateConversationUi();
 812                        return;
 813                    }
 814                }
 815            }
 816            if (conversation != null && mucUserElement != null && InvalidJid.hasValidFrom(packet) && from.isBareJid()) {
 817                for (Element child : mucUserElement.getChildren()) {
 818                    if ("status".equals(child.getName())) {
 819                        try {
 820                            int code = Integer.parseInt(child.getAttribute("code"));
 821                            if ((code >= 170 && code <= 174) || (code >= 102 && code <= 104)) {
 822                                mXmppConnectionService.fetchConferenceConfiguration(conversation);
 823                                break;
 824                            }
 825                        } catch (Exception e) {
 826                            //ignored
 827                        }
 828                    } else if ("item".equals(child.getName())) {
 829                        MucOptions.User user = AbstractParser.parseItem(conversation, child);
 830                        Log.d(Config.LOGTAG, account.getJid() + ": changing affiliation for "
 831                                + user.getRealJid() + " to " + user.getAffiliation() + " in "
 832                                + conversation.getJid().asBareJid());
 833                        if (!user.realJidMatchesAccount()) {
 834                            boolean isNew = conversation.getMucOptions().updateUser(user);
 835                            mXmppConnectionService.getAvatarService().clear(conversation);
 836                            mXmppConnectionService.updateMucRosterUi();
 837                            mXmppConnectionService.updateConversationUi();
 838                            Contact contact = user.getContact();
 839                            if (!user.getAffiliation().ranks(MucOptions.Affiliation.MEMBER)) {
 840                                Jid jid = user.getRealJid();
 841                                List<Jid> cryptoTargets = conversation.getAcceptedCryptoTargets();
 842                                if (cryptoTargets.remove(user.getRealJid())) {
 843                                    Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": removed " + jid + " from crypto targets of " + conversation.getName());
 844                                    conversation.setAcceptedCryptoTargets(cryptoTargets);
 845                                    mXmppConnectionService.updateConversation(conversation);
 846                                }
 847                            } else if (isNew
 848                                    && user.getRealJid() != null
 849                                    && conversation.getMucOptions().isPrivateAndNonAnonymous()
 850                                    && (contact == null || !contact.mutualPresenceSubscription())
 851                                    && account.getAxolotlService().hasEmptyDeviceList(user.getRealJid())) {
 852                                account.getAxolotlService().fetchDeviceIds(user.getRealJid());
 853                            }
 854                        }
 855                    }
 856                }
 857            }
 858            if (!isTypeGroupChat) {
 859                for (Element child : packet.getChildren()) {
 860                    if (Namespace.JINGLE_MESSAGE.equals(child.getNamespace()) && JINGLE_MESSAGE_ELEMENT_NAMES.contains(child.getName())) {
 861                        final String action = child.getName();
 862                        final String sessionId = child.getAttribute("id");
 863                        if (sessionId == null) {
 864                            break;
 865                        }
 866                        if (query == null && offlineMessagesRetrieved) {
 867                            if (serverMsgId == null) {
 868                                serverMsgId = extractStanzaId(account, packet);
 869                            }
 870                            mXmppConnectionService
 871                                    .getJingleConnectionManager()
 872                                    .deliverMessage(
 873                                            account,
 874                                            packet.getTo(),
 875                                            packet.getFrom(),
 876                                            child,
 877                                            remoteMsgId,
 878                                            serverMsgId,
 879                                            timestamp);
 880                            final Contact contact = account.getRoster().getContact(from);
 881                            if (mXmppConnectionService.confirmMessages()
 882                                    && !contact.isSelf()
 883                                    && remoteMsgId != null
 884                                    && contact.showInContactList()) {
 885                                processMessageReceipts(account, packet, remoteMsgId, null);
 886                            }
 887                        } else if ((query != null && query.isCatchup()) || !offlineMessagesRetrieved) {
 888                            if ("propose".equals(action)) {
 889                                final Element description = child.findChild("description");
 890                                final String namespace =
 891                                        description == null ? null : description.getNamespace();
 892                                if (Namespace.JINGLE_APPS_RTP.equals(namespace)) {
 893                                    final Conversation c =
 894                                            mXmppConnectionService.findOrCreateConversation(
 895                                                    account, counterpart.asBareJid(), false, false);
 896                                    final Message preExistingMessage =
 897                                            c.findRtpSession(sessionId, status);
 898                                    if (preExistingMessage != null) {
 899                                        preExistingMessage.setServerMsgId(serverMsgId);
 900                                        mXmppConnectionService.updateMessage(preExistingMessage);
 901                                        break;
 902                                    }
 903                                    final Message message =
 904                                            new Message(
 905                                                    c, status, Message.TYPE_RTP_SESSION, sessionId);
 906                                    message.setServerMsgId(serverMsgId);
 907                                    message.setTime(timestamp);
 908                                    message.setBody(new RtpSessionStatus(false, 0).toString());
 909                                    c.add(message);
 910                                    mXmppConnectionService.databaseBackend.createMessage(message);
 911                                }
 912                            } else if ("proceed".equals(action)) {
 913                                // status needs to be flipped to find the original propose
 914                                final Conversation c =
 915                                        mXmppConnectionService.findOrCreateConversation(
 916                                                account, counterpart.asBareJid(), false, false);
 917                                final int s =
 918                                        packet.fromAccount(account)
 919                                                ? Message.STATUS_RECEIVED
 920                                                : Message.STATUS_SEND;
 921                                final Message message = c.findRtpSession(sessionId, s);
 922                                if (message != null) {
 923                                    message.setBody(new RtpSessionStatus(true, 0).toString());
 924                                    if (serverMsgId != null) {
 925                                        message.setServerMsgId(serverMsgId);
 926                                    }
 927                                    message.setTime(timestamp);
 928                                    mXmppConnectionService.updateMessage(message, true);
 929                                } else {
 930                                    Log.d(
 931                                            Config.LOGTAG,
 932                                            "unable to find original rtp session message for received propose");
 933                                }
 934
 935                            } else if ("finish".equals(action)) {
 936                                Log.d(
 937                                        Config.LOGTAG,
 938                                        "received JMI 'finish' during MAM catch-up. Can be used to update success/failure and duration");
 939                            }
 940                        } else {
 941                            //MAM reloads (non catchups
 942                            if ("propose".equals(action)) {
 943                                final Element description = child.findChild("description");
 944                                final String namespace = description == null ? null : description.getNamespace();
 945                                if (Namespace.JINGLE_APPS_RTP.equals(namespace)) {
 946                                    final Conversation c = mXmppConnectionService.findOrCreateConversation(account, counterpart.asBareJid(), false, false);
 947                                    final Message preExistingMessage = c.findRtpSession(sessionId, status);
 948                                    if (preExistingMessage != null) {
 949                                        preExistingMessage.setServerMsgId(serverMsgId);
 950                                        mXmppConnectionService.updateMessage(preExistingMessage);
 951                                        break;
 952                                    }
 953                                    final Message message = new Message(
 954                                            c,
 955                                            status,
 956                                            Message.TYPE_RTP_SESSION,
 957                                            sessionId
 958                                    );
 959                                    message.setServerMsgId(serverMsgId);
 960                                    message.setTime(timestamp);
 961                                    message.setBody(new RtpSessionStatus(true, 0).toString());
 962                                    if (query.getPagingOrder() == MessageArchiveService.PagingOrder.REVERSE) {
 963                                        c.prepend(query.getActualInThisQuery(), message);
 964                                    } else {
 965                                        c.add(message);
 966                                    }
 967                                    query.incrementActualMessageCount();
 968                                    mXmppConnectionService.databaseBackend.createMessage(message);
 969                                }
 970                            }
 971                        }
 972                        break;
 973                    }
 974                }
 975            }
 976        }
 977
 978        Element received = packet.findChild("received", "urn:xmpp:chat-markers:0");
 979        if (received == null) {
 980            received = packet.findChild("received", "urn:xmpp:receipts");
 981        }
 982        if (received != null) {
 983            String id = received.getAttribute("id");
 984            if (packet.fromAccount(account)) {
 985                if (query != null && id != null && packet.getTo() != null) {
 986                    query.removePendingReceiptRequest(new ReceiptRequest(packet.getTo(), id));
 987                }
 988            } else if (id != null) {
 989                if (id.startsWith(JingleRtpConnection.JINGLE_MESSAGE_PROPOSE_ID_PREFIX)) {
 990                    final String sessionId = id.substring(JingleRtpConnection.JINGLE_MESSAGE_PROPOSE_ID_PREFIX.length());
 991                    mXmppConnectionService.getJingleConnectionManager()
 992                            .updateProposedSessionDiscovered(account, from, sessionId, JingleConnectionManager.DeviceDiscoveryState.DISCOVERED);
 993                } else {
 994                    mXmppConnectionService.markMessage(account, from.asBareJid(), id, Message.STATUS_SEND_RECEIVED);
 995                }
 996            }
 997        }
 998        final Element displayed = packet.findChild("displayed", "urn:xmpp:chat-markers:0");
 999        if (displayed != null) {
1000            final String id = displayed.getAttribute("id");
1001            final Jid sender = InvalidJid.getNullForInvalid(displayed.getAttributeAsJid("sender"));
1002            if (packet.fromAccount(account) && !selfAddressed) {
1003                final Conversation c =
1004                        mXmppConnectionService.find(account, counterpart.asBareJid());
1005                final Message message =
1006                        (c == null || id == null) ? null : c.findReceivedWithRemoteId(id);
1007                if (message != null && (query == null || query.isCatchup())) {
1008                    mXmppConnectionService.markReadUpTo(c, message);
1009                }
1010                if (query == null) {
1011                    activateGracePeriod(account);
1012                }
1013            } else if (isTypeGroupChat) {
1014                final Conversation conversation = mXmppConnectionService.find(account, counterpart.asBareJid());
1015                final Message message;
1016                if (conversation != null && id != null) {
1017                    if (sender != null) {
1018                        message = conversation.findMessageWithRemoteId(id, sender);
1019                    } else {
1020                        message = conversation.findMessageWithServerMsgId(id);
1021                    }
1022                } else {
1023                    message = null;
1024                }
1025                if (message != null) {
1026                    final Jid fallback = conversation.getMucOptions().getTrueCounterpart(counterpart);
1027                    final Jid trueJid = getTrueCounterpart((query != null && query.safeToExtractTrueCounterpart()) ? mucUserElement : null, fallback);
1028                    final boolean trueJidMatchesAccount = account.getJid().asBareJid().equals(trueJid == null ? null : trueJid.asBareJid());
1029                    if (trueJidMatchesAccount || conversation.getMucOptions().isSelf(counterpart)) {
1030                        if (!message.isRead() && (query == null || query.isCatchup())) { //checking if message is unread fixes race conditions with reflections
1031                            mXmppConnectionService.markReadUpTo(conversation, message);
1032                        }
1033                    } else if (!counterpart.isBareJid() && trueJid != null) {
1034                        final ReadByMarker readByMarker = ReadByMarker.from(counterpart, trueJid);
1035                        if (message.addReadByMarker(readByMarker)) {
1036                            mXmppConnectionService.updateMessage(message, false);
1037                        }
1038                    }
1039                }
1040            } else {
1041                final Message displayedMessage = mXmppConnectionService.markMessage(account, from.asBareJid(), id, Message.STATUS_SEND_DISPLAYED);
1042                Message message = displayedMessage == null ? null : displayedMessage.prev();
1043                while (message != null
1044                        && message.getStatus() == Message.STATUS_SEND_RECEIVED
1045                        && message.getTimeSent() < displayedMessage.getTimeSent()) {
1046                    mXmppConnectionService.markMessage(message, Message.STATUS_SEND_DISPLAYED);
1047                    message = message.prev();
1048                }
1049                if (displayedMessage != null && selfAddressed) {
1050                    dismissNotification(account, counterpart, query, id);
1051                }
1052            }
1053        }
1054
1055        final Element event = original.findChild("event", "http://jabber.org/protocol/pubsub#event");
1056        if (event != null && InvalidJid.hasValidFrom(original) && original.getFrom().isBareJid()) {
1057            if (event.hasChild("items")) {
1058                parseEvent(event, original.getFrom(), account);
1059            } else if (event.hasChild("delete")) {
1060                parseDeleteEvent(event, original.getFrom(), account);
1061            } else if (event.hasChild("purge")) {
1062                parsePurgeEvent(event, original.getFrom(), account);
1063            }
1064        }
1065
1066        final String nick = packet.findChildContent("nick", Namespace.NICK);
1067        if (nick != null && InvalidJid.hasValidFrom(original)) {
1068            if (mXmppConnectionService.isMuc(account, from)) {
1069                return;
1070            }
1071            final Contact contact = account.getRoster().getContact(from);
1072            if (contact.setPresenceName(nick)) {
1073                mXmppConnectionService.syncRoster(account);
1074                mXmppConnectionService.getAvatarService().clear(contact);
1075            }
1076        }
1077    }
1078
1079    private void dismissNotification(Account account, Jid counterpart, MessageArchiveService.Query query, final String id) {
1080        final Conversation conversation = mXmppConnectionService.find(account, counterpart.asBareJid());
1081        if (conversation != null && (query == null || query.isCatchup())) {
1082            final String displayableId = conversation.findMostRecentRemoteDisplayableId();
1083            if (displayableId != null && displayableId.equals(id)) {
1084                mXmppConnectionService.markRead(conversation);
1085            } else {
1086                Log.w(Config.LOGTAG, account.getJid().asBareJid() + ": received dismissing display marker that did not match our last id in that conversation");
1087            }
1088        }
1089    }
1090
1091    private void processMessageReceipts(final Account account, final MessagePacket packet, final String remoteMsgId, MessageArchiveService.Query query) {
1092        final boolean markable = packet.hasChild("markable", "urn:xmpp:chat-markers:0");
1093        final boolean request = packet.hasChild("request", "urn:xmpp:receipts");
1094        if (query == null) {
1095            final ArrayList<String> receiptsNamespaces = new ArrayList<>();
1096            if (markable) {
1097                receiptsNamespaces.add("urn:xmpp:chat-markers:0");
1098            }
1099            if (request) {
1100                receiptsNamespaces.add("urn:xmpp:receipts");
1101            }
1102            if (receiptsNamespaces.size() > 0) {
1103                final MessagePacket receipt = mXmppConnectionService.getMessageGenerator().received(account,
1104                        packet.getFrom(),
1105                        remoteMsgId,
1106                        receiptsNamespaces,
1107                        packet.getType());
1108                mXmppConnectionService.sendMessagePacket(account, receipt);
1109            }
1110        } else if (query.isCatchup()) {
1111            if (request) {
1112                query.addPendingReceiptRequest(new ReceiptRequest(packet.getFrom(), remoteMsgId));
1113            }
1114        }
1115    }
1116
1117    private void activateGracePeriod(Account account) {
1118        long duration = mXmppConnectionService.getLongPreference("grace_period_length", R.integer.grace_period) * 1000;
1119        Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": activating grace period till " + TIME_FORMAT.format(new Date(System.currentTimeMillis() + duration)));
1120        account.activateGracePeriod(duration);
1121    }
1122
1123    private class Invite {
1124        final Jid jid;
1125        final String password;
1126        final boolean direct;
1127        final Jid inviter;
1128
1129        Invite(Jid jid, String password, boolean direct, Jid inviter) {
1130            this.jid = jid;
1131            this.password = password;
1132            this.direct = direct;
1133            this.inviter = inviter;
1134        }
1135
1136        public boolean execute(final Account account) {
1137            if (this.jid == null) {
1138                return false;
1139            }
1140            final Contact contact =
1141                    this.inviter != null ? account.getRoster().getContact(this.inviter) : null;
1142            if (contact != null && contact.isBlocked()) {
1143                Log.d(
1144                        Config.LOGTAG,
1145                        account.getJid().asBareJid()
1146                                + ": ignore invite from "
1147                                + contact.getJid()
1148                                + " because contact is blocked");
1149                return false;
1150            }
1151            final AppSettings appSettings = new AppSettings(mXmppConnectionService);
1152            if ((contact != null && contact.showInContactList())
1153                    || appSettings.isAcceptInvitesFromStrangers()) {
1154                final Conversation conversation =
1155                        mXmppConnectionService.findOrCreateConversation(account, jid, true, false);
1156                if (conversation.getMucOptions().online()) {
1157                    Log.d(
1158                            Config.LOGTAG,
1159                            account.getJid().asBareJid()
1160                                    + ": received invite to "
1161                                    + jid
1162                                    + " but muc is considered to be online");
1163                    mXmppConnectionService.mucSelfPingAndRejoin(conversation);
1164                } else {
1165                    conversation.getMucOptions().setPassword(password);
1166                    mXmppConnectionService.databaseBackend.updateConversation(conversation);
1167                    mXmppConnectionService.joinMuc(
1168                            conversation, contact != null && contact.showInContactList());
1169                    mXmppConnectionService.updateConversationUi();
1170                }
1171                return true;
1172            } else {
1173                Log.d(
1174                        Config.LOGTAG,
1175                        account.getJid().asBareJid()
1176                                + ": ignoring invite from "
1177                                + this.inviter
1178                                + " because we are not accepting invites from strangers. direct="
1179                                + direct);
1180                return false;
1181            }
1182        }
1183    }
1184}