modify bookmarks vie respective managers

Daniel Gultsch created

Change summary

src/main/java/eu/siacs/conversations/entities/Bookmark.java                                   |  10 
src/main/java/eu/siacs/conversations/generator/IqGenerator.java                               |  21 
src/main/java/eu/siacs/conversations/services/XmppConnectionService.java                      | 175 
src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java                                 |  11 
src/main/java/eu/siacs/conversations/xmpp/manager/AvatarManager.java                          |   5 
src/main/java/eu/siacs/conversations/xmpp/manager/BookmarkManager.java                        |  31 
src/main/java/eu/siacs/conversations/xmpp/manager/DiscoManager.java                           |   7 
src/main/java/eu/siacs/conversations/xmpp/manager/LegacyBookmarkManager.java                  |  22 
src/main/java/eu/siacs/conversations/xmpp/manager/MessageDisplayedSynchronizationManager.java |   8 
src/main/java/eu/siacs/conversations/xmpp/manager/NickManager.java                            |  12 
src/main/java/eu/siacs/conversations/xmpp/manager/PepManager.java                             |  16 
src/main/java/eu/siacs/conversations/xmpp/manager/PrivateStorageManager.java                  |  13 
src/main/java/eu/siacs/conversations/xmpp/manager/PubSubManager.java                          |  10 
src/main/java/im/conversations/android/xmpp/NodeConfiguration.java                            |   6 
src/main/java/im/conversations/android/xmpp/model/bookmark2/Conference.java                   |   4 
src/main/java/im/conversations/android/xmpp/model/bookmark2/Password.java                     |  12 
src/main/java/im/conversations/android/xmpp/model/pubsub/owner/Delete.java                    |  16 
src/main/java/im/conversations/android/xmpp/processor/BindProcessor.java                      |  18 
18 files changed, 243 insertions(+), 154 deletions(-)

Detailed changes

src/main/java/eu/siacs/conversations/entities/Bookmark.java 🔗

@@ -8,10 +8,10 @@ import com.google.common.collect.ImmutableList;
 import eu.siacs.conversations.utils.StringUtils;
 import eu.siacs.conversations.utils.UIHelper;
 import eu.siacs.conversations.xml.Element;
-import eu.siacs.conversations.xml.Namespace;
 import eu.siacs.conversations.xmpp.Jid;
 import im.conversations.android.xmpp.model.bookmark.Storage;
 import im.conversations.android.xmpp.model.bookmark2.Conference;
+import im.conversations.android.xmpp.model.bookmark2.Extensions;
 import java.lang.ref.WeakReference;
 import java.util.Collections;
 import java.util.HashMap;
