From 12df7f1a17c5cfa0ea1607dde6705b0773d6ffd6 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sun, 11 May 2025 10:30:58 +0200 Subject: [PATCH] introduce ping and carbons managers --- .../services/XmppConnectionService.java | 1 + .../conversations/ui/EditAccountActivity.java | 6 +- .../eu/siacs/conversations/xmpp/Managers.java | 4 + .../conversations/xmpp/XmppConnection.java | 77 +++++++------------ .../xmpp/manager/CarbonsManager.java | 61 +++++++++++++++ .../xmpp/manager/DiscoManager.java | 5 ++ .../xmpp/manager/PingManager.java | 47 +++++++++++ .../android/xmpp/model/stanza/Iq.java | 10 ++- 8 files changed, 157 insertions(+), 54 deletions(-) create mode 100644 src/main/java/eu/siacs/conversations/xmpp/manager/CarbonsManager.java create mode 100644 src/main/java/eu/siacs/conversations/xmpp/manager/PingManager.java diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index dec7cc36e73c2d67f93670067b91955f360441d6..b090d622fd97e79bb347da656e16903a5af80105 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -3610,6 +3610,7 @@ public class XmppConnectionService extends Service { return; } } + // TODO use PingManager final Jid self = conversation.getMucOptions().getSelf().getFullJid(); final Iq ping = new Iq(Iq.Type.GET); ping.setTo(self); diff --git a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java index 494e75dd257ed6e04d955b20044067c0c02b280c..c531fb7b8c87d6a7c94ff5994f94b65166c35d4d 100644 --- a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java +++ b/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); diff --git a/src/main/java/eu/siacs/conversations/xmpp/Managers.java b/src/main/java/eu/siacs/conversations/xmpp/Managers.java index 306d6df593460e58abcd3e924cc00b115be808af..84e0225e53773b73c08b43fa34e32cd88eebdffb 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/Managers.java +++ b/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 get( final Context context, final XmppConnection connection) { return new ImmutableClassToInstanceMap.Builder() + .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(); } diff --git a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java index 3aa8f6b197a8338d9b451b8ca030f3b09c41d55a..d9e79c35f96ac24b69a7acd35c22510a566ad048 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java +++ b/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 sendIqPacket(final Iq request) { final SettableFuture 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); } diff --git a/src/main/java/eu/siacs/conversations/xmpp/manager/CarbonsManager.java b/src/main/java/eu/siacs/conversations/xmpp/manager/CarbonsManager.java new file mode 100644 index 0000000000000000000000000000000000000000..280275cbc6eee51346966b508b8825b414717dde --- /dev/null +++ b/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); + } +} diff --git a/src/main/java/eu/siacs/conversations/xmpp/manager/DiscoManager.java b/src/main/java/eu/siacs/conversations/xmpp/manager/DiscoManager.java index bd52f3d28cfb9cc9d290f766fea56dd372e24b8f..3c7c6215e5d949b9a1e246f540f7c1ee6ce09fef 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/manager/DiscoManager.java +++ b/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); diff --git a/src/main/java/eu/siacs/conversations/xmpp/manager/PingManager.java b/src/main/java/eu/siacs/conversations/xmpp/manager/PingManager.java new file mode 100644 index 0000000000000000000000000000000000000000..f162a04d6797d7adc9d296d5b54877de31d0dd69 --- /dev/null +++ b/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() { + @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()); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/stanza/Iq.java b/src/main/java/im/conversations/android/xmpp/model/stanza/Iq.java index 9f94400c32692b9a734c5d503e98d3151738d532..545808e69e51c554265b4c2e1e34c03fcd97e4da 100644 --- a/src/main/java/im/conversations/android/xmpp/model/stanza/Iq.java +++ b/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,