Detailed changes
  
  
    
    @@ -4,7 +4,6 @@ import android.os.Bundle;
 import android.util.Base64;
 import android.util.Log;
 import eu.siacs.conversations.Config;
-import eu.siacs.conversations.R;
 import eu.siacs.conversations.crypto.axolotl.AxolotlService;
 import eu.siacs.conversations.entities.Account;
 import eu.siacs.conversations.entities.Bookmark;
@@ -39,19 +38,6 @@ public class IqGenerator extends AbstractGenerator {
         super(service);
     }
 
-    public Iq versionResponse(final Iq request) {
-        final var packet = request.generateResponse(Iq.Type.RESULT);
-        Element query = packet.query("jabber:iq:version");
-        query.addChild("name").setContent(mXmppConnectionService.getString(R.string.app_name));
-        query.addChild("version").setContent(getIdentityVersion());
-        if ("chromium".equals(android.os.Build.BRAND)) {
-            query.addChild("os").setContent("Chrome OS");
-        } else {
-            query.addChild("os").setContent("Android");
-        }
-        return packet;
-    }
-
     public Iq entityTimeResponse(final Iq request) {
         final Iq packet = request.generateResponse(Iq.Type.RESULT);
         Element time = packet.addChild("time", "urn:xmpp:time");
  
  
  
    
    @@ -7,6 +7,7 @@ import eu.siacs.conversations.entities.MucOptions;
 import eu.siacs.conversations.services.XmppConnectionService;
 import eu.siacs.conversations.xml.Element;
 import eu.siacs.conversations.xmpp.Jid;
+import eu.siacs.conversations.xmpp.XmppConnection;
 import im.conversations.android.xmpp.model.stanza.Stanza;
 import java.text.ParseException;
 import java.text.SimpleDateFormat;
@@ -15,14 +16,13 @@ import java.util.Date;
 import java.util.List;
 import java.util.Locale;
 
-public abstract class AbstractParser {
+public abstract class AbstractParser extends XmppConnection.Delegate {
 
     protected final XmppConnectionService mXmppConnectionService;
-    protected final Account account;
 
-    protected AbstractParser(final XmppConnectionService service, final Account account) {
+    protected AbstractParser(final XmppConnectionService service, final XmppConnection connection) {
+        super(service.getApplicationContext(), connection);
         this.mXmppConnectionService = service;
-        this.account = account;
     }
 
     public static Long parseTimestamp(Element element, Long d) {
  
  
  
    
    @@ -14,9 +14,11 @@ import eu.siacs.conversations.xml.Element;
 import eu.siacs.conversations.xml.Namespace;
 import eu.siacs.conversations.xmpp.Jid;
 import eu.siacs.conversations.xmpp.OnUpdateBlocklist;
+import eu.siacs.conversations.xmpp.XmppConnection;
 import eu.siacs.conversations.xmpp.manager.DiscoManager;
 import im.conversations.android.xmpp.model.disco.info.InfoQuery;
 import im.conversations.android.xmpp.model.stanza.Iq;
+import im.conversations.android.xmpp.model.version.Version;
 import java.io.ByteArrayInputStream;
 import java.security.cert.CertificateException;
 import java.security.cert.CertificateFactory;
@@ -37,8 +39,8 @@ import org.whispersystems.libsignal.state.PreKeyBundle;
 
 public class IqParser extends AbstractParser implements Consumer<Iq> {
 
-    public IqParser(final XmppConnectionService service, final Account account) {
-        super(service, account);
+    public IqParser(final XmppConnectionService service, final XmppConnection connection) {
+        super(service, connection);
     }
 
     public static List<Jid> items(final Iq packet) {
@@ -379,7 +381,7 @@ public class IqParser extends AbstractParser implements Consumer<Iq> {
 
     @Override
     public void accept(final Iq packet) {
-        final var connection = account.getXmppConnection();
+        final var account = getAccount();
         final boolean isGet = packet.getType() == Iq.Type.GET;
         if (packet.getType() == Iq.Type.ERROR || packet.getType() == Iq.Type.TIMEOUT) {
             return;
@@ -467,11 +469,10 @@ public class IqParser extends AbstractParser implements Consumer<Iq> {
                 || packet.hasChild("data", "http://jabber.org/protocol/ibb")
                 || packet.hasChild("close", "http://jabber.org/protocol/ibb")) {
             mXmppConnectionService.getJingleConnectionManager().deliverIbbPacket(account, packet);
-        } else if (packet.hasExtension(InfoQuery.class)) {
-            connection.getManager(DiscoManager.class).handleInfoQuery(packet);
-        } else if (packet.hasChild("query", "jabber:iq:version") && isGet) {
-            final Iq response = mXmppConnectionService.getIqGenerator().versionResponse(packet);
-            mXmppConnectionService.sendIqPacket(account, response, null);
+        } else if (packet.hasExtension(InfoQuery.class) && isGet) {
+            this.getManager(DiscoManager.class).handleInfoQuery(packet);
+        } else if (packet.hasExtension(Version.class) && isGet) {
+            this.getManager(DiscoManager.class).handleVersionRequest(packet);
         } else if (packet.hasChild("ping", "urn:xmpp:ping") && isGet) {
             final Iq response = packet.generateResponse(Iq.Type.RESULT);
             mXmppConnectionService.sendIqPacket(account, response, null);
  
  
  
    
    @@ -33,6 +33,7 @@ import eu.siacs.conversations.xml.Element;
 import eu.siacs.conversations.xml.LocalizedContent;
 import eu.siacs.conversations.xml.Namespace;
 import eu.siacs.conversations.xmpp.Jid;
+import eu.siacs.conversations.xmpp.XmppConnection;
 import eu.siacs.conversations.xmpp.chatstate.ChatState;
 import eu.siacs.conversations.xmpp.jingle.JingleConnectionManager;
 import eu.siacs.conversations.xmpp.jingle.JingleRtpConnection;
@@ -79,8 +80,8 @@ public class MessageParser extends AbstractParser
     private static final List<String> JINGLE_MESSAGE_ELEMENT_NAMES =
             Arrays.asList("accept", "propose", "proceed", "reject", "retract", "ringing", "finish");
 
-    public MessageParser(final XmppConnectionService service, final Account account) {
-        super(service, account);
+    public MessageParser(final XmppConnectionService service, final XmppConnection connection) {
+        super(service, connection);
     }
 
     private static String extractStanzaId(
@@ -489,6 +490,7 @@ public class MessageParser extends AbstractParser
 
     @Override
     public void accept(final im.conversations.android.xmpp.model.stanza.Message original) {
+        final var account = connection.getAccount();
         if (handleErrorMessage(account, original)) {
             return;
         }
@@ -510,8 +512,7 @@ public class MessageParser extends AbstractParser
                 queryId == null
                         ? null
                         : mXmppConnectionService.getMessageArchiveService().findQuery(queryId);
-        final boolean offlineMessagesRetrieved =
-                account.getXmppConnection().isOfflineMessagesRetrieved();
+        final boolean offlineMessagesRetrieved = connection.isOfflineMessagesRetrieved();
         if (query != null && query.validFrom(original.getFrom())) {
             final var f = getForwardedMessagePacket(original, "result", query.version.namespace);
             if (f == null) {
@@ -692,7 +693,7 @@ public class MessageParser extends AbstractParser
             final boolean conversationIsProbablyMuc =
                     isTypeGroupChat
                             || mucUserElement != null
-                            || account.getXmppConnection()
+                            || connection
                                     .getMucServersWithholdAccount()
                                     .contains(counterpart.getDomain().toString());
             final Conversation conversation =
@@ -1445,6 +1446,7 @@ public class MessageParser extends AbstractParser
             final im.conversations.android.xmpp.model.stanza.Message packet,
             final MessageArchiveService.Query query,
             final Jid from) {
+        final var account = this.connection.getAccount();
         final var id = received.getId();
         if (packet.fromAccount(account)) {
             if (query != null && id != null && packet.getTo() != null) {
@@ -1475,9 +1477,10 @@ public class MessageParser extends AbstractParser
             final Jid counterpart,
             final MessageArchiveService.Query query,
             final boolean isTypeGroupChat,
-            Conversation conversation,
-            Element mucUserElement,
-            Jid from) {
+            final Conversation conversation,
+            final Element mucUserElement,
+            final Jid from) {
+        final var account = getAccount();
         final var id = displayed.getId();
         // TODO we donβt even use 'sender' any more. Remove this!
         final Jid sender = Jid.Invalid.getNullForInvalid(displayed.getAttributeAsJid("sender"));
@@ -1565,6 +1568,7 @@ public class MessageParser extends AbstractParser
             final Jid counterpart,
             final Jid mucTrueCounterPart,
             final im.conversations.android.xmpp.model.stanza.Message packet) {
+        final var account = getAccount();
         final String reactingTo = reactions.getId();
         if (conversation != null && reactingTo != null) {
             if (isTypeGroupChat && conversation.getMode() == Conversational.MODE_MULTI) {
  
  
  
    
    @@ -22,6 +22,7 @@ import eu.siacs.conversations.utils.XmppUri;
 import eu.siacs.conversations.xml.Element;
 import eu.siacs.conversations.xml.Namespace;
 import eu.siacs.conversations.xmpp.Jid;
+import eu.siacs.conversations.xmpp.XmppConnection;
 import eu.siacs.conversations.xmpp.manager.DiscoManager;
 import eu.siacs.conversations.xmpp.pep.Avatar;
 import im.conversations.android.xmpp.Entity;
@@ -35,12 +36,13 @@ import org.openintents.openpgp.util.OpenPgpUtils;
 public class PresenceParser extends AbstractParser
         implements Consumer<im.conversations.android.xmpp.model.stanza.Presence> {
 
-    public PresenceParser(final XmppConnectionService service, final Account account) {
-        super(service, account);
+    public PresenceParser(final XmppConnectionService service, final XmppConnection connection) {
+        super(service, connection);
     }
 
     public void parseConferencePresence(
-            final im.conversations.android.xmpp.model.stanza.Presence packet, Account account) {
+            final im.conversations.android.xmpp.model.stanza.Presence packet) {
+        final var account = getAccount();
         final Conversation conversation =
                 packet.getFrom() == null
                         ? null
@@ -325,8 +327,8 @@ public class PresenceParser extends AbstractParser
     }
 
     private void parseContactPresence(
-            final im.conversations.android.xmpp.model.stanza.Presence packet,
-            final Account account) {
+            final im.conversations.android.xmpp.model.stanza.Presence packet) {
+        final var account = getAccount();
         final PresenceGenerator mPresenceGenerator = mXmppConnectionService.getPresenceGenerator();
         final Jid from = packet.getFrom();
         if (from == null || from.equals(account.getJid())) {
@@ -372,8 +374,7 @@ public class PresenceParser extends AbstractParser
             final var connection = account.getXmppConnection();
             if (nodeHash != null && connection != null) {
                 final var discoFuture =
-                        connection
-                                .getManager(DiscoManager.class)
+                        this.getManager(DiscoManager.class)
                                 .infoOrCache(Entity.presence(from), nodeHash.node, nodeHash.hash);
 
                 logDiscoFailure(from, discoFuture);
@@ -486,14 +487,14 @@ public class PresenceParser extends AbstractParser
     @Override
     public void accept(final im.conversations.android.xmpp.model.stanza.Presence packet) {
         if (packet.hasChild("x", Namespace.MUC_USER)) {
-            this.parseConferencePresence(packet, account);
+            this.parseConferencePresence(packet);
         } else if (packet.hasChild("x", "http://jabber.org/protocol/muc")) {
-            this.parseConferencePresence(packet, account);
+            this.parseConferencePresence(packet);
         } else if ("error".equals(packet.getAttribute("type"))
-                && mXmppConnectionService.isMuc(account, packet.getFrom())) {
-            this.parseConferencePresence(packet, account);
+                && mXmppConnectionService.isMuc(getAccount(), packet.getFrom())) {
+            this.parseConferencePresence(packet);
         } else {
-            this.parseContactPresence(packet, account);
+            this.parseContactPresence(packet);
         }
     }
 }
  
  
  
    
    @@ -0,0 +1,23 @@
+package eu.siacs.conversations.xmpp;
+
+import android.content.Context;
+import com.google.common.collect.ClassToInstanceMap;
+import com.google.common.collect.ImmutableClassToInstanceMap;
+import eu.siacs.conversations.xmpp.manager.AbstractManager;
+import eu.siacs.conversations.xmpp.manager.DiscoManager;
+import eu.siacs.conversations.xmpp.manager.PresenceManager;
+
+public class Managers {
+
+    private Managers() {
+        throw new AssertionError("Do not instantiate me");
+    }
+
+    public static ClassToInstanceMap<AbstractManager> get(
+            final Context context, final XmppConnection connection) {
+        return new ImmutableClassToInstanceMap.Builder<AbstractManager>()
+                .put(DiscoManager.class, new DiscoManager(context, connection))
+                .put(PresenceManager.class, new PresenceManager(context, connection))
+                .build();
+    }
+}
  
  
  
    
    @@ -20,7 +20,6 @@ import com.google.common.base.Preconditions;
 import com.google.common.base.Strings;
 import com.google.common.base.Throwables;
 import com.google.common.collect.ClassToInstanceMap;
-import com.google.common.collect.ImmutableClassToInstanceMap;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
 import com.google.common.primitives.Ints;
@@ -74,7 +73,6 @@ import eu.siacs.conversations.xmpp.forms.Data;
 import eu.siacs.conversations.xmpp.jingle.OnJinglePacketReceived;
 import eu.siacs.conversations.xmpp.manager.AbstractManager;
 import eu.siacs.conversations.xmpp.manager.DiscoManager;
-import eu.siacs.conversations.xmpp.manager.PresenceManager;
 import im.conversations.android.xmpp.Entity;
 import im.conversations.android.xmpp.model.AuthenticationFailure;
 import im.conversations.android.xmpp.model.AuthenticationRequest;
@@ -217,19 +215,11 @@ public class XmppConnection implements Runnable {
         this.account = account;
         this.mXmppConnectionService = service;
         this.appSettings = mXmppConnectionService.getAppSettings();
-        this.presenceListener = new PresenceParser(service, account);
-        this.unregisteredIqListener = new IqParser(service, account);
-        this.messageListener = new MessageParser(service, account);
-        this.bindListener = new BindProcessor(service, account);
-        this.managers =
-                new ImmutableClassToInstanceMap.Builder<AbstractManager>()
-                        .put(
-                                DiscoManager.class,
-                                new DiscoManager(service.getApplicationContext(), this))
-                        .put(
-                                PresenceManager.class,
-                                new PresenceManager(service.getApplicationContext(), this))
-                        .build();
+        this.presenceListener = new PresenceParser(service, this);
+        this.unregisteredIqListener = new IqParser(service, this);
+        this.messageListener = new MessageParser(service, this);
+        this.bindListener = new BindProcessor(service, this);
+        this.managers = Managers.get(service.getApplicationContext(), this);
     }
 
     private static void fixResource(final Context context, final Account account) {
  
  
  
    
    @@ -30,6 +30,7 @@ import im.conversations.android.xmpp.model.disco.items.ItemsQuery;
 import im.conversations.android.xmpp.model.error.Condition;
 import im.conversations.android.xmpp.model.error.Error;
 import im.conversations.android.xmpp.model.stanza.Iq;
+import im.conversations.android.xmpp.model.version.Version;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
@@ -354,6 +355,19 @@ public class DiscoManager extends AbstractManager {
         }
     }
 
+    public void handleVersionRequest(final Iq request) {
+        final var version = new Version();
+        version.setSoftwareName(context.getString(R.string.app_name));
+        version.setVersion(getIdentityVersion());
+        if ("chromium".equals(android.os.Build.BRAND)) {
+            version.setOs("Chrome OS");
+        } else {
+            version.setOs("Android");
+        }
+        Log.d(Config.LOGTAG, "responding to version request from " + request.getFrom());
+        connection.sendResultFor(request, version);
+    }
+
     public void handleInfoQuery(final Iq request) {
         final var infoQueryRequest = request.getExtension(InfoQuery.class);
         final var nodeRequest = infoQueryRequest.getNode();
  
  
  
    
    @@ -9,19 +9,18 @@ import eu.siacs.conversations.services.XmppConnectionService;
 import eu.siacs.conversations.xmpp.XmppConnection;
 import im.conversations.android.xmpp.model.stanza.Iq;
 
-public class BindProcessor implements Runnable {
+public class BindProcessor extends XmppConnection.Delegate implements Runnable {
 
     private final XmppConnectionService service;
-    private final Account account;
 
-    public BindProcessor(XmppConnectionService service, Account account) {
+    public BindProcessor(final XmppConnectionService service, final XmppConnection connection) {
+        super(service.getApplicationContext(), connection);
         this.service = service;
-        this.account = account;
     }
 
     @Override
     public void run() {
-        final XmppConnection connection = account.getXmppConnection();
+        final var account = connection.getAccount();
         final var features = connection.getFeatures();
         service.cancelAvatarFetches(account);
         final boolean loggedInSuccessfully =