@@ -69,21 +69,6 @@ public class IqGenerator extends AbstractGenerator {
         return packet;
     }
 
-    public Iq retrieveBookmarks() {
-        return retrieve(Namespace.BOOKMARKS2, null);
-    }
-
-    public Iq retrieveMds() {
-        return retrieve(Namespace.MDS_DISPLAYED, null);
-    }
-
-    public Iq publishNick(String nick) {
-        final Element item = new Element("item");
-        item.setAttribute("id", "current");
-        item.addChild("nick", Namespace.NICK).setContent(nick);
-        return publish(Namespace.NICK, item);
-    }
-
     public Iq deleteNode(final String node) {
         final var packet = new Iq(Iq.Type.SET);
         final Element pubsub = packet.addChild("pubsub", Namespace.PUBSUB_OWNER);
@@ -91,45 +76,6 @@ public class IqGenerator extends AbstractGenerator {
         return packet;
     }
 
-    public Iq deleteItem(final String node, final String id) {
-        final var packet = new Iq(Iq.Type.SET);
-        final Element pubsub = packet.addChild("pubsub", Namespace.PUBSUB);
-        final Element retract = pubsub.addChild("retract");
-        retract.setAttribute("node", node);
-        retract.setAttribute("notify", "true");
-        retract.addChild("item").setAttribute("id", id);
-        return packet;
-    }
-
-    public Iq publishAvatar(Avatar avatar, Bundle options) {
-        final Element item = new Element("item");
-        item.setAttribute("id", avatar.sha1sum);
-        final Element data = item.addChild("data", Namespace.AVATAR_DATA);
-        data.setContent(avatar.image);
-        return publish(Namespace.AVATAR_DATA, item, options);
-    }
-
-    public Iq publishElement(
-            final String namespace, final Element element, String id, final Bundle options) {
-        final Element item = new Element("item");
-        item.setAttribute("id", id);
-        item.addChild(element);
-        return publish(namespace, item, options);
-    }
-
-    public Iq publishAvatarMetadata(final Avatar avatar, final Bundle options) {
-        final Element item = new Element("item");
-        item.setAttribute("id", avatar.sha1sum);
-        final Element metadata = item.addChild("metadata", Namespace.AVATAR_METADATA);
-        final Element info = metadata.addChild("info");
-        info.setAttribute("bytes", avatar.size);
-        info.setAttribute("id", avatar.sha1sum);
-        info.setAttribute("height", avatar.height);
-        info.setAttribute("width", avatar.height);
-        info.setAttribute("type", avatar.type);
-        return publish(Namespace.AVATAR_METADATA, item, options);
-    }
-
     public Iq retrievePepAvatar(final Avatar avatar) {
         final Element item = new Element("item");
         item.setAttribute("id", avatar.sha1sum);
  
  
  
    
    @@ -143,7 +143,6 @@ 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;
 import im.conversations.android.xmpp.Entity;
 import im.conversations.android.xmpp.IqErrorException;
 import im.conversations.android.xmpp.model.avatar.Metadata;
@@ -1653,7 +1652,6 @@ public class XmppConnectionService extends Service {
         final XmppConnection connection = new XmppConnection(account, this);
         connection.setOnJinglePacketReceivedListener((mJingleConnectionManager::deliverPacket));
         connection.addOnAdvancedStreamFeaturesAvailableListener(this.mMessageArchiveService);
-        connection.addOnAdvancedStreamFeaturesAvailableListener(this.mAvatarService);
         return connection;
     }
 
@@ -3561,6 +3559,7 @@ public class XmppConnectionService extends Service {
                         updateAccountUi();
                     }
                 };
+        // TODO execute this via the respective Managers
         deleteVcardAvatar(account, onDeleted);
         deletePepNode(account, Namespace.AVATAR_DATA);
         deletePepNode(account, Namespace.AVATAR_METADATA, onDeleted);
@@ -4368,15 +4367,36 @@ public class XmppConnectionService extends Service {
         final Bitmap.CompressFormat format = Config.AVATAR_FORMAT;
         final int size = Config.AVATAR_SIZE;
         final Avatar avatar = getFileBackend().getPepAvatar(image, size, format);
-        if (avatar != null) {
-            if (!getFileBackend().save(avatar)) {
-                Log.d(Config.LOGTAG, "unable to save vcard");
-                callback.onAvatarPublicationFailed(R.string.error_saving_avatar);
-                return;
-            }
-            publishAvatar(account, avatar, open, callback);
-        } else {
+        if (avatar == null) {
             callback.onAvatarPublicationFailed(R.string.error_publish_avatar_converting);
+            return;
+        }
+        if (fileBackend.save(avatar)) {
+            final var connection = account.getXmppConnection();
+            final var future = connection.getManager(AvatarManager.class).publish(avatar, open);
+            Futures.addCallback(
+                    future,
+                    new FutureCallback<Void>() {
+                        @Override
+                        public void onSuccess(Void result) {
+                            callback.onAvatarPublicationSucceeded();
+                        }
+
+                        @Override
+                        public void onFailure(@NonNull Throwable t) {
+                            Log.d(
+                                    Config.LOGTAG,
+                                    account.getJid().asBareJid() + ": could not publish avatar",
+                                    t);
+                            callback.onAvatarPublicationFailed(
+                                    R.string.error_publish_avatar_server_reject);
+                        }
+                    },
+                    MoreExecutors.directExecutor());
+
+        } else {
+            Log.d(Config.LOGTAG, "could not save avatar");
+            callback.onAvatarPublicationFailed(R.string.error_saving_avatar);
         }
     }
 
@@ -4430,211 +4450,6 @@ public class XmppConnectionService extends Service {
                 });
     }
 