@@ -24,7 +24,7 @@ public class Bookmark extends Element implements ListItem {
     private final Account account;
     private WeakReference<Conversation> conversation;
     private Jid jid;
-    protected Element extensions = new Element("extensions", Namespace.BOOKMARKS2);
+    protected Extensions extensions = new Extensions();
 
     public Bookmark(final Account account, final Jid jid) {
         super("conference");
@@ -90,14 +90,14 @@ public class Bookmark extends Element implements ListItem {
         bookmark.setAutojoin(conference.getAttributeAsBoolean("autojoin"));
         bookmark.setNick(conference.findChildContent("nick"));
         bookmark.setPassword(conference.findChildContent("password"));
-        final Element extensions = conference.findChild("extensions", Namespace.BOOKMARKS2);
+        final var extensions = conference.getExtensions();
         if (extensions != null) {
-            bookmark.extensions = extensions;
+            bookmark.extensions = conference.getExtensions();
         }
         return bookmark;
     }
 
-    public Element getExtensions() {
+    public Extensions getExtensions() {
         return extensions;
     }
 

src/main/java/eu/siacs/conversations/generator/IqGenerator.java 🔗

@@ -6,7 +6,6 @@ import android.util.Log;
 import eu.siacs.conversations.Config;
 import eu.siacs.conversations.crypto.axolotl.AxolotlService;
 import eu.siacs.conversations.entities.Account;
-import eu.siacs.conversations.entities.Bookmark;
 import eu.siacs.conversations.entities.Conversation;
 import eu.siacs.conversations.entities.DownloadableFile;
 import eu.siacs.conversations.services.MessageArchiveService;
@@ -193,26 +192,6 @@ public class IqGenerator extends AbstractGenerator {
         return publish(AxolotlService.PEP_DEVICE_LIST, item, publishOptions);
     }
 
-    public Element publishBookmarkItem(final Bookmark bookmark) {
-        final String name = bookmark.getBookmarkName();
-        final String nick = bookmark.getNick();
-        final String password = bookmark.getPassword();
-        final boolean autojoin = bookmark.autojoin();
-        final Element conference = new Element("conference", Namespace.BOOKMARKS2);
-        if (name != null) {
-            conference.setAttribute("name", name);
-        }
-        if (nick != null) {
-            conference.addChild("nick").setContent(nick);
-        }
-        if (password != null) {
-            conference.addChild("password").setContent(password);
-        }
-        conference.setAttribute("autojoin", String.valueOf(autojoin));
-        conference.addChild(bookmark.getExtensions());
-        return conference;
-    }
-
     public Element mdsDisplayed(final String stanzaId, final Conversation conversation) {
         final Jid by;
         if (conversation.getMode() == Conversation.MODE_MULTI) {

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

@@ -132,9 +132,14 @@ import eu.siacs.conversations.xmpp.jingle.JingleConnectionManager;
 import eu.siacs.conversations.xmpp.jingle.Media;
 import eu.siacs.conversations.xmpp.jingle.RtpEndUserState;
 import eu.siacs.conversations.xmpp.mam.MamReference;
+import eu.siacs.conversations.xmpp.manager.AvatarManager;
 import eu.siacs.conversations.xmpp.manager.BlockingManager;
+import eu.siacs.conversations.xmpp.manager.BookmarkManager;
 import eu.siacs.conversations.xmpp.manager.DiscoManager;
+import eu.siacs.conversations.xmpp.manager.LegacyBookmarkManager;
+import eu.siacs.conversations.xmpp.manager.NickManager;
 import eu.siacs.conversations.xmpp.manager.PresenceManager;
+import eu.siacs.conversations.xmpp.manager.PrivateStorageManager;
 import eu.siacs.conversations.xmpp.manager.RosterManager;
 import eu.siacs.conversations.xmpp.pep.Avatar;
 import eu.siacs.conversations.xmpp.pep.PublishOptions;
@@ -230,16 +235,6 @@ public class XmppConnectionService extends Service {
     private final Set<String> mInProgressAvatarFetches = new HashSet<>();
     private final Set<String> mOmittedPepAvatarFetches = new HashSet<>();
     public final HashSet<Jid> mLowPingTimeoutMode = new HashSet<>();
-    private final Consumer<Iq> mDefaultIqHandler =
-            (packet) -> {
-                if (packet.getType() != Iq.Type.RESULT) {
-                    final var error = packet.getError();
-                    String text = error != null ? error.findChildContent("text") : null;
-                    if (text != null) {
-                        Log.d(Config.LOGTAG, "received iq error: " + text);
-                    }
-                }
-            };
     public DatabaseBackend databaseBackend;
     private final ReplacingSerialSingleThreadExecutor mContactMergerExecutor =
             new ReplacingSerialSingleThreadExecutor("ContactMerger");
@@ -2040,81 +2035,76 @@ public class XmppConnectionService extends Service {
     public void createBookmark(final Account account, final Bookmark bookmark) {
         account.putBookmark(bookmark);
         final XmppConnection connection = account.getXmppConnection();
-        if (connection == null) {
-            Log.d(
-                    Config.LOGTAG,
-                    account.getJid().asBareJid() + ": no connection. ignoring bookmark creation");
-        } else if (connection.getFeatures().bookmarks2()) {
-            Log.d(
-                    Config.LOGTAG,
-                    account.getJid().asBareJid() + ": pushing bookmark via Bookmarks 2");
-            final Element item = mIqGenerator.publishBookmarkItem(bookmark);
-            pushNodeAndEnforcePublishOptions(
-                    account,
-                    Namespace.BOOKMARKS2,
-                    item,
-                    bookmark.getJid().asBareJid().toString(),
-                    PublishOptions.persistentWhitelistAccessMaxItems());
-        } else if (connection.getFeatures().bookmarksConversion()) {
-            pushBookmarksPep(account);
+        final ListenableFuture<Void> future;
+        if (connection.getManager(BookmarkManager.class).hasFeature()) {
+            future = connection.getManager(BookmarkManager.class).publish(bookmark);
+        } else if (connection.getManager(LegacyBookmarkManager.class).hasConversion()) {
+            future =
+                    connection
+                            .getManager(LegacyBookmarkManager.class)
+                            .publish(account.getBookmarks());
         } else {
-            pushBookmarksPrivateXml(account);
+            future =
+                    connection
+                            .getManager(PrivateStorageManager.class)
+                            .publishBookmarks(account.getBookmarks());
         }
+        Futures.addCallback(
+                future,
+                new FutureCallback<Void>() {
+                    @Override
+                    public void onSuccess(Void result) {
+                        Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": created bookmark");
+                    }
+
+                    @Override
+                    public void onFailure(@NonNull Throwable t) {
+                        Log.d(
+                                Config.LOGTAG,
+                                account.getJid().asBareJid() + ": could not create bookmark",
+                                t);
+                    }
+                },
+                MoreExecutors.directExecutor());
     }
 
     public void deleteBookmark(final Account account, final Bookmark bookmark) {
         account.removeBookmark(bookmark);
         final XmppConnection connection = account.getXmppConnection();
-        if (connection.getFeatures().bookmarks2()) {
-            final Iq request =
-                    mIqGenerator.deleteItem(
-                            Namespace.BOOKMARKS2, bookmark.getJid().asBareJid().toString());
-            Log.d(
-                    Config.LOGTAG,
-                    account.getJid().asBareJid() + ": removing bookmark via Bookmarks 2");
-            sendIqPacket(
-                    account,
-                    request,
-                    (response) -> {
-                        if (response.getType() == Iq.Type.ERROR) {
-                            Log.d(
-                                    Config.LOGTAG,
-                                    account.getJid().asBareJid()
-                                            + ": unable to delete bookmark "
-                                            + response.getErrorCondition());
-                        }
-                    });
-        } else if (connection.getFeatures().bookmarksConversion()) {
-            pushBookmarksPep(account);
+        final ListenableFuture<Void> future;
+        if (connection.getManager(BookmarkManager.class).hasFeature()) {
+            future =
+                    connection
+                            .getManager(BookmarkManager.class)
+                            .retract(bookmark.getJid().asBareJid());
+        } else if (connection.getManager(LegacyBookmarkManager.class).hasConversion()) {
+            future =
+                    connection
+                            .getManager(LegacyBookmarkManager.class)
+                            .publish(account.getBookmarks());
         } else {
-            pushBookmarksPrivateXml(account);
-        }
-    }
-
-    private void pushBookmarksPrivateXml(Account account) {
-        Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": pushing bookmarks via private xml");
-        final Iq iqPacket = new Iq(Iq.Type.SET);
-        // TODO we have extensions for that
-        Element query = iqPacket.query("jabber:iq:private");
-        Element storage = query.addChild("storage", "storage:bookmarks");
-        for (final Bookmark bookmark : account.getBookmarks()) {
-            storage.addChild(bookmark);
+            future =
+                    connection
+                            .getManager(PrivateStorageManager.class)
+                            .publishBookmarks(account.getBookmarks());
         }
-        sendIqPacket(account, iqPacket, mDefaultIqHandler);
-    }
+        Futures.addCallback(
+                future,
+                new FutureCallback<Void>() {
+                    @Override
+                    public void onSuccess(Void result) {
+                        Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": deleted bookmark");
+                    }
 
-    private void pushBookmarksPep(Account account) {
-        Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": pushing bookmarks via pep");
-        final Element storage = new Element("storage", "storage:bookmarks");
-        for (final Bookmark bookmark : account.getBookmarks()) {
-            storage.addChild(bookmark);
-        }
-        pushNodeAndEnforcePublishOptions(
-                account,
-                Namespace.BOOKMARKS,
-                storage,
-                "current",
-                PublishOptions.persistentWhitelistAccess());
+                    @Override
+                    public void onFailure(@NonNull Throwable t) {
+                        Log.d(
+                                Config.LOGTAG,
+                                account.getJid().asBareJid() + ": could not delete bookmark",
+                                t);
+                    }
+                },
+                MoreExecutors.directExecutor());
     }
 
     private void pushNodeAndEnforcePublishOptions(
@@ -4942,7 +4932,8 @@ public class XmppConnectionService extends Service {
 
     public void notifyAccountAvatarHasChanged(final Account account) {
         final XmppConnection connection = account.getXmppConnection();
-        if (connection != null && connection.getFeatures().bookmarksConversion()) {
+        // this was bookmark conversion for a bit which doesn't make sense
+        if (connection.getManager(AvatarManager.class).hasPepToVCardConversion()) {
             Log.d(
                     Config.LOGTAG,
                     account.getJid().asBareJid()
@@ -5831,26 +5822,26 @@ public class XmppConnectionService extends Service {
     }
 
     public void publishDisplayName(final Account account) {
-        String displayName = account.getDisplayName();
-        final Iq request;
-        if (TextUtils.isEmpty(displayName)) {
-            request = mIqGenerator.deleteNode(Namespace.NICK);
-        } else {
-            request = mIqGenerator.publishNick(displayName);
-        }
+        final var connection = account.getXmppConnection();
+        final String displayName = account.getDisplayName();
         mAvatarService.clear(account);
-        sendIqPacket(
-                account,
-                request,
-                (packet) -> {
-                    if (packet.getType() == Iq.Type.ERROR) {
+        final var future = connection.getManager(NickManager.class).publish(displayName);
+        Futures.addCallback(
+                future,
+                new FutureCallback<Void>() {
+                    @Override
+                    public void onSuccess(Void result) {
                         Log.d(
                                 Config.LOGTAG,
-                                account.getJid().asBareJid()
-                                        + ": unable to modify nick name "
-                                        + packet);
+                                account.getJid().asBareJid() + ": published User Nick");
                     }
-                });
+
+                    @Override
+                    public void onFailure(@NonNull Throwable t) {
+                        Log.d(Config.LOGTAG, "could not publish User Nick", t);
+                    }
+                },
+                MoreExecutors.directExecutor());
     }
 
     public void fetchMamPreferences(final Account account, final OnMamPreferencesFetched callback) {

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

@@ -3093,11 +3093,6 @@ public class XmppConnection implements Runnable {
             }
         }
 
-        public boolean bookmarksConversion() {
-            return hasDiscoFeature(account.getJid().asBareJid(), Namespace.BOOKMARKS_CONVERSION)
-                    && pepPublishOptions();
-        }
-
         public boolean blocking() {
             return connection.getManager(BlockingManager.class).hasFeature();
         }
@@ -3262,12 +3257,6 @@ public class XmppConnection implements Runnable {
             return hasDiscoFeature(account.getJid().asBareJid(), Namespace.STANZA_IDS);
         }
 
-        public boolean bookmarks2() {
-            return pepPublishOptions()
-                    && pepConfigNodeMax()
-                    && hasDiscoFeature(account.getJid().asBareJid(), Namespace.BOOKMARKS2_COMPAT);
-        }
-
         public boolean externalServiceDiscovery() {
             return hasDiscoFeature(account.getDomain(), Namespace.EXTERNAL_SERVICE_DISCOVERY);
         }

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

@@ -4,6 +4,7 @@ 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.xml.Namespace;
 import eu.siacs.conversations.xmpp.Jid;
 import eu.siacs.conversations.xmpp.XmppConnection;
 import eu.siacs.conversations.xmpp.pep.Avatar;
@@ -61,4 +62,8 @@ public class AvatarManager extends AbstractManager {
             Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": deleted avatar metadata node");
         }
     }
+
+    public boolean hasPepToVCardConversion() {
+        return getManager(DiscoManager.class).hasAccountFeature(Namespace.AVATAR_CONVERSION);
+    }
 }

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

@@ -16,6 +16,7 @@ import eu.siacs.conversations.xmpp.XmppConnection;
 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.bookmark2.Password;
 import im.conversations.android.xmpp.model.pubsub.Items;
 import im.conversations.android.xmpp.model.pubsub.event.Retract;
 import java.util.Collection;
@@ -89,18 +90,24 @@ public class BookmarkManager extends AbstractBookmarkManager {
         }
     }
 
-    public ListenableFuture<Void> publishBookmark(final Jid address, final boolean autoJoin) {
-        return publishBookmark(address, autoJoin, null);
-    }
-
-    public ListenableFuture<Void> publishBookmark(
-            final Jid address, final boolean autoJoin, final String nick) {
+    public ListenableFuture<Void> publish(final Bookmark bookmark) {
+        final var address = bookmark.getJid();
+        final var name = bookmark.getBookmarkName();
+        final var nick = bookmark.getNick();
+        final String password = bookmark.getPassword();
         final var itemId = address.toString();
         final var conference = new Conference();
-        conference.setAutoJoin(autoJoin);
+        conference.setAutoJoin(bookmark.autojoin());
         if (nick != null) {
             conference.addExtension(new Nick()).setContent(nick);
         }
+        if (name != null) {
+            conference.setConferenceName(name);
+        }
+        if (password != null) {
+            conference.addExtension(new Password()).setContent(password);
+        }
+        conference.addExtension(bookmark.getExtensions());
         return Futures.transform(
                 getManager(PepManager.class)
                         .publish(conference, itemId, NodeConfiguration.WHITELIST_MAX_ITEMS),
@@ -108,7 +115,7 @@ public class BookmarkManager extends AbstractBookmarkManager {
                 MoreExecutors.directExecutor());
     }
 
-    public ListenableFuture<Void> retractBookmark(final Jid address) {
+    public ListenableFuture<Void> retract(final Jid address) {
         final var itemId = address.toString();
         return Futures.transform(
                 getManager(PepManager.class).retract(itemId, Namespace.BOOKMARKS2),
@@ -132,4 +139,12 @@ public class BookmarkManager extends AbstractBookmarkManager {
         Log.d(Config.LOGTAG, getAccount().getJid().asBareJid() + ": purged bookmarks");
         this.deleteAllItems();
     }
+
+    public boolean hasFeature() {
+        final var pep = getManager(PepManager.class);
+        final var disco = getManager(DiscoManager.class);
+        return pep.hasPublishOptions()
+                && pep.hasConfigNodeMax()
+                && disco.hasAccountFeature(Namespace.BOOKMARKS2_COMPAT);
+    }
 }

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

@@ -331,7 +331,7 @@ public class DiscoManager extends AbstractManager {
         if (appSettings.isBroadcastLastActivity()) {
             features.add(Namespace.IDLE);
         }
-        if (connection.getFeatures().bookmarks2()) {
+        if (getManager(BookmarkManager.class).hasFeature()) {
             features.add(Namespace.BOOKMARKS2 + "+notify");
         } else {
             features.add(Namespace.BOOKMARKS + "+notify");
@@ -427,6 +427,11 @@ public class DiscoManager extends AbstractManager {
         return infoQuery != null && infoQuery.hasFeature(feature);
     }
 
+    public boolean hasAccountFeature(final String feature) {
+        final var infoQuery = this.get(getAccount().getJid().asBareJid());
+        return infoQuery != null && infoQuery.hasFeature(feature);
+    }
+
     private void put(final Jid address, final InfoQuery infoQuery) {
         synchronized (this.entityInformation) {
             this.entityInformation.put(address, infoQuery);

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

@@ -1,13 +1,17 @@
 package eu.siacs.conversations.xmpp.manager;
 
 import android.util.Log;
+import com.google.common.util.concurrent.ListenableFuture;
 import eu.siacs.conversations.Config;
 import eu.siacs.conversations.entities.Bookmark;
 import eu.siacs.conversations.services.XmppConnectionService;
+import eu.siacs.conversations.xml.Namespace;
 import eu.siacs.conversations.xmpp.Jid;
 import eu.siacs.conversations.xmpp.XmppConnection;
+import im.conversations.android.xmpp.NodeConfiguration;
 import im.conversations.android.xmpp.model.bookmark.Storage;
 import im.conversations.android.xmpp.model.pubsub.Items;
+import java.util.Collection;
 import java.util.Map;
 
 public class LegacyBookmarkManager extends AbstractBookmarkManager {
@@ -19,9 +23,8 @@ public class LegacyBookmarkManager extends AbstractBookmarkManager {
 
     public void handleItems(final Items items) {
         final var account = this.getAccount();
-        final var connection = this.connection;
-        if (connection.getFeatures().bookmarksConversion()) {
-            if (connection.getFeatures().bookmarks2()) {
+        if (this.hasConversion()) {
+            if (getManager(BookmarkManager.class).hasFeature()) {
                 Log.w(
                         Config.LOGTAG,
                         account.getJid().asBareJid()
@@ -40,4 +43,17 @@ public class LegacyBookmarkManager extends AbstractBookmarkManager {
                             + " not detected");
         }
     }
+
+    public boolean hasConversion() {
+        return getManager(PepManager.class).hasPublishOptions()
+                && getManager(DiscoManager.class).hasAccountFeature(Namespace.BOOKMARKS_CONVERSION);
+    }
+
+    public ListenableFuture<Void> publish(final Collection<Bookmark> bookmarks) {
+        final var storage = new Storage();
+        for (final var bookmark : bookmarks) {
+            storage.addChild(bookmark);
+        }
+        return getManager(PepManager.class).publishSingleton(storage, NodeConfiguration.WHITELIST);
+    }
 }

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

@@ -1,12 +1,10 @@
 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;
@@ -62,7 +60,11 @@ public class MessageDisplayedSynchronizationManager extends AbstractManager {
 
                     @Override
                     public void onFailure(@NonNull Throwable t) {
-                        Log.d(Config.LOGTAG,getAccount().getJid().asBareJid()+": could not retrieve MDS items", 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 🔗

@@ -5,6 +5,7 @@ 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.xml.Namespace;
 import eu.siacs.conversations.xmpp.Jid;
 import eu.siacs.conversations.xmpp.XmppConnection;
 import im.conversations.android.xmpp.NodeConfiguration;
@@ -48,10 +49,13 @@ public class NickManager extends AbstractManager {
         service.updateAccountUi();
     }
 
-    public ListenableFuture<Void> publishNick(final String name) {
-        final Nick nick = new Nick();
-        nick.setContent(name);
-        return getManager(PepManager.class).publishSingleton(nick, NodeConfiguration.PRESENCE);
+    public ListenableFuture<Void> publish(final String name) {
+        if (Strings.isNullOrEmpty(name)) {
+            return getManager(PepManager.class).delete(Namespace.NICK);
+        } else {
+            return getManager(PepManager.class)
+                    .publishSingleton(new Nick(name), NodeConfiguration.PRESENCE);
+        }
     }
 
     public void handleDelete(final Jid from) {

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

@@ -1,7 +1,10 @@
 package eu.siacs.conversations.xmpp.manager;
 
 import android.content.Context;
+import com.google.common.util.concurrent.Futures;
 import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.MoreExecutors;
+import eu.siacs.conversations.xml.Namespace;
 import eu.siacs.conversations.xmpp.Jid;
 import eu.siacs.conversations.xmpp.XmppConnection;
 import im.conversations.android.xmpp.NodeConfiguration;
@@ -43,6 +46,19 @@ public class PepManager extends AbstractManager {
         return pubSubManager().retract(pepService(), itemId, node);
     }
 
+    public ListenableFuture<Void> delete(final String node) {
+        final var future = pubSubManager().delete(pepService(), node);
+        return Futures.transform(future, iq -> null, MoreExecutors.directExecutor());
+    }
+
+    public boolean hasPublishOptions() {
+        return getManager(DiscoManager.class).hasAccountFeature(Namespace.PUBSUB_PUBLISH_OPTIONS);
+    }
+
+    public boolean hasConfigNodeMax() {
+        return getManager(DiscoManager.class).hasAccountFeature(Namespace.PUBSUB_CONFIG_NODE_MAX);
+    }
+
     private PubSubManager pubSubManager() {
         return getManager(PubSubManager.class);
     }

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

@@ -4,6 +4,7 @@ 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.ListenableFuture;
 import com.google.common.util.concurrent.MoreExecutors;
 import eu.siacs.conversations.Config;
 import eu.siacs.conversations.entities.Bookmark;
@@ -13,6 +14,7 @@ import eu.siacs.conversations.xmpp.XmppConnection;
 import im.conversations.android.xmpp.model.bookmark.Storage;
 import im.conversations.android.xmpp.model.stanza.Iq;
 import im.conversations.android.xmpp.model.storage.PrivateStorage;
+import java.util.Collection;
 import java.util.Map;
 
 public class PrivateStorageManager extends AbstractBookmarkManager {
@@ -52,4 +54,15 @@ public class PrivateStorageManager extends AbstractBookmarkManager {
                 },
                 MoreExecutors.directExecutor());
     }
+
+    public ListenableFuture<Void> publishBookmarks(Collection<Bookmark> bookmarks) {
+        final var iq = new Iq(Iq.Type.SET);
+        final var privateStorage = iq.addExtension(new PrivateStorage());
+        final var storage = privateStorage.addExtension(new Storage());
+        for (final var bookmark : bookmarks) {
+            storage.addChild(bookmark);
+        }
+        return Futures.transform(
+                connection.sendIqPacket(iq), result -> null, MoreExecutors.directExecutor());
+    }
 }

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

@@ -345,6 +345,16 @@ public class PubSubManager extends AbstractManager {
         return connection.sendIqPacket(iq);
     }
 
+    public ListenableFuture<Iq> delete(final Jid address, final String node) {
+        final var iq = new Iq(Iq.Type.SET);
+        iq.setTo(address);
+        final var pubSub = iq.addExtension(new PubSubOwner());
+        final var delete =
+                pubSub.addExtension(new im.conversations.android.xmpp.model.pubsub.owner.Delete());
+        delete.setNode(node);
+        return connection.sendIqPacket(iq);
+    }
+
     private static class PubSubExceptionTransformer<V>
             implements AsyncFunction<IqErrorException, V> {
 

src/main/java/im/conversations/android/xmpp/NodeConfiguration.java 🔗

@@ -28,6 +28,12 @@ public class NodeConfiguration implements Map<String, Object> {
                             .put(PERSIST_ITEMS, Boolean.TRUE)
                             .put(ACCESS_MODEL, "presence")
                             .build());
+    public static final NodeConfiguration WHITELIST =
+            new NodeConfiguration(
+                    new ImmutableMap.Builder<String, Object>()
+                            .put(PERSIST_ITEMS, Boolean.TRUE)
+                            .put(ACCESS_MODEL, "whitelist")
+                            .build());
     public static final NodeConfiguration WHITELIST_MAX_ITEMS =
             new NodeConfiguration(
                     new ImmutableMap.Builder<String, Object>()

src/main/java/im/conversations/android/xmpp/model/pubsub/owner/Delete.java 🔗

@@ -0,0 +1,16 @@
+package im.conversations.android.xmpp.model.pubsub.owner;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+
+@XmlElement
+public class Delete extends Extension {
+
+    public Delete() {
+        super(Delete.class);
+    }
+
+    public void setNode(final String node) {
+        this.setAttribute("node", node);
+    }
+}

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

@@ -1,14 +1,16 @@
 package im.conversations.android.xmpp.processor;
 
-import android.text.TextUtils;
 import android.util.Log;
+import com.google.common.base.Strings;
 import eu.siacs.conversations.Config;
 import eu.siacs.conversations.entities.Account;
 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.LegacyBookmarkManager;
 import eu.siacs.conversations.xmpp.manager.MessageDisplayedSynchronizationManager;
+import eu.siacs.conversations.xmpp.manager.NickManager;
 import eu.siacs.conversations.xmpp.manager.PrivateStorageManager;
 import eu.siacs.conversations.xmpp.manager.RosterManager;
 import im.conversations.android.xmpp.model.stanza.Iq;
@@ -45,12 +47,13 @@ public class BindProcessor extends XmppConnection.Delegate implements Runnable {
         }
 
         if (loggedInSuccessfully) {
-            if (!TextUtils.isEmpty(account.getDisplayName())) {
+            final String displayName = account.getDisplayName();
+            if (!Strings.isNullOrEmpty(displayName)) {
                 Log.d(
                         Config.LOGTAG,
                         account.getJid().asBareJid()
                                 + ": display name wasn't empty on first log in. publishing");
-                service.publishDisplayName(account);
+                getManager(NickManager.class).publish(displayName);
             }
         }
 
@@ -66,10 +69,13 @@ public class BindProcessor extends XmppConnection.Delegate implements Runnable {
 
         getManager(RosterManager.class).request();
 
-        if (features.bookmarks2()) {
+        if (getManager(BookmarkManager.class).hasFeature()) {
             connection.getManager(BookmarkManager.class).fetch();
-            // log that we use bookmarks 1 and wait for +notify
-        } else if (!features.bookmarksConversion()) {
+        } else if (getManager(LegacyBookmarkManager.class).hasConversion()) {
+            Log.d(
+                    Config.LOGTAG,
+                    account.getJid() + ": not fetching bookmarks. waiting for server to push");
+        } else {
             connection.getManager(PrivateStorageManager.class).fetchBookmarks();
         }