dispatch PEP events via manager

Daniel Gultsch created

Change summary

src/main/java/eu/siacs/conversations/parser/MessageParser.java                                | 195 
src/main/java/eu/siacs/conversations/services/XmppConnectionService.java                      |  46 
src/main/java/eu/siacs/conversations/xmpp/Managers.java                                       |   4 
src/main/java/eu/siacs/conversations/xmpp/manager/AbstractBookmarkManager.java                |  19 
src/main/java/eu/siacs/conversations/xmpp/manager/AvatarManager.java                          |  57 
src/main/java/eu/siacs/conversations/xmpp/manager/AxolotlManager.java                         |  27 
src/main/java/eu/siacs/conversations/xmpp/manager/BookmarkManager.java                        |  54 
src/main/java/eu/siacs/conversations/xmpp/manager/LegacyBookmarkManager.java                  |  30 
src/main/java/eu/siacs/conversations/xmpp/manager/MessageDisplayedSynchronizationManager.java |  70 
src/main/java/eu/siacs/conversations/xmpp/manager/NickManager.java                            |  37 
src/main/java/eu/siacs/conversations/xmpp/manager/PubSubManager.java                          |  24 
src/main/java/im/conversations/android/xmpp/processor/BindProcessor.java                      |   3 
12 files changed, 294 insertions(+), 272 deletions(-)

Detailed changes

src/main/java/eu/siacs/conversations/parser/MessageParser.java 🔗

@@ -2,7 +2,6 @@ package eu.siacs.conversations.parser;
 
 import android.util.Log;
 import android.util.Pair;
-import androidx.annotation.NonNull;
 import com.google.common.base.Strings;
 import com.google.common.collect.ImmutableSet;
 import eu.siacs.conversations.AppSettings;
@@ -14,7 +13,6 @@ import eu.siacs.conversations.crypto.axolotl.NotEncryptedForThisDeviceException;
 import eu.siacs.conversations.crypto.axolotl.OutdatedSenderException;
 import eu.siacs.conversations.crypto.axolotl.XmppAxolotlMessage;
 import eu.siacs.conversations.entities.Account;
-import eu.siacs.conversations.entities.Bookmark;
 import eu.siacs.conversations.entities.Contact;
 import eu.siacs.conversations.entities.Conversation;
 import eu.siacs.conversations.entities.Conversational;
@@ -26,7 +24,6 @@ import eu.siacs.conversations.entities.ReceiptRequest;
 import eu.siacs.conversations.entities.RtpSessionStatus;
 import eu.siacs.conversations.http.HttpConnectionManager;
 import eu.siacs.conversations.services.MessageArchiveService;
-import eu.siacs.conversations.services.QuickConversationsService;
 import eu.siacs.conversations.services.XmppConnectionService;
 import eu.siacs.conversations.utils.CryptoHelper;
 import eu.siacs.conversations.xml.Element;
@@ -39,25 +36,16 @@ import eu.siacs.conversations.xmpp.jingle.JingleConnectionManager;
 import eu.siacs.conversations.xmpp.jingle.JingleRtpConnection;
 import eu.siacs.conversations.xmpp.manager.PubSubManager;
 import eu.siacs.conversations.xmpp.manager.RosterManager;
-import eu.siacs.conversations.xmpp.pep.Avatar;
 import im.conversations.android.xmpp.model.Extension;
-import im.conversations.android.xmpp.model.avatar.Metadata;
-import im.conversations.android.xmpp.model.axolotl.DeviceList;
 import im.conversations.android.xmpp.model.axolotl.Encrypted;
-import im.conversations.android.xmpp.model.bookmark.Storage;
-import im.conversations.android.xmpp.model.bookmark2.Conference;
 import im.conversations.android.xmpp.model.carbons.Received;
 import im.conversations.android.xmpp.model.carbons.Sent;
 import im.conversations.android.xmpp.model.correction.Replace;
 import im.conversations.android.xmpp.model.forward.Forwarded;
 import im.conversations.android.xmpp.model.markers.Displayed;
-import im.conversations.android.xmpp.model.nick.Nick;
 import im.conversations.android.xmpp.model.occupant.OccupantId;
 import im.conversations.android.xmpp.model.oob.OutOfBandData;
-import im.conversations.android.xmpp.model.pubsub.Items;
-import im.conversations.android.xmpp.model.pubsub.event.Delete;
 import im.conversations.android.xmpp.model.pubsub.event.Event;
-import im.conversations.android.xmpp.model.pubsub.event.Purge;
 import im.conversations.android.xmpp.model.reactions.Reactions;
 import im.conversations.android.xmpp.model.receipts.Request;
 import im.conversations.android.xmpp.model.unique.StanzaId;