-    public void publishAvatar(
-            final Account account,
-            final Avatar avatar,
-            final boolean open,
-            final OnAvatarPublication callback) {
-        final Bundle options;
-        if (account.getXmppConnection().getFeatures().pepPublishOptions()) {
-            options = open ? PublishOptions.openAccess() : PublishOptions.presenceAccess();
-        } else {
-            options = null;
-        }
-        publishAvatar(account, avatar, options, true, callback);
-    }
-
-    public void publishAvatar(
-            Account account,
-            final Avatar avatar,
-            final Bundle options,
-            final boolean retry,
-            final OnAvatarPublication callback) {
-        Log.d(
-                Config.LOGTAG,
-                account.getJid().asBareJid() + ": publishing avatar. options=" + options);
-        final Iq packet = this.mIqGenerator.publishAvatar(avatar, options);
-        this.sendIqPacket(
-                account,
-                packet,
-                result -> {
-                    if (result.getType() == Iq.Type.RESULT) {
-                        publishAvatarMetadata(account, avatar, options, true, callback);
-                    } else if (retry && PublishOptions.preconditionNotMet(result)) {
-                        pushNodeConfiguration(
-                                account,
-                                Namespace.AVATAR_DATA,
-                                options,
-                                new OnConfigurationPushed() {
-                                    @Override
-                                    public void onPushSucceeded() {
-                                        Log.d(
-                                                Config.LOGTAG,
-                                                account.getJid().asBareJid()
-                                                        + ": changed node configuration for avatar"
-                                                        + " node");
-                                        publishAvatar(account, avatar, options, false, callback);
-                                    }
-
-                                    @Override
-                                    public void onPushFailed() {
-                                        Log.d(
-                                                Config.LOGTAG,
-                                                account.getJid().asBareJid()
-                                                        + ": unable to change node configuration"
-                                                        + " for avatar node");
-                                        publishAvatar(account, avatar, null, false, callback);
-                                    }
-                                });
-                    } else {
-                        Element error = result.findChild("error");
-                        Log.d(
-                                Config.LOGTAG,
-                                account.getJid().asBareJid()
-                                        + ": server rejected avatar "
-                                        + (avatar.size / 1024)
-                                        + "KiB "
-                                        + (error != null ? error.toString() : ""));
-                        if (callback != null) {
-                            callback.onAvatarPublicationFailed(
-                                    R.string.error_publish_avatar_server_reject);
-                        }
-                    }
-                });
-    }
-
-    public void publishAvatarMetadata(
-            Account account,
-            final Avatar avatar,
-            final Bundle options,
-            final boolean retry,
-            final OnAvatarPublication callback) {
-        final Iq packet =
-                XmppConnectionService.this.mIqGenerator.publishAvatarMetadata(avatar, options);
-        sendIqPacket(
-                account,
-                packet,
-                result -> {
-                    if (result.getType() == Iq.Type.RESULT) {
-                        if (account.setAvatar(avatar.getFilename())) {
-                            getAvatarService().clear(account);
-                            databaseBackend.updateAccount(account);
-                            notifyAccountAvatarHasChanged(account);
-                        }
-                        Log.d(
-                                Config.LOGTAG,
-                                account.getJid().asBareJid()
-                                        + ": published avatar "
-                                        + (avatar.size / 1024)
-                                        + "KiB");
-                        if (callback != null) {
-                            callback.onAvatarPublicationSucceeded();
-                        }
-                    } else if (retry && PublishOptions.preconditionNotMet(result)) {
-                        pushNodeConfiguration(
-                                account,
-                                Namespace.AVATAR_METADATA,
-                                options,
-                                new OnConfigurationPushed() {
-                                    @Override
-                                    public void onPushSucceeded() {
-                                        Log.d(
-                                                Config.LOGTAG,
-                                                account.getJid().asBareJid()
-                                                        + ": changed node configuration for avatar"
-                                                        + " meta data node");
-                                        publishAvatarMetadata(
-                                                account, avatar, options, false, callback);
-                                    }
-
-                                    @Override
-                                    public void onPushFailed() {
-                                        Log.d(
-                                                Config.LOGTAG,
-                                                account.getJid().asBareJid()
-                                                        + ": unable to change node configuration"
-                                                        + " for avatar meta data node");
-                                        publishAvatarMetadata(
-                                                account, avatar, null, false, callback);
-                                    }
-                                });
-                    } else {
-                        if (callback != null) {
-                            callback.onAvatarPublicationFailed(
-                                    R.string.error_publish_avatar_server_reject);
-                        }
-                    }
-                });
-    }
-
-    public void republishAvatarIfNeeded(final Account account) {
-        if (account.getAxolotlService().isPepBroken()) {
-            Log.d(
-                    Config.LOGTAG,
-                    account.getJid().asBareJid()
-                            + ": skipping republication of avatar because pep is broken");
-            return;
-        }
-        final Iq packet = this.mIqGenerator.retrieveAvatarMetaData(null);
-        this.sendIqPacket(
-                account,
-                packet,
-                new Consumer<Iq>() {
-
-                    private Avatar parseAvatar(final Iq packet) {
-                        final var pubsub = packet.getExtension(PubSub.class);
-                        if (pubsub == null) {
-                            return null;
-                        }
-                        final var items = pubsub.getItems();
-                        if (items == null) {
-                            return null;
-                        }
-                        final var item = items.getFirstItemWithId(Metadata.class);
-                        if (item == null) {
-                            return null;
-                        }
-                        return Avatar.parseMetadata(item.getKey(), item.getValue());
-                    }
-
-                    private boolean errorIsItemNotFound(Iq packet) {
-                        Element error = packet.findChild("error");
-                        return packet.getType() == Iq.Type.ERROR
-                                && error != null
-                                && error.hasChild("item-not-found");
-                    }
-
-                    @Override
-                    public void accept(final Iq packet) {
-                        if (packet.getType() == Iq.Type.RESULT || errorIsItemNotFound(packet)) {
-                            final Avatar serverAvatar = parseAvatar(packet);
-                            if (serverAvatar == null && account.getAvatar() != null) {
-                                final Avatar avatar =
-                                        fileBackend.getStoredPepAvatar(account.getAvatar());
-                                if (avatar != null) {
-                                    Log.d(
-                                            Config.LOGTAG,
-                                            account.getJid().asBareJid()
-                                                    + ": avatar on server was null. republishing");
-                                    // publishing as 'open' - old server (that requires
-                                    // republication) likely doesn't support access models anyway
-                                    publishAvatar(
-                                            account,
-                                            fileBackend.getStoredPepAvatar(account.getAvatar()),
-                                            true,
-                                            null);
-                                } else {
-                                    Log.e(
-                                            Config.LOGTAG,
-                                            account.getJid().asBareJid()
-                                                    + ": error rereading avatar");
-                                }
-                            }
-                        }
-                    }
-                });
-    }
-
     public void cancelAvatarFetches(final Account account) {
         synchronized (mInProgressAvatarFetches) {
             for (final Iterator<String> iterator = mInProgressAvatarFetches.iterator();
  
  
  
    
    @@ -1,6 +1,9 @@
 package eu.siacs.conversations.xmpp.manager;
 
 import android.util.Log;
+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.Contact;
 import eu.siacs.conversations.services.XmppConnectionService;
@@ -8,6 +11,9 @@ 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;
+import im.conversations.android.xmpp.NodeConfiguration;
+import im.conversations.android.xmpp.model.avatar.Data;
+import im.conversations.android.xmpp.model.avatar.Info;
 import im.conversations.android.xmpp.model.avatar.Metadata;
 import im.conversations.android.xmpp.model.pubsub.Items;
 
@@ -24,31 +30,35 @@ public class AvatarManager extends AbstractManager {
         final var account = getAccount();
         // TODO support retract
         final var entry = items.getFirstItemWithId(Metadata.class);
+        Log.d(Config.LOGTAG, "<-- " + entry + " (" + from + ")");
         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);
+        if (avatar == null) {
+            Log.d(Config.LOGTAG, "could not parse avatar metadata from " + from);
+            return;
+        }
+        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.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();
-                    }
+                    service.updateRosterUi();
                 }
-            } else if (service.isDataSaverDisabled()) {
-                service.fetchAvatar(account, avatar);
             }
