1package im.conversations.android.xmpp.processor;
2
3import static eu.siacs.conversations.utils.Random.SECURE_RANDOM;
4
5import android.util.Log;
6import eu.siacs.conversations.Config;
7import eu.siacs.conversations.entities.Account;
8import eu.siacs.conversations.entities.Conversation;
9import eu.siacs.conversations.http.ServiceOutageStatus;
10import eu.siacs.conversations.services.XmppConnectionService;
11import eu.siacs.conversations.xmpp.XmppConnection;
12import java.util.ArrayList;
13import java.util.List;
14import java.util.concurrent.TimeUnit;
15import java.util.function.Consumer;
16
17public class AccountStateProcessor extends XmppConnection.Delegate
18 implements Consumer<Account.State> {
19
20 private final XmppConnectionService service;
21
22 public AccountStateProcessor(final XmppConnectionService service, XmppConnection connection) {
23 super(service.getApplicationContext(), connection);
24 this.service = service;
25 }
26
27 @Override
28 public void accept(final Account.State status) {
29 final var account = getAccount();
30 if (ServiceOutageStatus.isPossibleOutage(status)) {
31 this.service.fetchServiceOutageStatus(account);
32 }
33 this.service.updateAccountUi();
34
35 if (account.getStatus() == Account.State.ONLINE || account.getStatus().isError()) {
36 this.service.getQuickConversationsService().signalAccountStateChange();
37 }
38
39 if (account.getStatus() == Account.State.ONLINE) {
40 synchronized (this.service.mLowPingTimeoutMode) {
41 if (this.service.mLowPingTimeoutMode.remove(account.getJid().asBareJid())) {
42 Log.d(
43 Config.LOGTAG,
44 account.getJid().asBareJid() + ": leaving low ping timeout mode");
45 }
46 }
47 if (account.setShowErrorNotification(true)) {
48 this.service.databaseBackend.updateAccount(account);
49 }
50 this.service.getMessageArchiveService().executePendingQueries(account);
51 if (connection != null && connection.getFeatures().csi()) {
52 if (this.service.checkListeners()) {
53 Log.d(Config.LOGTAG, account.getJid().asBareJid() + " sending csi//inactive");
54 connection.sendInactive();
55 } else {
56 Log.d(Config.LOGTAG, account.getJid().asBareJid() + " sending csi//active");
57 connection.sendActive();
58 }
59 }
60 List<Conversation> conversations = this.service.getConversations();
61 for (Conversation conversation : conversations) {
62 final boolean inProgressJoin;
63 synchronized (account.inProgressConferenceJoins) {
64 inProgressJoin = account.inProgressConferenceJoins.contains(conversation);
65 }
66 final boolean pendingJoin;
67 synchronized (account.pendingConferenceJoins) {
68 pendingJoin = account.pendingConferenceJoins.contains(conversation);
69 }
70 if (conversation.getAccount() == account && !pendingJoin && !inProgressJoin) {
71 this.service.sendUnsentMessages(conversation);
72 }
73 }
74 final List<Conversation> pendingLeaves;
75 synchronized (account.pendingConferenceLeaves) {
76 pendingLeaves = new ArrayList<>(account.pendingConferenceLeaves);
77 account.pendingConferenceLeaves.clear();
78 }
79 for (Conversation conversation : pendingLeaves) {
80 this.service.leaveMuc(conversation);
81 }
82 final List<Conversation> pendingJoins;
83 synchronized (account.pendingConferenceJoins) {
84 pendingJoins = new ArrayList<>(account.pendingConferenceJoins);
85 account.pendingConferenceJoins.clear();
86 }
87 for (Conversation conversation : pendingJoins) {
88 this.service.joinMuc(conversation);
89 }
90 this.service.scheduleWakeUpCall(
91 Config.PING_MAX_INTERVAL * 1000L, account.getUuid().hashCode());
92 } else if (account.getStatus() == Account.State.OFFLINE
93 || account.getStatus() == Account.State.DISABLED
94 || account.getStatus() == Account.State.LOGGED_OUT) {
95 this.service.resetSendingToWaiting(account);
96 if (account.isConnectionEnabled() && this.service.isInLowPingTimeoutMode(account)) {
97 Log.d(
98 Config.LOGTAG,
99 account.getJid().asBareJid()
100 + ": went into offline state during low ping mode."
101 + " reconnecting now");
102 this.service.reconnectAccount(account, true, false);
103 } else {
104 final int timeToReconnect = SECURE_RANDOM.nextInt(10) + 2;
105 this.service.scheduleWakeUpCall(timeToReconnect, account.getUuid().hashCode());
106 }
107 } else if (account.getStatus() == Account.State.REGISTRATION_SUCCESSFUL) {
108 this.service.databaseBackend.updateAccount(account);
109 this.service.reconnectAccount(account, true, false);
110 } else if (account.getStatus() != Account.State.CONNECTING
111 && account.getStatus() != Account.State.NO_INTERNET) {
112 this.service.resetSendingToWaiting(account);
113 if (connection != null && account.getStatus().isAttemptReconnect()) {
114 final boolean aggressive =
115 account.getStatus() == Account.State.SEE_OTHER_HOST
116 || this.service.hasJingleRtpConnection(account);
117 final int next = connection.getTimeToNextAttempt(aggressive);
118 final boolean lowPingTimeoutMode = this.service.isInLowPingTimeoutMode(account);
119 if (next <= 0) {
120 Log.d(
121 Config.LOGTAG,
122 account.getJid().asBareJid()
123 + ": error connecting account. reconnecting now."
124 + " lowPingTimeout="
125 + lowPingTimeoutMode);
126 this.service.reconnectAccount(account, true, false);
127 } else {
128 final int attempt = connection.getAttempt() + 1;
129 Log.d(
130 Config.LOGTAG,
131 account.getJid().asBareJid()
132 + ": error connecting account. try again in "
133 + next
134 + "s for the "
135 + attempt
136 + " time. lowPingTimeout="
137 + lowPingTimeoutMode
138 + ", aggressive="
139 + aggressive);
140 this.service.scheduleWakeUpCall(next, account.getUuid().hashCode());
141 if (aggressive) {
142 this.service.internalPingExecutor.schedule(
143 service::manageAccountConnectionStatesInternal,
144 (next * 1000L) + 50,
145 TimeUnit.MILLISECONDS);
146 }
147 }
148 }
149 }
150 this.service.getNotificationService().updateErrorNotification();
151 }
152}