@@ -65,10 +53,8 @@ import java.text.SimpleDateFormat;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.Date;
-import java.util.HashSet;
 import java.util.List;
 import java.util.Locale;
-import java.util.Map;
 import java.util.Set;
 import java.util.UUID;
 import java.util.function.Consumer;
@@ -257,169 +243,6 @@ public class MessageParser extends AbstractParser
         return null;
     }
 
-    private void parseEvent(final Items items, final Jid from, final Account account) {
-        final String node = items.getNode();
-        if ("urn:xmpp:avatar:metadata".equals(node)) {
-            // TODO support retract
-            final var entry = items.getFirstItemWithId(Metadata.class);
-            final var avatar =
-                    entry == null ? null : Avatar.parseMetadata(entry.getKey(), entry.getValue());
-            if (avatar != null) {
-                avatar.owner = from.asBareJid();
-                if (mXmppConnectionService.getFileBackend().isAvatarCached(avatar)) {
-                    if (account.getJid().asBareJid().equals(from)) {
-                        if (account.setAvatar(avatar.getFilename())) {
-                            mXmppConnectionService.databaseBackend.updateAccount(account);
-                            mXmppConnectionService.notifyAccountAvatarHasChanged(account);
-                        }
-                        mXmppConnectionService.getAvatarService().clear(account);
-                        mXmppConnectionService.updateConversationUi();
-                        mXmppConnectionService.updateAccountUi();
-                    } else {
-                        final Contact contact = account.getRoster().getContact(from);
-                        if (contact.setAvatar(avatar)) {
-                            connection.getManager(RosterManager.class).writeToDatabaseAsync();
-                            mXmppConnectionService.getAvatarService().clear(contact);
-                            mXmppConnectionService.updateConversationUi();
-                            mXmppConnectionService.updateRosterUi();
-                        }
-                    }
-                } else if (mXmppConnectionService.isDataSaverDisabled()) {
-                    mXmppConnectionService.fetchAvatar(account, avatar);
-                }
-            }
-        } else if (Namespace.NICK.equals(node)) {
-            final var nickItem = items.getFirstItem(Nick.class);
-            final String nick = nickItem == null ? null : nickItem.getContent();
-            if (nick != null) {
-                setNick(account, from, nick);
-            }
-        } else if (AxolotlService.PEP_DEVICE_LIST.equals(node)) {
-            final var deviceList = items.getFirstItem(DeviceList.class);
-            if (deviceList != null) {
-                final Set<Integer> deviceIds = deviceList.getDeviceIds();
-                Log.d(
-                        Config.LOGTAG,
-                        AxolotlService.getLogprefix(account)
-                                + "Received PEP device list "
-                                + deviceIds
-                                + " update from "
-                                + from
-                                + ", processing... ");
-                final AxolotlService axolotlService = account.getAxolotlService();
-                axolotlService.registerDevices(from, new HashSet<>(deviceIds));
-            }
-
-        } else if (Namespace.BOOKMARKS.equals(node) && account.getJid().asBareJid().equals(from)) {
-            final var connection = account.getXmppConnection();
-            if (connection.getFeatures().bookmarksConversion()) {
-                if (connection.getFeatures().bookmarks2()) {
-                    Log.w(
-                            Config.LOGTAG,
-                            account.getJid().asBareJid()
-                                    + ": received storage:bookmark notification even though we"
-                                    + " opted into bookmarks:1");
-                }
-                final var storage = items.getFirstItem(Storage.class);
-                final Map<Jid, Bookmark> bookmarks = Bookmark.parseFromStorage(storage, account);
-                // mXmppConnectionService.processBookmarksInitial(account, bookmarks, true);
-                Log.d(
-                        Config.LOGTAG,
-                        account.getJid().asBareJid() + ": processing bookmark PEP event");
-            } else {
-                Log.d(
-                        Config.LOGTAG,
-                        account.getJid().asBareJid()
-                                + ": ignoring bookmark PEP event because bookmark conversion was"
-                                + " not detected");
-            }
-        } else if (Namespace.BOOKMARKS2.equals(node) && account.getJid().asBareJid().equals(from)) {
-            final var retractions = items.getRetractions();
-            for (final var item : items.getItemMap(Conference.class).entrySet()) {
-                final Bookmark bookmark =
-                        Bookmark.parseFromItem(item.getKey(), item.getValue(), account);
-                if (bookmark == null) {
-                    continue;
-                }
-                account.putBookmark(bookmark);
-                mXmppConnectionService.processModifiedBookmark(bookmark);
-                mXmppConnectionService.updateConversationUi();
-            }
-            for (final var retract : retractions) {
-                final Jid id = Jid.Invalid.getNullForInvalid(retract.getAttributeAsJid("id"));
-                if (id != null) {
-                    account.removeBookmark(id);
-                    Log.d(
-                            Config.LOGTAG,
-                            account.getJid().asBareJid() + ": deleted bookmark for " + id);
-                    // mXmppConnectionService.processDeletedBookmark(account, id);
-                    mXmppConnectionService.updateConversationUi();
-                }
-            }
-        } else if (Config.MESSAGE_DISPLAYED_SYNCHRONIZATION
-                && Namespace.MDS_DISPLAYED.equals(node)
-                && account.getJid().asBareJid().equals(from)) {
-            for (final var item :
-                    items.getItemMap(im.conversations.android.xmpp.model.mds.Displayed.class)
-                            .entrySet()) {
-                mXmppConnectionService.processMdsItem(account, item);
-            }
-        }
-    }
-
-    private void parseDeleteEvent(final Delete delete, final Jid from, final Account account) {
-        final String node = delete.getNode();
-        if (Namespace.NICK.equals(node)) {
-            setNick(account, from, null);
-        } else if (Namespace.BOOKMARKS2.equals(node) && account.getJid().asBareJid().equals(from)) {
-            Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": deleted bookmarks node");
-            deleteAllBookmarks(account);
-        } else if (Namespace.AVATAR_METADATA.equals(node)) {
-            final boolean isAccount = account.getJid().asBareJid().equals(from);
-            if (isAccount) {
-                account.setAvatar(null);
-                mXmppConnectionService.databaseBackend.updateAccount(account);
-                mXmppConnectionService.getAvatarService().clear(account);
-                Log.d(
-                        Config.LOGTAG,
-                        account.getJid().asBareJid() + ": deleted avatar metadata node");
-            }
-        }
-    }
-
-    private void parsePurgeEvent(
-            @NonNull final Purge purge, final Jid from, final Account account) {
-        final String node = purge.getNode();
-        if (Namespace.BOOKMARKS2.equals(node) && account.getJid().asBareJid().equals(from)) {
-            Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": purged bookmarks");
-            deleteAllBookmarks(account);
-        }
-    }
-
-    private void deleteAllBookmarks(final Account account) {
-        final var previous = account.getBookmarkedJids();
-        account.setBookmarks(Collections.emptyMap());
-        // mXmppConnectionService.processDeletedBookmarks(account, previous);
-    }
-
-    private void setNick(final Account account, final Jid user, final String nick) {
-        if (user.asBareJid().equals(account.getJid().asBareJid())) {
-            account.setDisplayName(nick);
-            if (QuickConversationsService.isQuicksy()) {
-                mXmppConnectionService.getAvatarService().clear(account);
-            }
-            mXmppConnectionService.checkMucRequiresRename();
-        } else {
-            Contact contact = account.getRoster().getContact(user);
-            if (contact.setPresenceName(nick)) {
-                connection.getManager(RosterManager.class).writeToDatabaseAsync();
-                mXmppConnectionService.getAvatarService().clear(contact);
-            }
-        }
-        mXmppConnectionService.updateConversationUi();
-        mXmppConnectionService.updateAccountUi();
-    }
-
     private boolean handleErrorMessage(
             final Account account,
             final im.conversations.android.xmpp.model.stanza.Message packet) {
@@ -1414,24 +1237,6 @@ public class MessageParser extends AbstractParser
         if (original.hasExtension(Event.class)) {
             getManager(PubSubManager.class).handleEvent(original);
         }
-        final var event = original.getExtension(Event.class);
-        if (event != null && Jid.Invalid.hasValidFrom(original) && original.getFrom().isBareJid()) {
-            final var action = event.getAction();
-            final var node = action == null ? null : action.getNode();
-            if (node == null) {
-                Log.d(
-                        Config.LOGTAG,
-                        account.getJid().asBareJid()
-                                + ": no node found in PubSub event from "
-                                + original.getFrom());
-            } else if (action instanceof Items items) {
-                parseEvent(items, original.getFrom(), account);
-            } else if (action instanceof Purge purge) {
-                parsePurgeEvent(purge, original.getFrom(), account);
-            } else if (action instanceof Delete delete) {
-                parseDeleteEvent(delete, from, account);
-            }
-        }
 
         final String nick = packet.findChildContent("nick", Namespace.NICK);
         if (nick != null && Jid.Invalid.hasValidFrom(original)) {

src/main/java/eu/siacs/conversations/services/XmppConnectionService.java 🔗

@@ -142,7 +142,6 @@ import im.conversations.android.xmpp.Entity;
 import im.conversations.android.xmpp.IqErrorException;
 import im.conversations.android.xmpp.model.avatar.Metadata;
 import im.conversations.android.xmpp.model.disco.info.InfoQuery;
-import im.conversations.android.xmpp.model.mds.Displayed;
 import im.conversations.android.xmpp.model.pubsub.PubSub;
 import im.conversations.android.xmpp.model.stanza.Iq;
 import im.conversations.android.xmpp.model.up.Push;
@@ -1944,51 +1943,6 @@ public class XmppConnectionService extends Service {
                 });
     }
 