+        } else if (service.isDataSaverDisabled()) {
+            // TODO use internal mechanism to fetch PEP avatars
+            service.fetchAvatar(account, avatar);
         }
     }
 
@@ -63,6 +73,29 @@ public class AvatarManager extends AbstractManager {
         }
     }
 
+    public ListenableFuture<Void> publish(final Avatar avatar, final boolean open) {
+        final NodeConfiguration configuration =
+                open ? NodeConfiguration.OPEN : NodeConfiguration.PRESENCE;
+        final var avatarData = new Data();
+        avatarData.setContent(avatar.getImageAsBytes());
+        final var future =
+                getManager(PepManager.class).publish(avatarData, avatar.sha1sum, configuration);
+        return Futures.transformAsync(
+                future,
+                v -> {
+                    final var id = avatar.sha1sum;
+                    final var metadata = new Metadata();
+                    final var info = metadata.addExtension(new Info());
+                    info.setBytes(avatar.size);
+                    info.setId(avatar.sha1sum);
+                    info.setHeight(avatar.height);
+                    info.setWidth(avatar.width);
+                    info.setType(avatar.type);
+                    return getManager(PepManager.class).publish(metadata, id, configuration);
+                },
+                MoreExecutors.directExecutor());
+    }
+
     public boolean hasPepToVCardConversion() {
         return getManager(DiscoManager.class).hasAccountFeature(Namespace.AVATAR_CONVERSION);
     }