introduce ping and carbons managers

Daniel Gultsch created

Change summary

src/main/java/eu/siacs/conversations/services/XmppConnectionService.java |  1 
src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java         |  6 
src/main/java/eu/siacs/conversations/xmpp/Managers.java                  |  4 
src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java            | 77 
src/main/java/eu/siacs/conversations/xmpp/manager/CarbonsManager.java    | 61 
src/main/java/eu/siacs/conversations/xmpp/manager/DiscoManager.java      |  5 
src/main/java/eu/siacs/conversations/xmpp/manager/PingManager.java       | 47 
src/main/java/im/conversations/android/xmpp/model/stanza/Iq.java         | 10 
8 files changed, 157 insertions(+), 54 deletions(-)

Detailed changes

src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java 🔗

@@ -78,6 +78,7 @@ import eu.siacs.conversations.xmpp.OnUpdateBlocklist;
 import eu.siacs.conversations.xmpp.XmppConnection;
 import eu.siacs.conversations.xmpp.XmppConnection.Features;
 import eu.siacs.conversations.xmpp.forms.Data;
+import eu.siacs.conversations.xmpp.manager.CarbonsManager;
 import eu.siacs.conversations.xmpp.pep.Avatar;
 import im.conversations.android.xmpp.model.stanza.Presence;
 import java.util.Arrays;
@@ -1226,7 +1227,8 @@ public class EditAccountActivity extends OmemoActivity
             this.binding.accountRegisterNew.setVisibility(View.GONE);
         }
         if (this.mAccount.isOnlineAndConnected() && !this.mFetchingAvatar) {
-            final Features features = this.mAccount.getXmppConnection().getFeatures();
+            final var connection = this.mAccount.getXmppConnection();
+            final Features features = connection.getFeatures();
             this.binding.stats.setVisibility(View.VISIBLE);
             boolean showBatteryWarning = isOptimizingBattery();
             boolean showDataSaverWarning = isAffectedByDataSaver();
@@ -1239,7 +1241,7 @@ public class EditAccountActivity extends OmemoActivity
             } else {
                 this.binding.serverInfoRosterVersion.setText(R.string.server_info_unavailable);
             }
