@@ -1,7 +1,6 @@
package eu.siacs.conversations.services;
import static eu.siacs.conversations.utils.Compatibility.s;
-import static eu.siacs.conversations.utils.Random.SECURE_RANDOM;
import android.Manifest;
import android.annotation.SuppressLint;
@@ -125,7 +124,6 @@ import eu.siacs.conversations.xmpp.Jid;
import eu.siacs.conversations.xmpp.OnContactStatusChanged;
import eu.siacs.conversations.xmpp.OnKeyStatusUpdated;
import eu.siacs.conversations.xmpp.OnMessageAcknowledged;
-import eu.siacs.conversations.xmpp.OnStatusChanged;
import eu.siacs.conversations.xmpp.OnUpdateBlocklist;
import eu.siacs.conversations.xmpp.XmppConnection;
import eu.siacs.conversations.xmpp.chatstate.ChatState;
@@ -219,7 +217,7 @@ public class XmppConnectionService extends Service {
private static final Executor FILE_OBSERVER_EXECUTOR = Executors.newSingleThreadExecutor();
public static final Executor FILE_ATTACHMENT_EXECUTOR = Executors.newSingleThreadExecutor();
- private final ScheduledExecutorService internalPingExecutor =
+ public final ScheduledExecutorService internalPingExecutor =
Executors.newSingleThreadScheduledExecutor();
private static final SerialSingleThreadExecutor VIDEO_COMPRESSION_EXECUTOR =
new SerialSingleThreadExecutor("VideoCompression");
@@ -234,7 +232,7 @@ public class XmppConnectionService extends Service {
private final IqGenerator mIqGenerator = new IqGenerator(this);
private final Set<String> mInProgressAvatarFetches = new HashSet<>();
private final Set<String> mOmittedPepAvatarFetches = new HashSet<>();
- private final HashSet<Jid> mLowPingTimeoutMode = new HashSet<>();
+ public final HashSet<Jid> mLowPingTimeoutMode = new HashSet<>();
private final Consumer<Iq> mDefaultIqHandler =
(packet) -> {
if (packet.getType() != Iq.Type.RESULT) {
@@ -359,148 +357,6 @@ public class XmppConnectionService extends Service {
public final Set<String> FILENAMES_TO_IGNORE_DELETION = new HashSet<>();
private final AtomicLong mLastExpiryRun = new AtomicLong(0);
- private final OnStatusChanged statusListener =
- new OnStatusChanged() {
-
- @Override
- public void onStatusChanged(final Account account) {
- Log.d(Config.LOGTAG, "begin onStatusChanged()");
- final var status = account.getStatus();
- if (ServiceOutageStatus.isPossibleOutage(status)) {
- fetchServiceOutageStatus(account);
- }
- XmppConnection connection = account.getXmppConnection();
- updateAccountUi();
-
- if (account.getStatus() == Account.State.ONLINE
- || account.getStatus().isError()) {
- mQuickConversationsService.signalAccountStateChange();
- }
-
- if (account.getStatus() == Account.State.ONLINE) {
- synchronized (mLowPingTimeoutMode) {
- if (mLowPingTimeoutMode.remove(account.getJid().asBareJid())) {
- Log.d(
- Config.LOGTAG,
- account.getJid().asBareJid()
- + ": leaving low ping timeout mode");
- }
- }
- if (account.setShowErrorNotification(true)) {
- databaseBackend.updateAccount(account);
- }
- mMessageArchiveService.executePendingQueries(account);
- if (connection != null && connection.getFeatures().csi()) {
- if (checkListeners()) {
- Log.d(
- Config.LOGTAG,
- account.getJid().asBareJid() + " sending csi//inactive");
- connection.sendInactive();
- } else {
- Log.d(
- Config.LOGTAG,
- account.getJid().asBareJid() + " sending csi//active");
- connection.sendActive();
- }
- }
- List<Conversation> conversations = getConversations();
- for (Conversation conversation : conversations) {
- final boolean inProgressJoin;
- synchronized (account.inProgressConferenceJoins) {
- inProgressJoin =
- account.inProgressConferenceJoins.contains(conversation);
- }
- final boolean pendingJoin;
- synchronized (account.pendingConferenceJoins) {
- pendingJoin = account.pendingConferenceJoins.contains(conversation);
- }
- if (conversation.getAccount() == account
- && !pendingJoin
- && !inProgressJoin) {
- sendUnsentMessages(conversation);
- }
- }
- final List<Conversation> pendingLeaves;
- synchronized (account.pendingConferenceLeaves) {
- pendingLeaves = new ArrayList<>(account.pendingConferenceLeaves);
- account.pendingConferenceLeaves.clear();
- }
- for (Conversation conversation : pendingLeaves) {
- leaveMuc(conversation);
- }
- final List<Conversation> pendingJoins;
- synchronized (account.pendingConferenceJoins) {
- pendingJoins = new ArrayList<>(account.pendingConferenceJoins);
- account.pendingConferenceJoins.clear();
- }
- for (Conversation conversation : pendingJoins) {
- joinMuc(conversation);
- }
- scheduleWakeUpCall(
- Config.PING_MAX_INTERVAL * 1000L, account.getUuid().hashCode());
- } else if (account.getStatus() == Account.State.OFFLINE
- || account.getStatus() == Account.State.DISABLED
- || account.getStatus() == Account.State.LOGGED_OUT) {
- resetSendingToWaiting(account);
- if (account.isConnectionEnabled() && isInLowPingTimeoutMode(account)) {
- Log.d(
- Config.LOGTAG,
- account.getJid().asBareJid()
- + ": went into offline state during low ping mode."
- + " reconnecting now");
- reconnectAccount(account, true, false);
- } else {
- final int timeToReconnect = SECURE_RANDOM.nextInt(10) + 2;
- scheduleWakeUpCall(timeToReconnect, account.getUuid().hashCode());
- }
- } else if (account.getStatus() == Account.State.REGISTRATION_SUCCESSFUL) {
- databaseBackend.updateAccount(account);
- reconnectAccount(account, true, false);
- } else if (account.getStatus() != Account.State.CONNECTING
- && account.getStatus() != Account.State.NO_INTERNET) {
- resetSendingToWaiting(account);
- if (connection != null && account.getStatus().isAttemptReconnect()) {
- final boolean aggressive =
- account.getStatus() == Account.State.SEE_OTHER_HOST
- || hasJingleRtpConnection(account);
- final int next = connection.getTimeToNextAttempt(aggressive);
- final boolean lowPingTimeoutMode = isInLowPingTimeoutMode(account);
- if (next <= 0) {
- Log.d(
- Config.LOGTAG,
- account.getJid().asBareJid()
- + ": error connecting account. reconnecting now."
- + " lowPingTimeout="
- + lowPingTimeoutMode);
- reconnectAccount(account, true, false);
- } else {
- final int attempt = connection.getAttempt() + 1;
- Log.d(
- Config.LOGTAG,
- account.getJid().asBareJid()
- + ": error connecting account. try again in "
- + next
- + "s for the "
- + attempt
- + " time. lowPingTimeout="
- + lowPingTimeoutMode
- + ", aggressive="
- + aggressive);
- scheduleWakeUpCall(next, account.getUuid().hashCode());
- if (aggressive) {
- internalPingExecutor.schedule(
- XmppConnectionService.this
- ::manageAccountConnectionStatesInternal,
- (next * 1000L) + 50,
- TimeUnit.MILLISECONDS);
- }
- }
- }
- }
- getNotificationService().updateErrorNotification();
- Log.d(Config.LOGTAG, "end onStatusChanged()");
- }
- };
private OpenPgpServiceConnection pgpServiceConnection;
private PgpEngine mPgpEngine = null;
@@ -515,7 +371,7 @@ public class XmppConnectionService extends Service {
return account.getJid().asBareJid() + "_" + avatar.owner + "_" + avatar.sha1sum;
}
- private boolean isInLowPingTimeoutMode(Account account) {
+ public boolean isInLowPingTimeoutMode(Account account) {
synchronized (mLowPingTimeoutMode) {
return mLowPingTimeoutMode.contains(account.getJid().asBareJid());
}
@@ -987,7 +843,7 @@ public class XmppConnectionService extends Service {
updateConversationUi();
}
- private void manageAccountConnectionStatesInternal() {
+ public void manageAccountConnectionStatesInternal() {
manageAccountConnectionStates(ACTION_INTERNAL_PING, null);
}
@@ -1050,17 +906,16 @@ public class XmppConnectionService extends Service {
final boolean isUiAction,
final boolean isAccountPushed,
final HashSet<Account> pingCandidates) {
+ final var connection = account.getXmppConnection();
if (!account.getStatus().isAttemptReconnect()) {
return false;
}
final var requestCode = account.getUuid().hashCode();
if (!hasInternetConnection()) {
- account.setStatus(Account.State.NO_INTERNET);
- statusListener.onStatusChanged(account);
+ connection.setStatusAndTriggerProcessor(Account.State.NO_INTERNET);
} else {
if (account.getStatus() == Account.State.NO_INTERNET) {
- account.setStatus(Account.State.OFFLINE);
- statusListener.onStatusChanged(account);
+ connection.setStatusAndTriggerProcessor(Account.State.OFFLINE);
}
if (account.getStatus() == Account.State.ONLINE) {
synchronized (mLowPingTimeoutMode) {
@@ -1111,7 +966,6 @@ public class XmppConnectionService extends Service {
} else if (account.getStatus() == Account.State.OFFLINE) {
reconnectAccount(account, true, interactive);
} else if (account.getStatus() == Account.State.CONNECTING) {
- final var connection = account.getXmppConnection();
final var connectionDuration = connection.getConnectionDuration();
final var discoDuration = connection.getDiscoDuration();
final var connectionTimeout = Config.CONNECT_TIMEOUT * 1000L - connectionDuration;
@@ -1128,7 +982,7 @@ public class XmppConnectionService extends Service {
final boolean aggressive =
account.getStatus() == Account.State.SEE_OTHER_HOST
|| hasJingleRtpConnection(account);
- if (account.getXmppConnection().getTimeToNextAttempt(aggressive) <= 0) {
+ if (connection.getTimeToNextAttempt(aggressive) <= 0) {
reconnectAccount(account, true, interactive);
}
}
@@ -1146,7 +1000,7 @@ public class XmppConnectionService extends Service {
}
}
- private void fetchServiceOutageStatus(final Account account) {
+ public void fetchServiceOutageStatus(final Account account) {
final var sosUrl = account.getKey(Account.KEY_SOS_URL);
if (Strings.isNullOrEmpty(sosUrl)) {
return;
@@ -1436,6 +1290,9 @@ public class XmppConnectionService extends Service {
this.databaseBackend = DatabaseBackend.getInstance(getApplicationContext());
Log.d(Config.LOGTAG, "restoring accounts...");
this.accounts = databaseBackend.getAccounts();
+ for (final var account : this.accounts) {
+ account.setXmppConnection(createConnection(account));
+ }
final SharedPreferences.Editor editor = getPreferences().edit();
final boolean hasEnabledAccounts = hasEnabledAccounts();
editor.putBoolean(SystemEventReceiver.SETTING_ENABLED_ACCOUNTS, hasEnabledAccounts).apply();
@@ -1843,8 +1700,6 @@ public class XmppConnectionService extends Service {
public XmppConnection createConnection(final Account account) {
final XmppConnection connection = new XmppConnection(account, this);
- // TODO move status listener into final variable in XmppConnection
- connection.setOnStatusChangedListener(this.statusListener);
connection.setOnJinglePacketReceivedListener((mJingleConnectionManager::deliverPacket));
// TODO move MessageAck into final Processor into XmppConnection
connection.setOnMessageAcknowledgeListener(this.mOnMessageAcknowledgedListener);
@@ -2077,7 +1932,7 @@ public class XmppConnectionService extends Service {
}
}
- private void sendUnsentMessages(final Conversation conversation) {
+ public void sendUnsentMessages(final Conversation conversation) {
conversation.findWaitingMessages(message -> resendMessage(message, true));
}
@@ -2456,7 +2311,6 @@ public class XmppConnectionService extends Service {
}
Log.d(Config.LOGTAG, "restoring roster...");
for (final Account account : accounts) {
- account.setXmppConnection(createConnection(account));
account.getXmppConnection().getManager(RosterManager.class).restore();
}
getBitmapCache().evictAll();
@@ -3100,7 +2954,8 @@ public class XmppConnectionService extends Service {
public boolean updateAccount(final Account account) {
if (databaseBackend.updateAccount(account)) {
account.setShowErrorNotification(true);
- this.statusListener.onStatusChanged(account);
+ // TODO what was the purpose of that? will likely be triggered by reconnect anyway?
+ // this.statusListener.onStatusChanged(account);
databaseBackend.updateAccount(account);
reconnectAccountInBackground(account);
updateAccountUi();
@@ -5207,7 +5062,7 @@ public class XmppConnectionService extends Service {
mDatabaseWriterExecutor.execute(() -> databaseBackend.updateConversation(conversation));
}
- private void reconnectAccount(
+ public void reconnectAccount(
final Account account, final boolean force, final boolean interactive) {
synchronized (account) {
final XmppConnection connection = account.getXmppConnection();
@@ -5231,6 +5086,7 @@ public class XmppConnectionService extends Service {
axolotlService.resetBrokenness();
}
if (!hasInternet) {
+ // TODO should this go via XmppConnection.setStatusAndTriggerProcessor()?
account.setStatus(Account.State.NO_INTERNET);
}
}
@@ -5942,7 +5798,7 @@ public class XmppConnectionService extends Service {
return this.mJingleConnectionManager;
}
- private boolean hasJingleRtpConnection(final Account account) {
+ public boolean hasJingleRtpConnection(final Account account) {
return this.mJingleConnectionManager.hasJingleRtpConnection(account);
}
@@ -117,6 +117,7 @@ import im.conversations.android.xmpp.model.stanza.Stanza;
import im.conversations.android.xmpp.model.streams.StreamError;
import im.conversations.android.xmpp.model.tls.Proceed;
import im.conversations.android.xmpp.model.tls.StartTls;
+import im.conversations.android.xmpp.processor.AccountStateProcessor;
import im.conversations.android.xmpp.processor.BindProcessor;
import java.io.ByteArrayInputStream;
import java.io.IOException;
@@ -202,10 +203,10 @@ public class XmppConnection implements Runnable {
private final Consumer<Presence> presenceListener;
private final Consumer<Iq> unregisteredIqListener;
private final Consumer<im.conversations.android.xmpp.model.stanza.Message> messageListener;
+ private final Consumer<Account.State> accountStateProcessor;
private AxolotlService axolotlService;
private final PgpDecryptionService pgpDecryptionService;
- private OnStatusChanged statusListener = null;
- private final Runnable bindListener;
+ private final Runnable bindProcessor;
private OnMessageAcknowledged acknowledgedListener = null;
private final PendingItem<String> pendingResumeId = new PendingItem<>();
private LoginInfo loginInfo;
@@ -228,7 +229,8 @@ public class XmppConnection implements Runnable {
// TODO requires roster and blocking not to be handled by this
this.unregisteredIqListener = new IqParser(service, this);
this.messageListener = new MessageParser(service, this);
- this.bindListener = new BindProcessor(service, this);
+ this.bindProcessor = new BindProcessor(service, this);
+ this.accountStateProcessor = new AccountStateProcessor(service, this);
this.managers = Managers.get(service, this);
this.setAxolotlService(new AxolotlService(account, service));
this.pgpDecryptionService = new PgpDecryptionService(service);
@@ -282,15 +284,7 @@ public class XmppConnection implements Runnable {
return;
}
}
- if (statusListener != null) {
- try {
- statusListener.onStatusChanged(account);
- } catch (final Exception e) {
- Log.d(Config.LOGTAG, "error executing shit", e);
- }
- } else {
- Log.d(Config.LOGTAG, "status changed listener was null");
- }
+ this.accountStateProcessor.accept(nextStatus);
}
public Jid getJidForCommand(final String node) {
@@ -2366,7 +2360,7 @@ public class XmppConnection implements Runnable {
private void finalizeBind() {
this.offlineMessagesRetrieved = false;
- this.bindListener.run();
+ this.bindProcessor.run();
this.changeStatusToOnline();
}
@@ -2660,10 +2654,6 @@ public class XmppConnection implements Runnable {
this.jingleListener = listener;
}
- public void setOnStatusChangedListener(final OnStatusChanged listener) {
- this.statusListener = listener;
- }
-
public void setOnMessageAcknowledgeListener(final OnMessageAcknowledged listener) {
this.acknowledgedListener = listener;
}
@@ -2928,6 +2918,11 @@ public class XmppConnection implements Runnable {
this.addOnAdvancedStreamFeaturesAvailableListener(axolotlService);
}
+ public void setStatusAndTriggerProcessor(final Account.State state) {
+ this.account.setStatus(state);
+ this.accountStateProcessor.accept(state);
+ }
+
private class MyKeyManager implements X509KeyManager {
@Override
public String chooseClientAlias(String[] strings, Principal[] principals, Socket socket) {
@@ -0,0 +1,152 @@
+package im.conversations.android.xmpp.processor;
+
+import static eu.siacs.conversations.utils.Random.SECURE_RANDOM;
+
+import android.util.Log;
+import eu.siacs.conversations.Config;
+import eu.siacs.conversations.entities.Account;
+import eu.siacs.conversations.entities.Conversation;
+import eu.siacs.conversations.http.ServiceOutageStatus;
+import eu.siacs.conversations.services.XmppConnectionService;
+import eu.siacs.conversations.xmpp.XmppConnection;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
+
+public class AccountStateProcessor extends XmppConnection.Delegate
+ implements Consumer<Account.State> {
+
+ private final XmppConnectionService service;
+
+ public AccountStateProcessor(final XmppConnectionService service, XmppConnection connection) {
+ super(service.getApplicationContext(), connection);
+ this.service = service;
+ }
+
+ @Override
+ public void accept(final Account.State status) {
+ final var account = getAccount();
+ if (ServiceOutageStatus.isPossibleOutage(status)) {
+ this.service.fetchServiceOutageStatus(account);
+ }
+ this.service.updateAccountUi();
+
+ if (account.getStatus() == Account.State.ONLINE || account.getStatus().isError()) {
+ this.service.getQuickConversationsService().signalAccountStateChange();
+ }
+
+ if (account.getStatus() == Account.State.ONLINE) {
+ synchronized (this.service.mLowPingTimeoutMode) {
+ if (this.service.mLowPingTimeoutMode.remove(account.getJid().asBareJid())) {
+ Log.d(
+ Config.LOGTAG,
+ account.getJid().asBareJid() + ": leaving low ping timeout mode");
+ }
+ }
+ if (account.setShowErrorNotification(true)) {
+ this.service.databaseBackend.updateAccount(account);
+ }
+ this.service.getMessageArchiveService().executePendingQueries(account);
+ if (connection != null && connection.getFeatures().csi()) {
+ if (this.service.checkListeners()) {
+ Log.d(Config.LOGTAG, account.getJid().asBareJid() + " sending csi//inactive");
+ connection.sendInactive();
+ } else {
+ Log.d(Config.LOGTAG, account.getJid().asBareJid() + " sending csi//active");
+ connection.sendActive();
+ }
+ }
+ List<Conversation> conversations = this.service.getConversations();
+ for (Conversation conversation : conversations) {
+ final boolean inProgressJoin;
+ synchronized (account.inProgressConferenceJoins) {
+ inProgressJoin = account.inProgressConferenceJoins.contains(conversation);
+ }
+ final boolean pendingJoin;
+ synchronized (account.pendingConferenceJoins) {
+ pendingJoin = account.pendingConferenceJoins.contains(conversation);
+ }
+ if (conversation.getAccount() == account && !pendingJoin && !inProgressJoin) {
+ this.service.sendUnsentMessages(conversation);
+ }
+ }
+ final List<Conversation> pendingLeaves;
+ synchronized (account.pendingConferenceLeaves) {
+ pendingLeaves = new ArrayList<>(account.pendingConferenceLeaves);
+ account.pendingConferenceLeaves.clear();
+ }
+ for (Conversation conversation : pendingLeaves) {
+ this.service.leaveMuc(conversation);
+ }
+ final List<Conversation> pendingJoins;
+ synchronized (account.pendingConferenceJoins) {
+ pendingJoins = new ArrayList<>(account.pendingConferenceJoins);
+ account.pendingConferenceJoins.clear();
+ }
+ for (Conversation conversation : pendingJoins) {
+ this.service.joinMuc(conversation);
+ }
+ this.service.scheduleWakeUpCall(
+ Config.PING_MAX_INTERVAL * 1000L, account.getUuid().hashCode());
+ } else if (account.getStatus() == Account.State.OFFLINE
+ || account.getStatus() == Account.State.DISABLED
+ || account.getStatus() == Account.State.LOGGED_OUT) {
+ this.service.resetSendingToWaiting(account);
+ if (account.isConnectionEnabled() && this.service.isInLowPingTimeoutMode(account)) {
+ Log.d(
+ Config.LOGTAG,
+ account.getJid().asBareJid()
+ + ": went into offline state during low ping mode."
+ + " reconnecting now");
+ this.service.reconnectAccount(account, true, false);
+ } else {
+ final int timeToReconnect = SECURE_RANDOM.nextInt(10) + 2;
+ this.service.scheduleWakeUpCall(timeToReconnect, account.getUuid().hashCode());
+ }
+ } else if (account.getStatus() == Account.State.REGISTRATION_SUCCESSFUL) {
+ this.service.databaseBackend.updateAccount(account);
+ this.service.reconnectAccount(account, true, false);
+ } else if (account.getStatus() != Account.State.CONNECTING
+ && account.getStatus() != Account.State.NO_INTERNET) {
+ this.service.resetSendingToWaiting(account);
+ if (connection != null && account.getStatus().isAttemptReconnect()) {
+ final boolean aggressive =
+ account.getStatus() == Account.State.SEE_OTHER_HOST
+ || this.service.hasJingleRtpConnection(account);
+ final int next = connection.getTimeToNextAttempt(aggressive);
+ final boolean lowPingTimeoutMode = this.service.isInLowPingTimeoutMode(account);
+ if (next <= 0) {
+ Log.d(
+ Config.LOGTAG,
+ account.getJid().asBareJid()
+ + ": error connecting account. reconnecting now."
+ + " lowPingTimeout="
+ + lowPingTimeoutMode);
+ this.service.reconnectAccount(account, true, false);
+ } else {
+ final int attempt = connection.getAttempt() + 1;
+ Log.d(
+ Config.LOGTAG,
+ account.getJid().asBareJid()
+ + ": error connecting account. try again in "
+ + next
+ + "s for the "
+ + attempt
+ + " time. lowPingTimeout="
+ + lowPingTimeoutMode
+ + ", aggressive="
+ + aggressive);
+ this.service.scheduleWakeUpCall(next, account.getUuid().hashCode());
+ if (aggressive) {
+ this.service.internalPingExecutor.schedule(
+ service::manageAccountConnectionStatesInternal,
+ (next * 1000L) + 50,
+ TimeUnit.MILLISECONDS);
+ }
+ }
+ }
+ }
+ this.service.getNotificationService().updateErrorNotification();
+ }
+}