-    public void fetchMessageDisplayedSynchronization(final Account account) {
-        Log.d(Config.LOGTAG, account.getJid() + ": retrieve mds");
-        final var retrieve = mIqGenerator.retrieveMds();
-        sendIqPacket(
-                account,
-                retrieve,
-                (response) -> {
-                    if (response.getType() != Iq.Type.RESULT) {
-                        return;
-                    }
-                    final var pubsub = response.getExtension(PubSub.class);
-                    if (pubsub == null) {
-                        return;
-                    }
-                    final var items = pubsub.getItems();
-                    if (items == null) {
-                        return;
-                    }
-                    if (Namespace.MDS_DISPLAYED.equals(items.getNode())) {
-                        for (final var item :
-                                items.getItemMap(
-                                                im.conversations.android.xmpp.model.mds.Displayed
-                                                        .class)
-                                        .entrySet()) {
-                            processMdsItem(account, item);
-                        }
-                    }
-                });
-    }
-
-    public void processMdsItem(final Account account, final Map.Entry<String, Displayed> item) {
-        final Jid jid = Jid.Invalid.getNullForInvalid(Jid.ofOrInvalid(item.getKey()));
-        if (jid == null) {
-            return;
-        }
-        final var displayed = item.getValue();
-        final var stanzaId = displayed.getStanzaId();
-        final String id = stanzaId == null ? null : stanzaId.getId();
-        final Conversation conversation = find(account, jid);
-        if (id != null && conversation != null) {
-            conversation.setDisplayState(id);
-            markReadUpToStanzaId(conversation, id);
-        }
-    }
-
     public void markReadUpToStanzaId(final Conversation conversation, final String stanzaId) {
         final Message message = conversation.findMessageWithServerMsgId(stanzaId);
         if (message == null) { // do we want to check if isRead?

src/main/java/eu/siacs/conversations/xmpp/Managers.java 🔗

@@ -12,6 +12,7 @@ import eu.siacs.conversations.xmpp.manager.CarbonsManager;
 import eu.siacs.conversations.xmpp.manager.DiscoManager;
 import eu.siacs.conversations.xmpp.manager.EntityTimeManager;
 import eu.siacs.conversations.xmpp.manager.LegacyBookmarkManager;
+import eu.siacs.conversations.xmpp.manager.MessageDisplayedSynchronizationManager;
 import eu.siacs.conversations.xmpp.manager.NickManager;
 import eu.siacs.conversations.xmpp.manager.PepManager;
 import eu.siacs.conversations.xmpp.manager.PingManager;
@@ -38,6 +39,9 @@ public class Managers {
                 .put(DiscoManager.class, new DiscoManager(context, connection))
                 .put(EntityTimeManager.class, new EntityTimeManager(context, connection))
                 .put(LegacyBookmarkManager.class, new LegacyBookmarkManager(context, connection))
+                .put(
+                        MessageDisplayedSynchronizationManager.class,
+                        new MessageDisplayedSynchronizationManager(context, connection))
                 .put(NickManager.class, new NickManager(context, connection))
                 .put(PepManager.class, new PepManager(context, connection))
                 .put(PingManager.class, new PingManager(context, connection))

src/main/java/eu/siacs/conversations/xmpp/manager/AbstractBookmarkManager.java 🔗

@@ -2,7 +2,6 @@ package eu.siacs.conversations.xmpp.manager;
 
 import android.util.Log;
 import eu.siacs.conversations.Config;
-import eu.siacs.conversations.entities.Account;
 import eu.siacs.conversations.entities.Bookmark;
 import eu.siacs.conversations.entities.Conversation;
 import eu.siacs.conversations.services.XmppConnectionService;
@@ -14,7 +13,7 @@ import java.util.Set;
 
 public class AbstractBookmarkManager extends AbstractManager {
 
-    private final XmppConnectionService service;
+    protected final XmppConnectionService service;
 
     protected AbstractBookmarkManager(
             final XmppConnectionService service, final XmppConnection connection) {
@@ -23,7 +22,7 @@ public class AbstractBookmarkManager extends AbstractManager {
     }
 
     // TODO rename to setBookmarks?
-    public void processBookmarksInitial(final Map<Jid, Bookmark> bookmarks, final boolean pep) {
+    protected void processBookmarksInitial(final Map<Jid, Bookmark> bookmarks, final boolean pep) {
         final var account = getAccount();
         // TODO we can internalize this getBookmarkedJid
         final Set<Jid> previousBookmarks = account.getBookmarkedJids();
@@ -32,31 +31,31 @@ public class AbstractBookmarkManager extends AbstractManager {
             service.processModifiedBookmark(bookmark, pep);
         }
         if (pep) {
-            this.processDeletedBookmarks(account, previousBookmarks);
+            this.processDeletedBookmarks(previousBookmarks);
         }
         account.setBookmarks(bookmarks);
     }
 
-    public void processDeletedBookmarks(final Account account, final Collection<Jid> bookmarks) {
+    protected void processDeletedBookmarks(final Collection<Jid> bookmarks) {
         Log.d(
                 Config.LOGTAG,
-                account.getJid().asBareJid()
+                getAccount().getJid().asBareJid()
                         + ": "
                         + bookmarks.size()
                         + " bookmarks have been removed");
         for (final Jid bookmark : bookmarks) {
-            processDeletedBookmark(account, bookmark);
+            processDeletedBookmark(bookmark);
         }
     }
 
-    public void processDeletedBookmark(final Account account, final Jid jid) {
-        final Conversation conversation = service.find(account, jid);
+    protected void processDeletedBookmark(final Jid jid) {
+        final Conversation conversation = service.find(getAccount(), jid);
         if (conversation == null) {
             return;
         }
         Log.d(
                 Config.LOGTAG,
-                account.getJid().asBareJid() + ": archiving MUC " + jid + " after PEP update");
+                getAccount().getJid().asBareJid() + ": archiving MUC " + jid + " after PEP update");
         this.service.archiveConversation(conversation, false);
     }
 }

src/main/java/eu/siacs/conversations/xmpp/manager/AvatarManager.java 🔗

@@ -1,15 +1,64 @@
 package eu.siacs.conversations.xmpp.manager;
 
-import android.content.Context;
+import android.util.Log;
+import eu.siacs.conversations.Config;
+import eu.siacs.conversations.entities.Contact;
+import eu.siacs.conversations.services.XmppConnectionService;
 import eu.siacs.conversations.xmpp.Jid;
 import eu.siacs.conversations.xmpp.XmppConnection;
+import eu.siacs.conversations.xmpp.pep.Avatar;
+import im.conversations.android.xmpp.model.avatar.Metadata;
 import im.conversations.android.xmpp.model.pubsub.Items;
 
 public class AvatarManager extends AbstractManager {
 
-    public AvatarManager(Context context, XmppConnection connection) {
-        super(context, connection);
+    private final XmppConnectionService service;
+
+    public AvatarManager(final XmppConnectionService service, XmppConnection connection) {
+        super(service.getApplicationContext(), connection);
+        this.service = service;
+    }
+
+    public void handleItems(final Jid from, final Items items) {
+        final var account = getAccount();
+        // TODO support retract
+        final var entry = items.getFirstItemWithId(Metadata.class);
+        final var avatar =
+                entry == null ? null : Avatar.parseMetadata(entry.getKey(), entry.getValue());
+        if (avatar != null) {
+            avatar.owner = from.asBareJid();
+            if (service.getFileBackend().isAvatarCached(avatar)) {
+                if (account.getJid().asBareJid().equals(from)) {
+                    if (account.setAvatar(avatar.getFilename())) {
+                        service.databaseBackend.updateAccount(account);
+                        service.notifyAccountAvatarHasChanged(account);
+                    }
+                    service.getAvatarService().clear(account);
+                    service.updateConversationUi();
+                    service.updateAccountUi();
+                } else {
+                    final Contact contact = account.getRoster().getContact(from);
+                    if (contact.setAvatar(avatar)) {
+                        connection.getManager(RosterManager.class).writeToDatabaseAsync();
+                        service.getAvatarService().clear(contact);
+                        service.updateConversationUi();
+                        service.updateRosterUi();
+                    }
+                }
+            } else if (service.isDataSaverDisabled()) {
+                service.fetchAvatar(account, avatar);
+            }
+        }
     }
 
-    public void handleItems(Jid from, final Items items) {}
+    public void handleDelete(final Jid from) {
+        final var account = getAccount();
+        final boolean isAccount = account.getJid().asBareJid().equals(from);
+        if (isAccount) {
+            account.setAvatar(null);
+            getDatabase().updateAccount(account);
+            service.getAvatarService().clear(account);
+            Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": deleted avatar metadata node");
+        }
+    }
 }

src/main/java/eu/siacs/conversations/xmpp/manager/AxolotlManager.java 🔗

@@ -1,15 +1,38 @@
 package eu.siacs.conversations.xmpp.manager;
 
 import android.content.Context;
+import android.util.Log;
+import eu.siacs.conversations.Config;
+import eu.siacs.conversations.crypto.axolotl.AxolotlService;
 import eu.siacs.conversations.xmpp.Jid;
 import eu.siacs.conversations.xmpp.XmppConnection;
+import im.conversations.android.xmpp.model.axolotl.DeviceList;
 import im.conversations.android.xmpp.model.pubsub.Items;
+import java.util.HashSet;
+import java.util.Set;
 
 public class AxolotlManager extends AbstractManager {
 
-    public AxolotlManager(Context context, XmppConnection connection) {
+    public AxolotlManager(final Context context, final XmppConnection connection) {
         super(context, connection);
     }
 
-    public void handleItems(Jid from, final Items items) {}
+    public void handleItems(final Jid from, final Items items) {
+        final var account = getAccount();
+        final var deviceList = items.getFirstItem(DeviceList.class);
+        if (deviceList == null) {
+            return;
+        }
+        final Set<Integer> deviceIds = deviceList.getDeviceIds();
+        Log.d(
+                Config.LOGTAG,
+                AxolotlService.getLogprefix(account)
+                        + "Received PEP device list "
+                        + deviceIds
+                        + " update from "
+                        + from
+                        + ", processing... ");
+        final AxolotlService axolotlService = account.getAxolotlService();
+        axolotlService.registerDevices(from, new HashSet<>(deviceIds));
+    }
 }

src/main/java/eu/siacs/conversations/xmpp/manager/BookmarkManager.java 🔗

@@ -17,6 +17,9 @@ import im.conversations.android.xmpp.NodeConfiguration;
 import im.conversations.android.xmpp.model.bookmark2.Conference;
 import im.conversations.android.xmpp.model.bookmark2.Nick;
 import im.conversations.android.xmpp.model.pubsub.Items;
+import im.conversations.android.xmpp.model.pubsub.event.Retract;
+import java.util.Collection;
+import java.util.Collections;
 import java.util.Map;
 
 public class BookmarkManager extends AbstractBookmarkManager {
@@ -54,13 +57,35 @@ public class BookmarkManager extends AbstractBookmarkManager {
     }
 
     public void handleItems(final Items items) {
-        final var retractions = items.getRetractions();
-        final var itemMap = items.getItemMap(Conference.class);
-        if (!retractions.isEmpty()) {
-            // deleteItems(retractions);
+        this.handleItems(items.getItemMap(Conference.class));
+        this.handleRetractions(items.getRetractions());
+    }
+
+    private void handleRetractions(final Collection<Retract> retractions) {
+        final var account = getAccount();
+        for (final var retract : retractions) {
+            final Jid id = Jid.Invalid.getNullForInvalid(retract.getAttributeAsJid("id"));
+            if (id != null) {
+                account.removeBookmark(id);
+                Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": deleted bookmark for " + id);
+                processDeletedBookmark(id);
+                service.updateConversationUi();
+            }
         }
-        if (!itemMap.isEmpty()) {
-            // updateItems(itemMap);
+    }
+
+    private void handleItems(final Map<String, Conference> items) {
+        final var account = getAccount();
+        for (final var item : items.entrySet()) {
+            // TODO parseFromItem can be included in this Manager
+            final Bookmark bookmark =
+                    Bookmark.parseFromItem(item.getKey(), item.getValue(), account);
+            if (bookmark == null) {
+                continue;
+            }
+            account.putBookmark(bookmark);
+            service.processModifiedBookmark(bookmark);
+            service.updateConversationUi();
         }
     }
 
@@ -91,5 +116,20 @@ public class BookmarkManager extends AbstractBookmarkManager {
                 MoreExecutors.directExecutor());
     }
 
-    public void deleteAllItems() {}
+    private void deleteAllItems() {
+        final var account = getAccount();
+        final var previous = account.getBookmarkedJids();
+        account.setBookmarks(Collections.emptyMap());
+        processDeletedBookmarks(previous);
+    }
+
+    public void handleDelete() {
+        Log.d(Config.LOGTAG, getAccount().getJid().asBareJid() + ": deleted bookmarks node");
+        this.deleteAllItems();
+    }
+
+    public void handlePurge() {
+        Log.d(Config.LOGTAG, getAccount().getJid().asBareJid() + ": purged bookmarks");
+        this.deleteAllItems();
+    }
 }

src/main/java/eu/siacs/conversations/xmpp/manager/LegacyBookmarkManager.java 🔗

@@ -1,8 +1,14 @@
 package eu.siacs.conversations.xmpp.manager;
 
+import android.util.Log;
+import eu.siacs.conversations.Config;
+import eu.siacs.conversations.entities.Bookmark;
 import eu.siacs.conversations.services.XmppConnectionService;
+import eu.siacs.conversations.xmpp.Jid;
 import eu.siacs.conversations.xmpp.XmppConnection;
+import im.conversations.android.xmpp.model.bookmark.Storage;
 import im.conversations.android.xmpp.model.pubsub.Items;
+import java.util.Map;
 
 public class LegacyBookmarkManager extends AbstractBookmarkManager {
 
@@ -11,5 +17,27 @@ public class LegacyBookmarkManager extends AbstractBookmarkManager {
         super(service, connection);
     }
 
-    public void handleItems(final Items items) {}
+    public void handleItems(final Items items) {
+        final var account = this.getAccount();
+        final var connection = this.connection;
+        if (connection.getFeatures().bookmarksConversion()) {
+            if (connection.getFeatures().bookmarks2()) {
+                Log.w(
+                        Config.LOGTAG,
+                        account.getJid().asBareJid()
+                                + ": received storage:bookmark notification even though we"
+                                + " opted into bookmarks:1");
+            }
+            final var storage = items.getFirstItem(Storage.class);
+            final Map<Jid, Bookmark> bookmarks = Bookmark.parseFromStorage(storage, account);
+            this.processBookmarksInitial(bookmarks, true);
+            Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": processing bookmark PEP event");
+        } else {
+            Log.d(
+                    Config.LOGTAG,
+                    account.getJid().asBareJid()
+                            + ": ignoring bookmark PEP event because bookmark conversion was"
+                            + " not detected");
+        }
+    }
 }

src/main/java/eu/siacs/conversations/xmpp/manager/MessageDisplayedSynchronizationManager.java 🔗

@@ -0,0 +1,70 @@
+package eu.siacs.conversations.xmpp.manager;
+
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import com.google.common.util.concurrent.FutureCallback;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.MoreExecutors;
+
+import eu.siacs.conversations.Config;
+import eu.siacs.conversations.entities.Conversation;
+import eu.siacs.conversations.services.XmppConnectionService;
+import eu.siacs.conversations.xmpp.Jid;
+import eu.siacs.conversations.xmpp.XmppConnection;
+import im.conversations.android.xmpp.model.mds.Displayed;
+import im.conversations.android.xmpp.model.pubsub.Items;
+import java.util.Map;
+
+public class MessageDisplayedSynchronizationManager extends AbstractManager {
+
+    private final XmppConnectionService service;
+
+    public MessageDisplayedSynchronizationManager(
+            final XmppConnectionService service, XmppConnection connection) {
+        super(service.getApplicationContext(), connection);
+        this.service = service;
+    }
+
+    public void handleItems(final Items items) {
+        for (final var item : items.getItemMap(Displayed.class).entrySet()) {
+            this.processMdsItem(item);
+        }
+    }
+
+    public void processMdsItem(final Map.Entry<String, Displayed> item) {
+        final var account = getAccount();
+        final Jid jid = Jid.Invalid.getNullForInvalid(Jid.ofOrInvalid(item.getKey()));
+        if (jid == null) {
+            return;
+        }
+        final var displayed = item.getValue();
+        final var stanzaId = displayed.getStanzaId();
+        final String id = stanzaId == null ? null : stanzaId.getId();
+        final Conversation conversation = this.service.find(account, jid);
+        if (id != null && conversation != null) {
+            conversation.setDisplayState(id);
+            this.service.markReadUpToStanzaId(conversation, id);
+        }
+    }
+
+    public void fetch() {
+        final var future = getManager(PepManager.class).fetchItems(Displayed.class);
+        Futures.addCallback(
+                future,
+                new FutureCallback<>() {
+                    @Override
+                    public void onSuccess(Map<String, Displayed> result) {
+                        for (final var entry : result.entrySet()) {
+                            processMdsItem(entry);
+                        }
+                    }
+
+                    @Override
+                    public void onFailure(@NonNull Throwable t) {
+                        Log.d(Config.LOGTAG,getAccount().getJid().asBareJid()+": could not retrieve MDS items", t);
+                    }
+                },
+                MoreExecutors.directExecutor());
+    }
+}

src/main/java/eu/siacs/conversations/xmpp/manager/NickManager.java 🔗

@@ -1,8 +1,10 @@
 package eu.siacs.conversations.xmpp.manager;
 
-import android.content.Context;
 import com.google.common.base.Strings;
 import com.google.common.util.concurrent.ListenableFuture;
+import eu.siacs.conversations.entities.Contact;
+import eu.siacs.conversations.services.QuickConversationsService;
+import eu.siacs.conversations.services.XmppConnectionService;
 import eu.siacs.conversations.xmpp.Jid;
 import eu.siacs.conversations.xmpp.XmppConnection;
 import im.conversations.android.xmpp.NodeConfiguration;
@@ -11,16 +13,39 @@ import im.conversations.android.xmpp.model.pubsub.Items;
 
 public class NickManager extends AbstractManager {
 
-    public NickManager(Context context, XmppConnection connection) {
-        super(context, connection);
+    private final XmppConnectionService service;
+
+    public NickManager(final XmppConnectionService service, final XmppConnection connection) {
+        super(service.getApplicationContext(), connection);
+        this.service = service;
     }
 
-    public void handleItems(final Jid from, Items items) {
+    public void handleItems(final Jid from, final Items items) {
         final var item = items.getFirstItem(Nick.class);
         final var nick = item == null ? null : item.getContent();
         if (from == null || Strings.isNullOrEmpty(nick)) {
             return;
         }
+        setNick(from, nick);
+    }
+
+    private void setNick(final Jid user, final String nick) {
+        final var account = getAccount();
+        if (user.asBareJid().equals(account.getJid().asBareJid())) {
+            account.setDisplayName(nick);
+            if (QuickConversationsService.isQuicksy()) {
+                service.getAvatarService().clear(account);
+            }
+            service.checkMucRequiresRename();
+        } else {
+            final Contact contact = account.getRoster().getContact(user);
+            if (contact.setPresenceName(nick)) {
+                connection.getManager(RosterManager.class).writeToDatabaseAsync();
+                service.getAvatarService().clear(contact);
+            }
+        }
+        service.updateConversationUi();
+        service.updateAccountUi();
     }
 
     public ListenableFuture<Void> publishNick(final String name) {
@@ -28,4 +53,8 @@ public class NickManager extends AbstractManager {
         nick.setContent(name);
         return getManager(PepManager.class).publishSingleton(nick, NodeConfiguration.PRESENCE);
     }
+
+    public void handleDelete(final Jid from) {
+        this.setNick(from, null);
+    }
 }

src/main/java/eu/siacs/conversations/xmpp/manager/PubSubManager.java 🔗

@@ -172,6 +172,10 @@ public class PubSubManager extends AbstractManager {
             getManager(LegacyBookmarkManager.class).handleItems(items);
             return;
         }
+        if (connection.fromAccount(message) && Namespace.MDS_DISPLAYED.equals(node)) {
+            getManager(MessageDisplayedSynchronizationManager.class).handleItems(items);
+            return;
+        }
         if (isFromBare && Namespace.AVATAR_METADATA.equals(node)) {
             getManager(AvatarManager.class).handleItems(from, items);
             return;
@@ -187,13 +191,29 @@ public class PubSubManager extends AbstractManager {
 
     private void handlePurge(final Message message, final Purge purge) {
         final var from = message.getFrom();
+        final var isFromBare = from == null || from.isBareJid();
         final var node = purge.getNode();
         if (connection.fromAccount(message) && Namespace.BOOKMARKS2.equals(node)) {
-            getManager(BookmarkManager.class).deleteAllItems();
+            getManager(BookmarkManager.class).handlePurge();
         }
     }
 
-    private void handleDelete(final Message message, final Delete delete) {}
+    private void handleDelete(final Message message, final Delete delete) {
+        final var from = message.getFrom();
+        final var isFromBare = from == null || from.isBareJid();
+        final var node = delete.getNode();
+        if (connection.fromAccount(message) && Namespace.BOOKMARKS2.equals(node)) {
+            getManager(BookmarkManager.class).handleDelete();
+            return;
+        }
+        if (isFromBare && Namespace.AVATAR_METADATA.equals(node)) {
+            getManager(AvatarManager.class).handleDelete(from);
+            return;
+        }
+        if (isFromBare && Namespace.NICK.equals(node)) {
+            getManager(NickManager.class).handleDelete(from);
+        }
+    }
 
     public ListenableFuture<Void> publishSingleton(
             Jid address, Extension item, final NodeConfiguration nodeConfiguration) {

src/main/java/im/conversations/android/xmpp/processor/BindProcessor.java 🔗

@@ -8,6 +8,7 @@ import eu.siacs.conversations.generator.IqGenerator;
 import eu.siacs.conversations.services.XmppConnectionService;
 import eu.siacs.conversations.xmpp.XmppConnection;
 import eu.siacs.conversations.xmpp.manager.BookmarkManager;
+import eu.siacs.conversations.xmpp.manager.MessageDisplayedSynchronizationManager;
 import eu.siacs.conversations.xmpp.manager.PrivateStorageManager;
 import eu.siacs.conversations.xmpp.manager.RosterManager;
 import im.conversations.android.xmpp.model.stanza.Iq;
@@ -73,7 +74,7 @@ public class BindProcessor extends XmppConnection.Delegate implements Runnable {
         }
 
         if (features.mds()) {
-            service.fetchMessageDisplayedSynchronization(account);
+            connection.getManager(MessageDisplayedSynchronizationManager.class).fetch();
         } else {
             Log.d(Config.LOGTAG, account.getJid() + ": server has no support for mds");
         }