-            if (features.carbons()) {
+            if (connection.getManager(CarbonsManager.class).isEnabled()) {
                 this.binding.serverInfoCarbons.setText(R.string.server_info_available);
             } else {
                 this.binding.serverInfoCarbons.setText(R.string.server_info_unavailable);

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

@@ -4,7 +4,9 @@ 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.CarbonsManager;
 import eu.siacs.conversations.xmpp.manager.DiscoManager;
+import eu.siacs.conversations.xmpp.manager.PingManager;
 import eu.siacs.conversations.xmpp.manager.PresenceManager;
 
 public class Managers {
@@ -16,7 +18,9 @@ public class Managers {
     public static ClassToInstanceMap<AbstractManager> get(
             final Context context, final XmppConnection connection) {
         return new ImmutableClassToInstanceMap.Builder<AbstractManager>()
+                .put(CarbonsManager.class, new CarbonsManager(context, connection))
                 .put(DiscoManager.class, new DiscoManager(context, connection))
+                .put(PingManager.class, new PingManager(context, connection))
                 .put(PresenceManager.class, new PresenceManager(context, connection))
                 .build();
     }

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

@@ -72,7 +72,9 @@ import eu.siacs.conversations.xmpp.bind.Bind2;
 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.CarbonsManager;
 import eu.siacs.conversations.xmpp.manager.DiscoManager;
+import eu.siacs.conversations.xmpp.manager.PingManager;
 import im.conversations.android.xmpp.Entity;
 import im.conversations.android.xmpp.model.AuthenticationFailure;
 import im.conversations.android.xmpp.model.AuthenticationRequest;
@@ -895,7 +897,6 @@ public class XmppConnection implements Runnable {
                             Config.LOGTAG,
                             account.getJid().asBareJid()
                                     + ": successfully enabled carbons (via Bind 2.0)");
-                    features.carbonsEnabled = true;
                 } else if (currentLoginInfo.inlineBindFeatures != null
                         && currentLoginInfo.inlineBindFeatures.contains(Namespace.CARBONS)) {
                     negotiatedCarbons = true;
@@ -903,7 +904,6 @@ public class XmppConnection implements Runnable {
                             Config.LOGTAG,
                             account.getJid().asBareJid()
                                     + ": successfully enabled carbons (via Bind 2.0/implicit)");
-                    features.carbonsEnabled = true;
                 } else {
                     negotiatedCarbons = false;
                 }
@@ -2175,7 +2175,7 @@ public class XmppConnection implements Runnable {
 
     private void sendPostBindInitialization(
             final boolean waitForDisco, final boolean carbonsEnabled) {
-        features.carbonsEnabled = carbonsEnabled;
+        getManager(CarbonsManager.class).setEnabledOnBind(carbonsEnabled);
         features.blockListRequested = false;
         getManager(DiscoManager.class).clear();
         Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": starting service discovery");
@@ -2365,35 +2365,15 @@ public class XmppConnection implements Runnable {
                 advancedStreamFeaturesLoadedListeners) {
             listener.onAdvancedStreamFeaturesAvailable(account);
         }
-        if (getFeatures().carbons() && !features.carbonsEnabled) {
-            sendEnableCarbons();
+        final var carbonsManager = getManager(CarbonsManager.class);
+        if (carbonsManager.hasFeature() && !carbonsManager.isEnabled()) {
+            carbonsManager.enable();
         }
         if (getFeatures().commands()) {
             discoverCommands();
         }
     }
 
-    private void sendEnableCarbons() {
-        final Iq iq = new Iq(Iq.Type.SET);
-        iq.addChild("enable", Namespace.CARBONS);
-        this.sendIqPacket(
-                iq,
-                (packet) -> {
-                    if (packet.getType() == Iq.Type.RESULT) {
-                        Log.d(
-                                Config.LOGTAG,
-                                account.getJid().asBareJid() + ": successfully enabled carbons");
-                        features.carbonsEnabled = true;
-                    } else {
-                        Log.d(
-                                Config.LOGTAG,
-                                account.getJid().asBareJid()
-                                        + ": could not enable carbons "
-                                        + packet);
-                    }
-                });
-    }
-
     private void processStreamError(final StreamError streamError) throws IOException {
         final var loginInfo = this.loginInfo;
         final var isSecureLoggedIn = isSecure() && LoginInfo.isSuccess(loginInfo);
@@ -2525,6 +2505,10 @@ public class XmppConnection implements Runnable {
         return String.format("%s.%s", BuildConfig.APP_NAME, CryptoHelper.random(3));
     }
 
+    public void sendRequestStanza() {
+        this.sendPacket(new Request());
+    }
+
     public ListenableFuture<Iq> sendIqPacket(final Iq request) {
         final SettableFuture<Iq> settable = SettableFuture.create();
         this.sendIqPacket(
@@ -2654,12 +2638,7 @@ public class XmppConnection implements Runnable {
     }
 
     public void sendPing() {
-        if (!r()) {
-            final Iq iq = new Iq(Iq.Type.GET);
-            iq.setFrom(account.getJid());
-            iq.addChild("ping", Namespace.PING);
-            this.sendIqPacket(iq, null);
-        }
+        this.getManager(PingManager.class).ping();
         this.lastPingSent = SystemClock.elapsedRealtime();
     }
 
@@ -2873,17 +2852,16 @@ public class XmppConnection implements Runnable {
 
     public void trackOfflineMessageRetrieval(boolean trackOfflineMessageRetrieval) {
         if (trackOfflineMessageRetrieval) {
-            final Iq iqPing = new Iq(Iq.Type.GET);
-            iqPing.addChild("ping", Namespace.PING);
-            this.sendIqPacket(
-                    iqPing,
-                    (response) -> {
-                        Log.d(
-                                Config.LOGTAG,
-                                account.getJid().asBareJid()
-                                        + ": got ping response after sending initial presence");
-                        XmppConnection.this.offlineMessagesRetrieved = true;
-                    });
+            getManager(PingManager.class)
+                    .ping(
+                            () -> {
+                                Log.d(
+                                        Config.LOGTAG,
+                                        account.getJid().asBareJid()
+                                                + ": got ping response after sending initial"
+                                                + " presence");
+                                this.offlineMessagesRetrieved = true;
+                            });
         } else {
             this.offlineMessagesRetrieved = true;
         }
@@ -2927,6 +2905,10 @@ public class XmppConnection implements Runnable {
         return this.account;
     }
 
+    public Features getStreamFeatures() {
+        return this.features;
+    }
+
     private class MyKeyManager implements X509KeyManager {
         @Override
         public String chooseClientAlias(String[] strings, Principal[] principals, Socket socket) {
@@ -3074,8 +3056,9 @@ public class XmppConnection implements Runnable {
     }
 
     public class Features {
-        XmppConnection connection;
-        private boolean carbonsEnabled = false;
+        private final XmppConnection connection;
+
+        // TODO move these three into their respective managers or into XmppConnection
         private boolean encryptionEnabled = false;
         private boolean blockListRequested = false;
 
@@ -3088,10 +3071,6 @@ public class XmppConnection implements Runnable {
             return infoQuery != null && infoQuery.getFeatureStrings().contains(feature);
         }
 
-        public boolean carbons() {
-            return hasDiscoFeature(account.getDomain(), Namespace.CARBONS);
-        }
-
         public boolean commands() {
             return hasDiscoFeature(account.getDomain(), Namespace.COMMANDS);
         }

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

@@ -0,0 +1,61 @@
+package eu.siacs.conversations.xmpp.manager;
+
+import android.content.Context;
+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.xml.Namespace;
+import eu.siacs.conversations.xmpp.XmppConnection;
+import im.conversations.android.xmpp.model.carbons.Enable;
+import im.conversations.android.xmpp.model.stanza.Iq;
+
+public class CarbonsManager extends AbstractManager {
+
+    private boolean enabled = false;
+
+    public CarbonsManager(final Context context, final XmppConnection connection) {
+        super(context, connection);
+    }
+
+    public void setEnabledOnBind(final boolean enabledOnBind) {
+        this.enabled = enabledOnBind;
+    }
+
+    public void enable() {
+        final var request = new Iq(Iq.Type.SET);
+        request.addExtension(new Enable());
+        final var future = this.connection.sendIqPacket(request);
+        Futures.addCallback(
+                future,
+                new FutureCallback<>() {
+                    @Override
+                    public void onSuccess(final Iq result) {
+                        CarbonsManager.this.enabled = true;
+                        Log.d(
+                                Config.LOGTAG,
+                                getAccount().getJid().asBareJid()
+                                        + ": successfully enabled carbons");
+                    }
+
+                    @Override
+                    public void onFailure(@NonNull final Throwable throwable) {
+                        Log.d(
+                                Config.LOGTAG,
+                                getAccount().getJid().asBareJid() + ": could not enable carbons",
+                                throwable);
+                    }
+                },
+                MoreExecutors.directExecutor());
+    }
+
+    public boolean isEnabled() {
+        return this.enabled;
+    }
+
+    public boolean hasFeature() {
+        return getManager(DiscoManager.class).hasServerFeature(Namespace.CARBONS);
+    }
+}

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

@@ -422,6 +422,11 @@ public class DiscoManager extends AbstractManager {
         return builder.buildKeepingLast();
     }
 
+    public boolean hasServerFeature(final String feature) {
+        final var infoQuery = this.get(getAccount().getDomain());
+        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/PingManager.java 🔗

@@ -0,0 +1,47 @@
+package eu.siacs.conversations.xmpp.manager;
+
+import android.content.Context;
+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.xmpp.XmppConnection;
+import im.conversations.android.xmpp.model.ping.Ping;
+import im.conversations.android.xmpp.model.stanza.Iq;
+import java.util.concurrent.TimeoutException;
+
+public class PingManager extends AbstractManager {
+
+    public PingManager(final Context context, final XmppConnection connection) {
+        super(context, connection);
+    }
+
+    public void ping() {
+        if (connection.getStreamFeatures().sm()) {
+            this.connection.sendRequestStanza();
+        } else {
+            this.connection.sendIqPacket(new Iq(Iq.Type.GET, new Ping()));
+        }
+    }
+
+    public void ping(final Runnable runnable) {
+        final var pingFuture = this.connection.sendIqPacket(new Iq(Iq.Type.GET, new Ping()));
+        Futures.addCallback(
+                pingFuture,
+                new FutureCallback<Iq>() {
+                    @Override
+                    public void onSuccess(Iq result) {
+                        runnable.run();
+                    }
+
+                    @Override
+                    public void onFailure(final @NonNull Throwable t) {
+                        if (t instanceof TimeoutException) {
+                            return;
+                        }
+                        runnable.run();
+                    }
+                },
+                MoreExecutors.directExecutor());
+    }
+}

src/main/java/im/conversations/android/xmpp/model/stanza/Iq.java 🔗

@@ -1,12 +1,11 @@
 package im.conversations.android.xmpp.model.stanza;
 
 import com.google.common.base.Strings;
-
 import eu.siacs.conversations.xml.Element;
-
 import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
 import im.conversations.android.xmpp.model.error.Error;
-
+import java.util.Arrays;
 import java.util.Locale;
 
 @XmlElement
@@ -23,6 +22,11 @@ public class Iq extends Stanza {
         this.setAttribute("type", type.toString().toLowerCase(Locale.ROOT));
     }
 
+    public Iq(final Type type, final Extension... extensions) {
+        this(type);
+        this.addExtensions(Arrays.asList(extensions));
+    }
+
     // TODO get rid of timeout
     public enum Type {
         SET,