package com.xabber.android.data.connection; import android.support.annotation.NonNull; import com.xabber.android.data.OnTimerListener; import com.xabber.android.data.account.AccountItem; import com.xabber.android.data.account.AccountManager; import com.xabber.android.data.account.listeners.OnAccountRemovedListener; import com.xabber.android.data.connection.listeners.OnConnectedListener; import com.xabber.android.data.entity.AccountJid; import com.xabber.android.data.log.LogManager; import java.util.Collection; import java.util.HashMap; import java.util.concurrent.TimeUnit; public class ReconnectionManager implements OnConnectedListener, OnAccountRemovedListener, OnTimerListener { /** * Intervals in seconds to be used for attempt to reconnect. First value * will be used on first attempt. Next value will be used if reconnection * fails. Last value will be used if there is no more values in array. */ private final static int RECONNECT_AFTER[] = new int[]{0, 2, 10, 30, 60}; private static final String LOG_TAG = ReconnectionManager.class.getSimpleName(); /** * Managed connections. */ private final HashMap<AccountJid, ReconnectionInfo> connections; private static ReconnectionManager instance; public static ReconnectionManager getInstance() { if (instance == null) { instance = new ReconnectionManager(); } return instance; } private ReconnectionManager() { connections = new HashMap<>(); } @Override public void onTimer() { Collection<AccountJid> allAccounts = AccountManager.getInstance().getAllAccounts(); for (AccountJid accountJid : allAccounts) { checkConnection(AccountManager.getInstance().getAccount(accountJid), getReconnectionInfo(accountJid)); } } private void checkConnection(AccountItem accountItem, ReconnectionInfo reconnectionInfo) { if (!accountItem.isEnabled()) { if (accountItem.getState() != ConnectionState.offline) { ((ConnectionItem)accountItem).updateState(ConnectionState.offline); } } if ((!accountItem.isEnabled() || !accountItem.getRawStatusMode().isOnline()) && accountItem.getConnection().isConnected()) { accountItem.disconnect(); return; } if (!isAccountNeedConnection(accountItem)) { reconnectionInfo.reset(); return; } if (!isTimeToReconnect(reconnectionInfo)) { LogManager.i(LOG_TAG, accountItem.getAccount() + " not authenticated. State: " + accountItem.getState() + " waiting... seconds from last reconnection " + getTimeSinceLastReconnectionSeconds(reconnectionInfo)); return; } boolean newThreadStarted = accountItem.connect(); if (newThreadStarted) { reconnectionInfo.nextAttempt(); LogManager.i(LOG_TAG, accountItem.getAccount() + " not authenticated. new thread started. next attempt " + reconnectionInfo.getReconnectAttempts()); } else { reconnectionInfo.resetReconnectionTime(); LogManager.i(LOG_TAG, accountItem.getAccount() + " not authenticated. already in progress. reset time. attempt " + reconnectionInfo.getReconnectAttempts()); } } private boolean isAccountNeedConnection(AccountItem accountItem) { return accountItem.isEnabled() && accountItem.getRawStatusMode().isOnline() && !accountItem.getConnection().isAuthenticated(); } private boolean isTimeToReconnect(ReconnectionInfo reconnectionInfo) { int reconnectAfter; if (reconnectionInfo.getReconnectAttempts() < RECONNECT_AFTER.length) { reconnectAfter = RECONNECT_AFTER[reconnectionInfo.getReconnectAttempts()]; } else { reconnectAfter = RECONNECT_AFTER[RECONNECT_AFTER.length - 1]; } return getTimeSinceLastReconnectionSeconds(reconnectionInfo) >= reconnectAfter; } private long getTimeSinceLastReconnectionSeconds(ReconnectionInfo reconnectionInfo) { return TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis() - reconnectionInfo.getLastReconnectionTimeMillis()); } public void requestReconnect(AccountJid accountJid) { getReconnectionInfo(accountJid).reset(); } @NonNull private ReconnectionInfo getReconnectionInfo(AccountJid accountJid) { ReconnectionInfo reconnectionInfo = connections.get(accountJid); if (reconnectionInfo == null) { LogManager.i(LOG_TAG, "getReconnectionInfo new reconnection info for " + accountJid); reconnectionInfo = new ReconnectionInfo(); connections.put(accountJid, reconnectionInfo); } return reconnectionInfo; } void resetReconnectionInfo(AccountJid accountJid) { ReconnectionInfo info = connections.get(accountJid); if (info != null) { info.reset(); } } @Override public void onConnected(ConnectionItem connection) { LogManager.i(LOG_TAG, "onConnected " + connection.getAccount()); resetReconnectionInfo(connection.getAccount()); } @Override public void onAccountRemoved(AccountItem accountItem) { connections.remove(accountItem.getAccount()); } }