Detailed changes
  
  
    
    @@ -87,13 +87,6 @@ public class IqGenerator extends AbstractGenerator {
         return packet;
     }
 
-    public Iq retrieveVcardAvatar(final Jid to) {
-        final Iq packet = new Iq(Iq.Type.GET);
-        packet.setTo(to);
-        packet.addChild("vCard", "vcard-temp");
-        return packet;
-    }
-
     public Iq retrieveAvatarMetaData(final Jid to) {
         final Iq packet = retrieve("urn:xmpp:avatar:metadata", null);
         if (to != null) {
  
  
  
    
    @@ -491,7 +491,6 @@ public class PresenceParser extends AbstractParser
 
     @Override
     public void accept(final im.conversations.android.xmpp.model.stanza.Presence packet) {
-        // Log.d(Config.LOGTAG,"<--"+packet);
         if (packet.hasChild("x", Namespace.MUC_USER)) {
             this.parseConferencePresence(packet);
         } else if (packet.hasChild("x", "http://jabber.org/protocol/muc")) {
  
  
  
    
    @@ -1220,7 +1220,7 @@ public class FileBackend {
         try {
             ByteArrayOutputStream mByteArrayOutputStream = new ByteArrayOutputStream();
             Base64OutputStream mBase64OutputStream =
-                    new Base64OutputStream(mByteArrayOutputStream, Base64.DEFAULT);
+                    new Base64OutputStream(mByteArrayOutputStream, Base64.DEFAULT | Base64.NO_WRAP);
             MessageDigest digest = MessageDigest.getInstance("SHA-1");
             DigestOutputStream mDigestOutputStream =
                     new DigestOutputStream(mBase64OutputStream, digest);
  
  
  
    
    @@ -4366,53 +4366,39 @@ public class XmppConnectionService extends Service {
     }
 
     private void publishMucAvatar(
-            Conversation conversation, Avatar avatar, OnAvatarPublication callback) {
+            final Conversation conversation,
+            final Avatar avatar,
+            final OnAvatarPublication callback) {
         final var account = conversation.getAccount();
-        final Iq retrieve = mIqGenerator.retrieveVcardAvatar(avatar);
-        sendIqPacket(
-                account,
-                retrieve,
-                (response) -> {
-                    boolean itemNotFound =
-                            response.getType() == Iq.Type.ERROR
-                                    && response.hasChild("error")
-                                    && response.findChild("error").hasChild("item-not-found");
-                    if (response.getType() == Iq.Type.RESULT || itemNotFound) {
-                        Element vcard = response.findChild("vCard", "vcard-temp");
-                        if (vcard == null) {
-                            vcard = new Element("vCard", "vcard-temp");
-                        }
-                        Element photo = vcard.findChild("PHOTO");
-                        if (photo == null) {
-                            photo = vcard.addChild("PHOTO");
-                        }
-                        photo.clearChildren();
-                        photo.addChild("TYPE").setContent(avatar.type);
-                        photo.addChild("BINVAL").setContent(avatar.image);
-                        final Iq publication = new Iq(Iq.Type.SET);
-                        publication.setTo(conversation.getJid().asBareJid());
-                        publication.addChild(vcard);
-                        sendIqPacket(
-                                account,
-                                publication,
-                                (publicationResponse) -> {
-                                    if (publicationResponse.getType() == Iq.Type.RESULT) {
-                                        callback.onAvatarPublicationSucceeded();
-                                    } else {
-                                        Log.d(
-                                                Config.LOGTAG,
-                                                "failed to publish vcard "
-                                                        + publicationResponse.getErrorCondition());
-                                        callback.onAvatarPublicationFailed(
-                                                R.string.error_publish_avatar_server_reject);
-                                    }
-                                });
-                    } else {
-                        Log.d(Config.LOGTAG, "failed to request vcard " + response);
+        final var connection = account.getXmppConnection();
+        final var future =
+                connection
+                        .getManager(VCardManager.class)
+                        .publishPhoto(
+                                avatar.owner,
+                                avatar.type,
+                                BaseEncoding.base64().decode(avatar.image));
+        Futures.addCallback(
+                future,
+                new FutureCallback<>() {
+                    @Override
+                    public void onSuccess(Void result) {
+                        Log.d(Config.LOGTAG, "published muc avatar");
+                        final var c = account.getRoster().getContact(avatar.owner);
+                        c.setAvatar(avatar);
+                        getAvatarService().clear(c);
+                        getAvatarService().clear(conversation.getMucOptions());
+                        callback.onAvatarPublicationSucceeded();
+                    }
+
+                    @Override
+                    public void onFailure(@NonNull Throwable t) {
+                        Log.d(Config.LOGTAG, "could not publish muc avatar", t);
                         callback.onAvatarPublicationFailed(
-                                R.string.error_publish_avatar_no_server_support);
+                                R.string.error_publish_avatar_server_reject);
                     }
-                });
+                },
+                MoreExecutors.directExecutor());
     }
 
     public void cancelAvatarFetches(final Account account) {
@@ -4563,6 +4549,8 @@ public class XmppConnectionService extends Service {
                 getAvatarService().clear(account);
                 updateAccountUi();
             } else {
+                // TODO if this is a MUC clear MucOptions too
+                // TODO do the same clearing for when setting a cached version
                 final Contact contact = account.getRoster().getContact(avatar.owner);
                 contact.setAvatar(avatar);
                 account.getXmppConnection().getManager(RosterManager.class).writeToDatabaseAsync();
  
  
  
    
    @@ -8,7 +8,11 @@ import com.google.common.util.concurrent.MoreExecutors;
 import eu.siacs.conversations.Config;
 import eu.siacs.conversations.xmpp.Jid;
 import eu.siacs.conversations.xmpp.XmppConnection;
+import im.conversations.android.xmpp.IqErrorException;
+import im.conversations.android.xmpp.model.error.Condition;
 import im.conversations.android.xmpp.model.stanza.Iq;
+import im.conversations.android.xmpp.model.vcard.BinaryValue;
+import im.conversations.android.xmpp.model.vcard.Photo;
 import im.conversations.android.xmpp.model.vcard.VCard;
 import java.util.Objects;
 
@@ -54,8 +58,12 @@ public class VCardManager extends AbstractManager {
     }
 
     public ListenableFuture<Void> publish(final VCard vCard) {
+        return publish(getAccount().getJid().asBareJid(), vCard);
+    }
+
+    public ListenableFuture<Void> publish(final Jid address, final VCard vCard) {
         final var iq = new Iq(Iq.Type.SET, vCard);
-        iq.setTo(getAccount().getJid().asBareJid());
+        iq.setTo(address);
         return Futures.transform(
                 connection.sendIqPacket(iq), result -> null, MoreExecutors.directExecutor());
     }
@@ -78,4 +86,42 @@ public class VCardManager extends AbstractManager {
                 },
                 MoreExecutors.directExecutor());
     }
+
+    public ListenableFuture<Void> publishPhoto(
+            final Jid address, final String type, final byte[] image) {
+        final var retrieveFuture = this.retrieve(address);
+
+        final var caughtFuture =
+                Futures.catchingAsync(
+                        retrieveFuture,
+                        IqErrorException.class,
+                        ex -> {
+                            final var error = ex.getError();
+                            if (error != null
+                                    && error.getCondition() instanceof Condition.ItemNotFound) {
+                                return Futures.immediateFuture(null);
+                            } else {
+                                return Futures.immediateFailedFuture(ex);
+                            }
+                        },
+                        MoreExecutors.directExecutor());
+
+        return Futures.transformAsync(
+                caughtFuture,
+                existing -> {
+                    final VCard vCard;
+                    if (existing == null) {
+                        Log.d(Config.LOGTAG, "item-not-found. created fresh vCard");
+                        vCard = new VCard();
+                    } else {
+                        vCard = existing;
+                    }
+                    final var photo = new Photo();
+                    photo.setType(type);
+                    photo.addExtension(new BinaryValue()).setContent(image);
+                    vCard.setExtension(photo);
+                    return publish(address, vCard);
+                },
+                MoreExecutors.directExecutor());
+    }
 }
  
  
  
    
    @@ -59,6 +59,16 @@ public class Extension extends Element {
         return child;
     }
 
+    public <T extends Extension> T setExtension(T child) {
+        final var iterator = this.children.iterator();
+        while (iterator.hasNext()) {
+            if (iterator.next().getClass().isInstance(child)) {
+                iterator.remove();
+            }
+        }
+        return this.addExtension(child);
+    }
+
     public void addExtensions(final Collection<? extends Extension> extensions) {
         for (final Extension extension : extensions) {
             addExtension(extension);
  
  
  
    
    @@ -12,4 +12,9 @@ public class Photo extends Extension {
     public BinaryValue getBinaryValue() {
         return this.getExtension(BinaryValue.class);
     }
+
+    public void setType(final String value) {
+        final var type = this.addExtension(new Type());
+        type.setContent(value);
+    }
 }
  
  
  
    
    @@ -0,0 +1,12 @@
+package im.conversations.android.xmpp.model.vcard;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+
+@XmlElement(name = "TYPE")
+public class Type extends Extension {
+
+    public Type() {
+        super(Type.class);
+    }
+}