/** * Copyright (c) 2013, Redsolution LTD. All rights reserved. * * This file is part of Xabber project; you can redistribute it and/or * modify it under the terms of the GNU General Public License, Version 3. * * Xabber is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License, * along with this program. If not, see http://www.gnu.org/licenses/. */ package com.xabber.android.data.connection; import android.support.annotation.NonNull; import android.widget.Toast; import com.xabber.android.data.Application; import com.xabber.android.data.account.AccountItem; import com.xabber.android.data.log.LogManager; import com.xabber.android.data.account.AccountManager; import com.xabber.android.data.connection.listeners.OnPacketListener; import com.xabber.android.data.entity.AccountJid; import com.xabber.android.data.roster.AccountRosterListener; import org.jivesoftware.smack.*; import org.jivesoftware.smack.packet.Stanza; import org.jivesoftware.smack.parsing.ExceptionLoggingCallback; import org.jivesoftware.smack.roster.Roster; import org.jivesoftware.smack.sm.predicates.ForEveryStanza; import org.jivesoftware.smack.tcp.XMPPTCPConnection; import org.jivesoftware.smackx.ping.PingFailedListener; import org.jivesoftware.smackx.ping.PingManager; import org.jxmpp.jid.DomainBareJid; import org.jxmpp.jid.EntityFullJid; import org.jxmpp.jid.parts.Localpart; import org.jxmpp.jid.parts.Resourcepart; /** * Abstract connection. * * @author alexander.ivanov */ public abstract class ConnectionItem { @NonNull private final AccountJid account; final String logTag; /** * Connection options. */ @NonNull private final ConnectionSettings connectionSettings; @NonNull private final com.xabber.android.data.connection.ConnectionListener connectionListener; /** * XMPP connection. */ @NonNull XMPPTCPConnection connection; /** * Current state. */ private ConnectionState state; @NonNull private final AccountRosterListener rosterListener; Toast toast; private ConnectionThread connectionThread; public ConnectionItem(boolean custom, String host, int port, DomainBareJid serverName, Localpart userName, Resourcepart resource, boolean storePassword, String password, boolean saslEnabled, TLSMode tlsMode, boolean compression, ProxyType proxyType, String proxyHost, int proxyPort, String proxyUser, String proxyPassword) { this.account = AccountJid.from(userName, serverName, resource); this.logTag = getClass().getSimpleName() + ": " + account; rosterListener = new AccountRosterListener(getAccount()); connectionListener = new com.xabber.android.data.connection.ConnectionListener(this); connectionSettings = new ConnectionSettings(userName, serverName, resource, custom, host, port, password, saslEnabled, tlsMode, compression, proxyType, proxyHost, proxyPort, proxyUser, proxyPassword); connection = createConnection(); updateState(ConnectionState.offline); } private XMPPTCPConnection createConnection() { connection = ConnectionBuilder.build(account, connectionSettings); LogManager.i(logTag, "Connection created"); connectionThread = new ConnectionThread(connection, this); addConnectionListeners(); configureConnection(); return connection; } @NonNull public AccountJid getAccount() { return account; } @NonNull public XMPPTCPConnection getConnection() { return connection; } /** * @return connection options. */ @NonNull public ConnectionSettings getConnectionSettings() { return connectionSettings; } public synchronized ConnectionState getState() { return state; } /** * Returns real full jid, that was assigned while login. * * @return <code>null</code> if connection is not established. */ public EntityFullJid getRealJid() { return connection.getUser(); } public boolean connect() { LogManager.i(logTag, "connect"); updateState(ConnectionState.connecting); if (connectionThread == null) { connectionThread = new ConnectionThread(connection, this); }; return connectionThread.start(); } private void configureConnection() { // enable Stream Management support. SMACK will only enable SM if supported by the server, // so no additional checks are required. connection.setUseStreamManagement(true); connection.setUseStreamManagementResumption(false); // by default Smack disconnects in case of parsing errors connection.setParsingExceptionCallback(new ExceptionLoggingCallback()); } private void addConnectionListeners() { final Roster roster = Roster.getInstanceFor(connection); roster.addRosterListener(rosterListener); roster.addRosterLoadedListener(rosterListener); roster.setSubscriptionMode(Roster.SubscriptionMode.manual); roster.setRosterLoadedAtLogin(true); connection.addAsyncStanzaListener(everyStanzaListener, ForEveryStanza.INSTANCE); connection.addConnectionListener(connectionListener); PingManager.getInstanceFor(connection).registerPingFailedListener(pingFailedListener); } /** * Update password. */ protected void onPasswordChanged(String password) { connectionSettings.setPassword(password); } public void disconnect() { Thread thread = new Thread("Disconnection thread for " + connection) { @Override public void run() { LogManager.i(logTag, "disconnect"); if (connection.isConnected()) { updateState(ConnectionState.disconnecting); LogManager.i(logTag, "connected now, disconnecting..."); connection.disconnect(); } else { LogManager.i(logTag, "already disconnected"); } } }; thread.setPriority(Thread.MIN_PRIORITY); thread.setDaemon(true); thread.start(); } public void recreateConnection() { LogManager.i(logTag, "recreateConnection"); Thread thread = new Thread("Disconnection thread for " + connection) { @Override public void run() { updateState(ConnectionState.disconnecting); connection.disconnect(); Application.getInstance().runOnUiThread(new Runnable() { @Override public void run() { createNewConnection(); } }); } }; thread.setPriority(Thread.MIN_PRIORITY); thread.setDaemon(true); thread.start(); } @SuppressWarnings("WeakerAccess") void createNewConnection() { LogManager.i(logTag, "createNewConnection"); PingManager.getInstanceFor(connection).unregisterPingFailedListener(pingFailedListener); connection.removeConnectionListener(connectionListener); connection.removeAsyncStanzaListener(everyStanzaListener); final Roster roster = Roster.getInstanceFor(connection); roster.removeRosterLoadedListener(rosterListener); roster.removeRosterListener(rosterListener); createConnection(); ReconnectionManager.getInstance().resetReconnectionInfo(account); } void updateState(ConnectionState newState) { boolean changed = setState(newState); if (changed) { if (newState == ConnectionState.connected) { AccountManager.getInstance().setSuccessfulConnectionHappened(account, true); } AccountManager.getInstance().onAccountChanged(getAccount()); } } private synchronized boolean setState(ConnectionState newState) { ConnectionState prevState = this.state; this.state = newState; LogManager.i(logTag, "updateState. prev " + prevState + " new " + newState); return prevState != state; } private StanzaListener everyStanzaListener = new StanzaListener() { @Override public void processStanza(final Stanza stanza) throws SmackException.NotConnectedException { Application.getInstance().runOnUiThread(new Runnable() { @Override public void run() { for (OnPacketListener listener : Application.getInstance().getManagers(OnPacketListener.class)) { listener.onStanza(ConnectionItem.this, stanza); } } }); } }; private PingFailedListener pingFailedListener = new PingFailedListener() { @Override public void pingFailed() { LogManager.i(this, "pingFailed for " + getAccount()); updateState(ConnectionState.offline); disconnect(); } }; }