From 3138af664aa31b6ce02c2589210062992cd487f5 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Thu, 9 Jan 2025 12:36:27 +0100 Subject: [PATCH] introduce connection timeout as its own state --- .../siacs/conversations/entities/Account.java | 3 + .../services/CallIntegration.java | 4 +- .../services/XmppConnectionService.java | 57 +++++++------------ .../conversations/xmpp/XmppConnection.java | 51 ++++++++++------- src/main/res/values/strings.xml | 1 + 5 files changed, 56 insertions(+), 60 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/entities/Account.java b/src/main/java/eu/siacs/conversations/entities/Account.java index b667428e0b72657a696eacb9d4a4edf049ed2f9f..ce13ba9f3594d42242854083496af1d0fb2fab94 100644 --- a/src/main/java/eu/siacs/conversations/entities/Account.java +++ b/src/main/java/eu/siacs/conversations/entities/Account.java @@ -779,6 +779,7 @@ public class Account extends AbstractEntity implements AvatarService.Avatarable CONNECTING(false), ONLINE(false), NO_INTERNET(false), + CONNECTION_TIMEOUT, UNAUTHORIZED, TEMPORARY_AUTH_FAILURE, SERVER_NOT_FOUND, @@ -848,6 +849,8 @@ public class Account extends AbstractEntity implements AvatarService.Avatarable return R.string.account_status_not_found; case NO_INTERNET: return R.string.account_status_no_internet; + case CONNECTION_TIMEOUT: + return R.string.account_status_connection_timeout; case REGISTRATION_FAILED: return R.string.account_status_regis_fail; case REGISTRATION_WEB: diff --git a/src/main/java/eu/siacs/conversations/services/CallIntegration.java b/src/main/java/eu/siacs/conversations/services/CallIntegration.java index 505b0decc99f306687b098c558a52d039e9e5833..f4d4fae35c70d0e3d9dd861f2883c76df56fc315 100644 --- a/src/main/java/eu/siacs/conversations/services/CallIntegration.java +++ b/src/main/java/eu/siacs/conversations/services/CallIntegration.java @@ -2,7 +2,6 @@ package eu.siacs.conversations.services; import android.content.Context; import android.content.pm.PackageManager; -import android.media.AudioAttributes; import android.media.AudioManager; import android.media.ToneGenerator; import android.net.Uri; @@ -20,7 +19,6 @@ import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import eu.siacs.conversations.AppSettings; import eu.siacs.conversations.Config; -import eu.siacs.conversations.R; import eu.siacs.conversations.ui.util.MainThreadExecutor; import eu.siacs.conversations.xmpp.Jid; import eu.siacs.conversations.xmpp.jingle.JingleConnectionManager; @@ -374,7 +372,7 @@ public class CallIntegration extends Connection { } } if (state == STATE_ACTIVE) { - startTone(DEFAULT_TONE_VOLUME, ToneGenerator.TONE_CDMA_ANSWER, 100 ); + startTone(DEFAULT_TONE_VOLUME, ToneGenerator.TONE_CDMA_ANSWER, 100); } else if (state == STATE_DISCONNECTED) { final var audioManager = this.appRTCAudioManager; if (audioManager != null) { diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index 2762f29e0aa0f03156538108d075ed4a1971d9d1..41f7d18ec23f73305ab5a9fd144eae7662fa6c30 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -5,7 +5,6 @@ import static eu.siacs.conversations.utils.Random.SECURE_RANDOM; import android.Manifest; import android.annotation.SuppressLint; -import android.annotation.TargetApi; import android.app.AlarmManager; import android.app.KeyguardManager; import android.app.Notification; @@ -1036,6 +1035,7 @@ public class XmppConnectionService extends Service { if (!account.getStatus().isAttemptReconnect()) { return false; } + final var requestCode = account.getUuid().hashCode(); if (!hasInternetConnection()) { account.setStatus(Account.State.NO_INTERNET); statusListener.onStatusChanged(account); @@ -1065,8 +1065,7 @@ public class XmppConnectionService extends Service { Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": ping timeout"); this.reconnectAccount(account, true, interactive); } else { - int secs = (int) (pingTimeoutIn / 1000); - this.scheduleWakeUpCall(secs, account.getUuid().hashCode()); + this.scheduleWakeUpCall(pingTimeoutIn, requestCode); } } else { pingCandidates.add(account); @@ -1081,8 +1080,7 @@ public class XmppConnectionService extends Service { } else if (msToNextPing <= 0) { return true; } else { - this.scheduleWakeUpCall( - (int) (msToNextPing / 1000), account.getUuid().hashCode()); + this.scheduleWakeUpCall(msToNextPing, requestCode); if (mLowPingTimeoutMode.remove(account.getJid().asBareJid())) { Log.d( Config.LOGTAG, @@ -1095,32 +1093,18 @@ public class XmppConnectionService extends Service { } else if (account.getStatus() == Account.State.OFFLINE) { reconnectAccount(account, true, interactive); } else if (account.getStatus() == Account.State.CONNECTING) { - long secondsSinceLastConnect = - (SystemClock.elapsedRealtime() - - account.getXmppConnection().getLastConnect()) - / 1000; - long secondsSinceLastDisco = - (SystemClock.elapsedRealtime() - - account.getXmppConnection().getLastDiscoStarted()) - / 1000; - long discoTimeout = Config.CONNECT_DISCO_TIMEOUT - secondsSinceLastDisco; - long timeout = Config.CONNECT_TIMEOUT - secondsSinceLastConnect; - if (timeout < 0) { - Log.d( - Config.LOGTAG, - account.getJid() - + ": time out during connect reconnecting (secondsSinceLast=" - + secondsSinceLastConnect - + ")"); - account.getXmppConnection().resetAttemptCount(false); - reconnectAccount(account, true, interactive); + final var connection = account.getXmppConnection(); + final var connectionDuration = connection.getConnectionDuration(); + final var discoDuration = connection.getDiscoDuration(); + final var connectionTimeout = Config.CONNECT_TIMEOUT * 1000L - connectionDuration; + final var discoTimeout = Config.CONNECT_DISCO_TIMEOUT * 1000L - discoDuration; + if (connectionTimeout < 0) { + connection.triggerConnectionTimeout(); } else if (discoTimeout < 0) { - account.getXmppConnection().sendDiscoTimeout(); - scheduleWakeUpCall( - (int) Math.min(timeout, discoTimeout), account.getUuid().hashCode()); + connection.sendDiscoTimeout(); + scheduleWakeUpCall(discoTimeout, requestCode); } else { - scheduleWakeUpCall( - (int) Math.min(timeout, discoTimeout), account.getUuid().hashCode()); + scheduleWakeUpCall(Math.min(connectionTimeout, discoTimeout), requestCode); } } else { final boolean aggressive = @@ -1768,12 +1752,12 @@ public class XmppConnectionService extends Service { } public void scheduleWakeUpCall(final int seconds, final int requestCode) { - final long timeToWake = - SystemClock.elapsedRealtime() + (seconds < 0 ? 1 : seconds + 1) * 1000L; - final AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE); - if (alarmManager == null) { - return; - } + scheduleWakeUpCall((long) (seconds < 0 ? 1 : seconds + 1), requestCode); + } + + private void scheduleWakeUpCall(final long milliSeconds, final int requestCode) { + final var timeToWake = SystemClock.elapsedRealtime() + milliSeconds; + final var alarmManager = getSystemService(AlarmManager.class); final Intent intent = new Intent(this, SystemEventReceiver.class); intent.setAction(ACTION_PING); try { @@ -1781,12 +1765,11 @@ public class XmppConnectionService extends Service { PendingIntent.getBroadcast( this, requestCode, intent, PendingIntent.FLAG_IMMUTABLE); alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, timeToWake, pendingIntent); - } catch (RuntimeException e) { + } catch (final RuntimeException e) { Log.e(Config.LOGTAG, "unable to schedule alarm for ping", e); } } - @TargetApi(Build.VERSION_CODES.M) private void scheduleNextIdlePing() { final long timeToWake = SystemClock.elapsedRealtime() + (Config.IDLE_PING_INTERVAL * 1000); final AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE); diff --git a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java index ba800163258ef9137b922cf788f9fcbaba4ddfe2..9845f4149bf384a45772d053a4034f21ad53890a 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java @@ -20,6 +20,7 @@ import com.google.common.base.Preconditions; import com.google.common.base.Strings; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; +import com.google.common.primitives.Ints; import eu.siacs.conversations.AppSettings; import eu.siacs.conversations.BuildConfig; import eu.siacs.conversations.Config; @@ -166,7 +167,7 @@ public class XmppConnection implements Runnable { private int stanzasSentBeforeAuthentication; private long lastPacketReceived = 0; private long lastPingSent = 0; - private long lastConnect = 0; + private long lastConnectionStarted = 0; private long lastSessionStarted = 0; private long lastDiscoStarted = 0; private boolean isMamPreferenceAlways = false; @@ -264,7 +265,7 @@ public class XmppConnection implements Runnable { } public void prepareNewConnection() { - this.lastConnect = SystemClock.elapsedRealtime(); + this.lastConnectionStarted = SystemClock.elapsedRealtime(); this.lastPingSent = SystemClock.elapsedRealtime(); this.lastDiscoStarted = Long.MAX_VALUE; this.mWaitingForSmCatchup.set(false); @@ -526,15 +527,17 @@ public class XmppConnection implements Runnable { if (Thread.currentThread().isInterrupted()) { throw new InterruptedException(); } + // this means we have at least found a socket to connect to. give the connection another 90s + this.lastConnectionStarted = SystemClock.elapsedRealtime(); this.socket = socket; - tagReader = new XmlReader(); + this.tagReader = new XmlReader(); if (tagWriter != null) { tagWriter.forceClose(); } - tagWriter = new TagWriter(); - tagWriter.setOutputStream(socket.getOutputStream()); - tagReader.setInputStream(socket.getInputStream()); - tagWriter.beginDocument(); + this.tagWriter = new TagWriter(); + this.tagWriter.setOutputStream(socket.getOutputStream()); + this.tagReader.setInputStream(socket.getInputStream()); + this.tagWriter.beginDocument(); final boolean quickStart; if (socket instanceof SSLSocket sslSocket) { SSLSockets.log(account, sslSocket); @@ -2372,7 +2375,7 @@ public class XmppConnection implements Runnable { } else if (streamError.hasChild("host-unknown")) { throw new StateChangingException(Account.State.HOST_UNKNOWN); } else if (streamError.hasChild("policy-violation")) { - this.lastConnect = SystemClock.elapsedRealtime(); + this.lastConnectionStarted = SystemClock.elapsedRealtime(); final String text = streamError.findChildContent("text"); Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": policy violation. " + text); if (isSecureLoggedIn) { @@ -2712,8 +2715,7 @@ public class XmppConnection implements Runnable { } public String getMucServer() { - List servers = getMucServers(); - return servers.size() > 0 ? servers.get(0) : null; + return Iterables.getFirst(getMucServers(), null); } public int getTimeToNextAttempt(final boolean aggressive) { @@ -2725,9 +2727,8 @@ public class XmppConnection implements Runnable { account.getLastErrorStatus() == Account.State.POLICY_VIOLATION ? 3 : 0; interval = Math.min((int) (25 * Math.pow(1.3, (additionalTime + attempt))), 300); } - final int secondsSinceLast = - (int) ((SystemClock.elapsedRealtime() - this.lastConnect) / 1000); - return interval - secondsSinceLast; + final var connectionDuration = Ints.saturatedCast(getConnectionDuration() / 1000); + return interval - connectionDuration; } public int getAttempt() { @@ -2743,16 +2744,16 @@ public class XmppConnection implements Runnable { return System.currentTimeMillis() - diff; } - public long getLastConnect() { - return this.lastConnect; + public long getConnectionDuration() { + return SystemClock.elapsedRealtime() - this.lastConnectionStarted; } - public long getLastPingSent() { - return this.lastPingSent; + public long getDiscoDuration() { + return SystemClock.elapsedRealtime() - this.lastDiscoStarted; } - public long getLastDiscoStarted() { - return this.lastDiscoStarted; + public long getLastPingSent() { + return this.lastPingSent; } public long getLastPacketReceived() { @@ -2770,7 +2771,7 @@ public class XmppConnection implements Runnable { public void resetAttemptCount(boolean resetConnectTime) { this.attempt = 0; if (resetConnectTime) { - this.lastConnect = 0; + this.lastConnectionStarted = 0; } } @@ -2818,6 +2819,16 @@ public class XmppConnection implements Runnable { sendIqPacket(iqPacket, unregisteredIqListener); } + public void triggerConnectionTimeout() { + final var duration = getConnectionDuration(); + Log.d( + Config.LOGTAG, + account.getJid().asBareJid() + ": connection timeout after " + duration + "ms"); + this.changeStatus(Account.State.CONNECTION_TIMEOUT); + this.interrupt(); + this.forceCloseSocket(); + } + private class MyKeyManager implements X509KeyManager { @Override public String chooseClientAlias(String[] strings, Principal[] principals, Socket socket) { diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index f38d7cae16c34bad868b4acc2c94bfca44c19ee8..1045f797f0eb0a3714f1738dea97892798b2f072 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -158,6 +158,7 @@ Unauthorized Server not found No connectivity + Connection timeout Registration failed Username already in use Registration completed