/**
* Copyright (c) 2011, SOCIETIES Consortium (WATERFORD INSTITUTE OF TECHNOLOGY (TSSG), HERIOT-WATT UNIVERSITY (HWU), SOLUTA.NET
* (SN), GERMAN AEROSPACE CENTRE (Deutsches Zentrum fuer Luft- und Raumfahrt e.V.) (DLR), Zavod za varnostne tehnologije
* informacijske družbe in elektronsko poslovanje (SETCCE), INSTITUTE OF COMMUNICATION AND COMPUTER SYSTEMS (ICCS), LAKE
* COMMUNICATIONS (LAKE), INTEL PERFORMANCE LEARNING SOLUTIONS LTD (INTEL), PORTUGAL TELECOM INOVAÇÃO, SA (PTIN), IBM Corp.,
* INSTITUT TELECOM (ITSUD), AMITEC DIACHYTI EFYIA PLIROFORIKI KAI EPIKINONIES ETERIA PERIORISMENIS EFTHINIS (AMITEC), TELECOM
* ITALIA S.p.a.(TI), TRIALOG (TRIALOG), Stiftelsen SINTEF (SINTEF), NEC EUROPE LTD (NEC))
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following
* conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
* BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
* SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.societies.android.platform.comms.state;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.util.Log;
import org.jivesoftware.smack.*;
import org.jivesoftware.smack.filter.PacketFilter;
import org.jivesoftware.smack.packet.Packet;
import org.societies.utilities.DBC.Dbc;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.*;
/**
* Manages the Societies Android Communications XMPP connection with a finite state machine (FSM)
* The main
*/
@SuppressWarnings("ConstantConditions")
public class XMPPConnectionManager implements IConnectionState {
private final static String LOG_TAG = XMPPConnectionManager.class.getCanonicalName();
private final static boolean DEBUG_LOGGING = true;
private final static String KEY_DIVIDER = ":";
//(a)Smack does not differentiate between different types of network problems and authentication and all
//exceptions are returned as the generic XMPPException
private final static String XMPP_AUTHENICATION_EXCEPTION_ID = "SASL authentication";
private final static int EVENT_PROCESSOR_SLEEP_INTERVAL = 200;
//In the event that the XMPP connection is broken the manager will use the following parameters
//to control its manual reconnection attempts
//TODO: app preferences
private final static int MANUAL_RECONNECTION_DELAY = 2000;
private final static int MANUAL_RECONNECTION_ATTEMPTS = 20;
private final static int MANUAL_RECONNECTION_INTERVAL = 5000;
private Context context;
private ConnectionState currentState;
private XMPPConnection xmppConnection;
private ConnectionListener connectionListener;
private Map<String, StateEventAction> fsmLookupTable;
private ConcurrentLinkedQueue<ConnectionEvent> eventQueue;
private ScheduledExecutorService queueScheduler;
private ScheduledExecutorService connectionScheduler;
private XMPPConnectionProperties currentXmppConnectProps;
private XMPPConnectionProperties newXmppConnectProps;
private BroadcastReceiver bReceiver;
private Class<?> actionHandlerParams[] = {ConnectionState.class};
private long remoteCallId;
private String remoteCallClient;
private String remoteUserJid;
private int reconnectionAttempts;
public XMPPConnectionManager() {
this.currentState = IConnectionState.ConnectionState.Disconnected;
this.eventQueue = new ConcurrentLinkedQueue<ConnectionEvent>();
this.xmppConnection = null;
this.connectionListener = null;
this.currentXmppConnectProps = null;
this.newXmppConnectProps = null;
this.fsmLookupTable = new HashMap<String, StateEventAction>();
this.populateFSMtable();
}
@Override
public boolean isConnected() {
boolean retValue = false;
if (null != this.xmppConnection && this.xmppConnection.isConnected() && (this.currentState.equals(ConnectionState.Connected))) {
retValue = true;
}
return retValue;
}
@Override
public boolean isAuthenticated() {
return isConnected() && this.xmppConnection.isAuthenticated();
}
@Override
public ConnectionState getConnectionState() {
if (DEBUG_LOGGING) {
Log.d(LOG_TAG, "getConnectionState: " + this.currentState);
}
return this.currentState;
}
@Override
public void enableConnection(XMPPConnectionProperties connectProps, Context context, String userJid, String client, long remoteCallId) {
Dbc.require("Connection properties cannot be null", null != connectProps);
Dbc.require("Context cannot be null", null != context);
Dbc.require("Client must be specified", null != client && client.length() > 0);
if (DEBUG_LOGGING) {
Log.d(LOG_TAG, "enableConnection : " + connectProps.toString());
}
this.context = context;
this.remoteCallId = remoteCallId;
this.remoteCallClient = client;
this.remoteUserJid = userJid;
this.newXmppConnectProps = connectProps;
if (null == this.currentXmppConnectProps) {
this.currentXmppConnectProps = connectProps;
}
this.reconnectionAttempts = 0;
this.startEventProcessor();
this.addEventToQueue(ConnectionEvent.Start);
}
@Override
public void disableConnection(String client, long remoteCallId) {
Dbc.require("Client must be specified", null != client && client.length() > 0);
if (DEBUG_LOGGING) {
Log.d(LOG_TAG, "disableConnection");
}
//the connection must be disabled even if the connection has been previously broken
this.remoteCallId = remoteCallId;
this.remoteCallClient = client;
this.addEventToQueue(ConnectionEvent.Disconnect);
}
@Override
public XMPPConnection getValidConnection() throws NoXMPPConnectionAvailableException {
XMPPConnection xmppConnection = null;
if (this.isConnected()
&& this.getConnectionState().equals(IConnectionState.ConnectionState.Connected)) {
xmppConnection = this.xmppConnection;
} else {
throw new NoXMPPConnectionAvailableException("XMPP connection unavailable, currently in state: " + this.getConnectionState().name());
}
return xmppConnection;
}
/**
* Start a connection
*/
private void startEventProcessor() {
if (DEBUG_LOGGING) {
Log.d(LOG_TAG, "startEventProcessor");
}
this.queueScheduler = Executors.newSingleThreadScheduledExecutor();
final Runnable processQueue = new Runnable() {
@Override
public void run() {
// if (DEBUG_LOGGING) {
// Log.d(LOG_TAG, "event processor running");
// }
while (null != XMPPConnectionManager.this.eventQueue.peek()) {
ConnectionEvent pendingEvent = XMPPConnectionManager.this.eventQueue.poll();
XMPPConnectionManager.this.processQueueEvent(pendingEvent);
}
}
};
final ScheduledFuture processQueueTimer = queueScheduler.scheduleAtFixedRate(processQueue, 0, EVENT_PROCESSOR_SLEEP_INTERVAL, TimeUnit.MILLISECONDS);
}
/**
* End a connection
*/
// public void endConnection() {
// if (DEBUG_LOGGING) {
// Log.d(LOG_TAG, "endConnection");
// }
// this.addEventToQueue(ConnectionEvent.Disconnect);
// }
/**
* Update the status of the FSM and send intent to interested receivers
*/
private void updateStatus(ConnectionState newState) {
if (DEBUG_LOGGING) {
Log.d(LOG_TAG, "updateStatus from: " + this.currentState.name() + " to : " + newState.name());
}
ConnectionState formerState = this.currentState;
this.currentState = newState;
this.sendStateChangedIntent(formerState, newState);
}
/**
* Send an intent of the current state. Intents are not sent when attempting a re-connection.
*/
private void sendStateChangedIntent(ConnectionState formerState, ConnectionState currentState) {
if (DEBUG_LOGGING) {
Log.d(LOG_TAG, "send State Changed intent");
}
if (0 == this.reconnectionAttempts) {
Intent intent = new Intent(IConnectionState.XMPP_CONNECTION_CHANGED);
intent.putExtra(IConnectionState.INTENT_FORMER_CONNECTION_STATE, formerState.ordinal());
intent.putExtra(IConnectionState.INTENT_CURRENT_CONNECTION_STATE, currentState.ordinal());
intent.putExtra(IConnectionState.INTENT_CONNECTED, currentState.equals(ConnectionState.Connected));
intent.putExtra(IConnectionState.INTENT_REMOTE_CALL_ID, this.remoteCallId);
intent.putExtra(IConnectionState.INTENT_REMOTE_CALL_CLIENT, this.remoteCallClient);
intent.putExtra(IConnectionState.INTENT_REMOTE_USER_JID, this.remoteUserJid);
this.context.sendBroadcast(intent);
}
}
/**
* Send an intent of the current state. Intents are not sent when attempting a re-connection.
*/
private void sendLoginFailureIntent(String type, String failureMessage) {
if (DEBUG_LOGGING) {
Log.d(LOG_TAG, "send login failure intent, reason: " + failureMessage + " type: " + type);
}
if (0 == this.reconnectionAttempts) {
Intent intent = new Intent(type);
intent.putExtra(IConnectionState.INTENT_FAILURE_DESCRIPTION, failureMessage);
intent.putExtra(IConnectionState.INTENT_REMOTE_CALL_ID, this.remoteCallId);
intent.putExtra(IConnectionState.INTENT_REMOTE_CALL_CLIENT, this.remoteCallClient);
intent.putExtra(IConnectionState.INTENT_REMOTE_USER_JID, this.remoteUserJid);
this.context.sendBroadcast(intent);
}
}
/**
* Create an {@link XMPPConnection} listener to monitor the connection status
*/
private void createConnectionListener() {
Dbc.invariant("XMPP connection cannot be null", null != this.xmppConnection);
if (DEBUG_LOGGING) {
Log.d(LOG_TAG, "createConnectionListener");
}
ConnectionListener connListener = new ConnectionListener() {
@Override
public void reconnectionSuccessful() {
if (DEBUG_LOGGING) {
Log.d(LOG_TAG, "XMPP connection reconnection attempt successful");
}
XMPPConnectionManager.this.addEventToQueue(ConnectionEvent.AutoReconnected);
}
@Override
public void reconnectionFailed(Exception e) {
if (DEBUG_LOGGING) {
Log.d(LOG_TAG, "XMPP connection reconnection attempt failed due to: " + e.getMessage());
}
XMPPConnectionManager.this.addEventToQueue(ConnectionEvent.AttemptAutoReconnection);
}
@Override
public void reconnectingIn(int seconds) {
if (DEBUG_LOGGING) {
Log.d(LOG_TAG, "XMPP connection attempting reconnection in " + seconds + " seconds");
}
//TODO: may have to examine timespan to determine if reconnection attempts futile
}
@Override
public void connectionClosedOnError(Exception e) {
if (DEBUG_LOGGING) {
Log.d(LOG_TAG, "XMPP connection closed with error: " + e.getMessage());
}
XMPPConnectionManager.this.addEventToQueue(ConnectionEvent.ConnectionBroken);
}
@Override
public void connectionClosed() {
if (DEBUG_LOGGING) {
Log.d(LOG_TAG, "XMPP connection closed");
}
// XMPPConnectionManager.this.addEventToQueue(ConnectionEvent.Disconnect);
}
};
this.xmppConnection.addConnectionListener(connListener);
this.connectionListener = connListener;
}
private static String generateKey(ConnectionState currentState, ConnectionEvent event) {
return currentState.name() + KEY_DIVIDER + event.name();
}
/**
* populate the FSM lookuptable
* <p/>
* The table is modelled as a Map with the current state and event acting as the key and a StateEventAction object as
* the Map pair object. As a result, knowing the current state and the event allows the action handler to be retrieved
* and the resulting state to be set on the successful completion of the handler
*/
private void populateFSMtable() {
if (DEBUG_LOGGING) {
Log.d(LOG_TAG, "Creating FSM lookup table");
}
this.fsmLookupTable.put(generateKey(ConnectionState.Disconnected, ConnectionEvent.Start),
new StateEventAction(ConnectionAction.startConnection, ConnectionState.WaitForNetwork));
this.fsmLookupTable.put(generateKey(ConnectionState.WaitForNetwork, ConnectionEvent.CheckForConnectivity),
new StateEventAction(ConnectionAction.verifyNetworkConnectivity, ConnectionState.ValidNetwork));
this.fsmLookupTable.put(generateKey(ConnectionState.WaitForNetwork, ConnectionEvent.NoNetworkFound),
new StateEventAction(ConnectionAction.cleanupConnection, ConnectionState.Disconnected));
this.fsmLookupTable.put(generateKey(ConnectionState.ValidNetwork, ConnectionEvent.AttemptConnection),
new StateEventAction(ConnectionAction.initialiseConnection, ConnectionState.WaitingToConnect));
this.fsmLookupTable.put(generateKey(ConnectionState.WaitingToConnect, ConnectionEvent.AttemptConnectionSuccess),
new StateEventAction(ConnectionAction.verifyAuthenticatedConnection, ConnectionState.Connected));
this.fsmLookupTable.put(generateKey(ConnectionState.WaitingToConnect, ConnectionEvent.AttemptConnectionFailure),
new StateEventAction(ConnectionAction.cleanupConnection, ConnectionState.Disconnected));
this.fsmLookupTable.put(generateKey(ConnectionState.Connected, ConnectionEvent.Disconnect),
new StateEventAction(ConnectionAction.cleanupConnection, ConnectionState.Disconnected));
this.fsmLookupTable.put(generateKey(ConnectionState.Connected, ConnectionEvent.ConnectionBroken),
new StateEventAction(ConnectionAction.manualReconnection, ConnectionState.Disconnected));
this.fsmLookupTable.put(generateKey(ConnectionState.Connected, ConnectionEvent.AttemptAutoReconnection),
new StateEventAction(ConnectionAction.updateStatus, ConnectionState.ReConnecting));
this.fsmLookupTable.put(generateKey(ConnectionState.ReConnecting, ConnectionEvent.AutoReconnected),
new StateEventAction(ConnectionAction.updateStatus, ConnectionState.Connected));
// this.fsmLookupTable.put(this.generateKey(ConnectionState.???, ConnectionEvent.xxx),
// new StateEventAction(ConnectionAction.zzz, ConnectionState.fff));
//Log the lookup table
// StateEventAction eventAction = null;
// for (String key: this.fsmLookupTable.keySet()) {
// eventAction = this.fsmLookupTable.get(key);
// Log.d(LOG_TAG, "Key: " + key + " Action: " + eventAction.getActionHandler() + "Next State: " + eventAction.getFutureState().name());
// }
}
private void addEventToQueue(ConnectionEvent event) {
if (DEBUG_LOGGING) {
Log.d(LOG_TAG, "addEventToQueue: " + event.name());
}
this.eventQueue.add(event);
}
private void processQueueEvent(ConnectionEvent event) {
if (DEBUG_LOGGING) {
Log.d(LOG_TAG, "processQueueEvent for state: " + this.currentState.name() + " and event: " + event.name());
}
StateEventAction object = this.fsmLookupTable.get(generateKey(this.currentState, event));
if (null != object) {
String handlerName = object.getActionHandler().name();
if (null != handlerName && handlerName.length() > 0) {
try {
Method actionHandler = this.getClass().getMethod(object.getActionHandler().name(), this.actionHandlerParams);
Object params[] = {object.getFutureState()};
try {
actionHandler.invoke(this, params);
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} catch (SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchMethodException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
/**
* FSM action handlers
*/
/**
* Get things rolling. Declared as public to allow reflection.
*/
public void startConnection(ConnectionState futureState) {
if (DEBUG_LOGGING) {
Log.d(LOG_TAG, "startConnection: " + futureState.name());
}
this.updateStatus(futureState);
this.addEventToQueue(ConnectionEvent.CheckForConnectivity);
}
/**
* Create the XMPP connection and authenticate
*
* @return true if connection and authentication successful
* @throws XMPPException
*/
private void authenticateConnection() throws XMPPException {
Dbc.invariant("XMPP connection cannot be null", null != this.xmppConnection);
if (DEBUG_LOGGING) {
Log.d(LOG_TAG, "authenticateConnection");
}
if (!(this.xmppConnection.isConnected() && this.xmppConnection.isAuthenticated())) {
this.xmppConnection.connect();
this.xmppConnection.login(this.newXmppConnectProps.getUserName(),
this.newXmppConnectProps.getPassword(),
this.newXmppConnectProps.getNodeResource());
}
}
/**
* Restore connection
*
* @return true if connection restored
* @throws XMPPException
*/
private void restoreConnection() throws XMPPException {
Dbc.invariant("XMPP connection cannot be null", null != this.xmppConnection);
if (DEBUG_LOGGING) {
Log.d(LOG_TAG, "restoreConnection");
}
this.xmppConnection.connect();
}
/**
* Verify that the established authenticated connection is in a connected state
* and add a {@link ConnectionListener} to monitor the connection.
* Declared as public to allow reflection.
*
* @param futureState
*/
public void verifyAuthenticatedConnection(ConnectionState futureState) {
if (DEBUG_LOGGING) {
Log.d(LOG_TAG, "verifyAuthenticatedConnection : " + futureState.name());
}
if (this.xmppConnection.isConnected() && this.xmppConnection.isAuthenticated()) {
this.setupBroadcastReceiver();
this.createConnectionListener();
this.updateStatus(futureState);
this.stopReconnectionTask();
} else {
this.addEventToQueue(ConnectionEvent.AttemptConnectionFailure);
this.sendLoginFailureIntent(IConnectionState.XMPP_AUTHENTICATION_FAILURE, AUTHENTICATION_FAILURE_MESSAGE);
}
}
/**
* Initialise the XMPPConnection. Declared as public to allow reflection.
*
* @param futureState
*/
public void initialiseConnection(ConnectionState futureState) {
if (DEBUG_LOGGING) {
Log.d(LOG_TAG, "initialiseConnection : " + futureState.name());
}
this.updateStatus(futureState);
if (!this.currentXmppConnectProps.equals(this.newXmppConnectProps) ||
null == this.xmppConnection) {
this.xmppConnection = createNewXMPPConnection(this.newXmppConnectProps);
}
try {
if (this.reconnectionAttempts > 0) {
this.restoreConnection();
} else {
this.authenticateConnection();
}
this.addEventToQueue(ConnectionEvent.AttemptConnectionSuccess);
} catch (XMPPException x) {
if (DEBUG_LOGGING) {
Log.d(LOG_TAG, "Unable to establish XMPP connection");
}
if (x.getMessage().contains(XMPP_AUTHENICATION_EXCEPTION_ID)) {
this.sendLoginFailureIntent(IConnectionState.XMPP_AUTHENTICATION_FAILURE, IConnectionState.AUTHENTICATION_FAILURE_MESSAGE);
} else {
this.sendLoginFailureIntent(IConnectionState.XMPP_CONNECTIVITY_FAILURE, CONNECTIVITY_FAILURE_MESSAGE);
}
this.addEventToQueue(ConnectionEvent.AttemptConnectionFailure);
} catch (Exception e) {
if (DEBUG_LOGGING) {
Log.d(LOG_TAG, "Unable to establish XMPP connection");
}
this.sendLoginFailureIntent(IConnectionState.XMPP_CONNECTIVITY_FAILURE, CONNECTIVITY_FAILURE_MESSAGE);
this.addEventToQueue(ConnectionEvent.AttemptConnectionFailure);
}
}
/**
* Disconnect and destroy connection
*
* @param destroyConnection true if new connection required, false for reconnects
*/
private void teardownConnection(boolean destroyConnection) {
if (DEBUG_LOGGING) {
Log.d(LOG_TAG, "teardownConnection");
}
if (null != this.xmppConnection && this.xmppConnection.isConnected()) {
this.xmppConnection.disconnect();
if (null != this.connectionListener) {
this.xmppConnection.removeConnectionListener(this.connectionListener);
}
}
if (destroyConnection) {
if (DEBUG_LOGGING) {
Log.d(LOG_TAG, "teardownConnection - destroy connection");
}
this.xmppConnection = null;
}
}
/**
* Carry out clean up tasks required. Declared as public to allow reflection.
*
* @param futureState
*/
public void cleanupConnection(ConnectionState futureState) {
if (DEBUG_LOGGING) {
Log.d(LOG_TAG, "cleanupConnection: " + futureState.name());
Log.d(LOG_TAG, "Reconnection attempts: " + this.reconnectionAttempts);
}
this.stopExecutorTask();
this.teardownBroadcastReceiver();
if (this.reconnectionAttempts > 0) {
teardownConnection(false);
if (this.reconnectionAttempts < MANUAL_RECONNECTION_ATTEMPTS) {
this.reconnectionAttempts++;
this.updateStatus(futureState);
} else {
this.updateStatus(futureState);
this.stopReconnectionTask();
}
} else {
teardownConnection(true);
this.updateStatus(futureState);
}
}
public void manualReconnection(ConnectionState futureState) {
if (DEBUG_LOGGING) {
Log.d(LOG_TAG, "manualReconnection: " + futureState.name());
}
//initialise reconnection counter
this.reconnectionAttempts = 1;
//clean up the connection
this.cleanupConnection(futureState);
//create a reconnection scheduler
this.reconnectionScheduler();
}
/**
* Stop the event queue consumer executor thread
*/
private void stopExecutorTask() {
if (DEBUG_LOGGING) {
Log.d(LOG_TAG, "stopExecutorTask");
}
if (this.queueScheduler != null) {
queueScheduler.shutdown();
this.queueScheduler = null;
}
}
/**
* Stop the reconnection scheduler executor thread
*/
private void stopReconnectionTask() {
if (DEBUG_LOGGING) {
Log.d(LOG_TAG, "stopReconnectionTask");
}
if (this.connectionScheduler != null) {
this.reconnectionAttempts = 0;
this.connectionScheduler.shutdown();
this.connectionScheduler = null;
}
}
/**
* Verify that a network exists and is connected. Declared as public to allow reflection.
*
* @param futureState
*/
public void verifyNetworkConnectivity(ConnectionState futureState) {
if (DEBUG_LOGGING) {
Log.d(LOG_TAG, "verifyNetworkConnectivity: " + futureState.name());
}
ConnectivityManager cm =
(ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
if (null != activeNetwork && activeNetwork.getState().equals(NetworkInfo.State.CONNECTED)) {
this.updateStatus(futureState);
this.addEventToQueue(ConnectionEvent.AttemptConnection);
} else {
this.addEventToQueue(ConnectionEvent.NoNetworkFound);
this.sendLoginFailureIntent(IConnectionState.XMPP_NO_NETWORK_FOUND_FAILURE, NO_NETWORK_FOUND_MESSAGE);
}
}
/**
* Create a new XMPPConnection object according to the supplied connection configuration properties
*
* @param connectProps Connection configuration properties
* @return {@link XMPPConnection}
*/
private XMPPConnection createNewXMPPConnection(XMPPConnectionProperties connectProps) {
if (DEBUG_LOGGING) {
Log.d(LOG_TAG, "createNewXMPPConnection for : " + connectProps.toString());
}
XMPPConnection connection = null;
if (DEBUG_LOGGING) {
Log.d(LOG_TAG, "Using configuration: " + connectProps.toString());
}
ConnectionConfiguration config = new ConnectionConfiguration(connectProps.getValidHost(),
connectProps.getServicePort(),
connectProps.getServiceName());
if (DEBUG_LOGGING) {
Log.d(LOG_TAG, "Reconnection enabled : " + config.isReconnectionAllowed());
}
connection = new XMPPConnection(config);
if (connectProps.isDebug()) {
this.xmppConnection.addPacketListener(new PacketListener() {
public void processPacket(Packet packet) {
if (DEBUG_LOGGING) {
Log.d(LOG_TAG, "Packet received: " + packet.toXML());
}
}
}, new PacketFilter() {
public boolean accept(Packet packet) {
return true;
}
}
);
connection.addPacketSendingListener(new PacketListener() {
public void processPacket(Packet packet) {
if (DEBUG_LOGGING) {
Log.d(LOG_TAG, "Packet sent: " + packet.toXML());
}
}
}, new PacketFilter() {
public boolean accept(Packet packet) {
return true;
}
}
);
}
return connection;
}
/**
* Broadcast receiver to receive intent return values from ConnectionManager
* TODO: If base API increases extra notification information can be displayed
* in the more detailed notification style
*/
private class AndroidCommsReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (DEBUG_LOGGING) {
Log.d(LOG_TAG, "Received action: " + intent.getAction());
}
if (intent.getAction().equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
boolean unConnected = intent.getBooleanExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, false);
if (unConnected) {
ConnectivityManager cm =
(ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
String networkType = null;
if (null != activeNetwork) {
networkType = activeNetwork.getTypeName();
}
String reason = intent.getStringExtra(ConnectivityManager.EXTRA_REASON);
String extraInfo = intent.getStringExtra(ConnectivityManager.EXTRA_EXTRA_INFO);
boolean failover = intent.getBooleanExtra(ConnectivityManager.EXTRA_IS_FAILOVER, false);
addEventToQueue(ConnectionEvent.NoNetworkFound);
XMPPConnectionManager.this.sendLoginFailureIntent(IConnectionState.XMPP_NO_NETWORK_FOUND_FAILURE, NO_NETWORK_FOUND_MESSAGE);
}
}
}
}
/**
* Create a suitable intent filter
*
* @return IntentFilter
*/
private IntentFilter createIntentFilter() {
//register broadcast receiver to receive SocietiesEvents return values
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
return intentFilter;
}
/**
* Create a broadcast receiver
*
* @return the created broadcast receiver
*/
private void setupBroadcastReceiver() {
if (DEBUG_LOGGING) {
Log.d(LOG_TAG, "Set up connectivity changes broadcast receiver");
}
this.bReceiver = new AndroidCommsReceiver();
this.context.registerReceiver(this.bReceiver, createIntentFilter());
if (DEBUG_LOGGING) {
Log.d(LOG_TAG, "Register connectivity changes broadcast receiver");
}
}
/**
* Unregister the broadcast receiver
*/
private void teardownBroadcastReceiver() {
if (null != this.bReceiver) {
if (DEBUG_LOGGING) {
Log.d(LOG_TAG, "Tear down connectivity changes broadcast receiver");
}
this.context.unregisterReceiver(this.bReceiver);
this.bReceiver = null;
}
}
/**
* Manual re-connection manager
* This method creates a scheduler that will wake up periodically and kickstart the FSM
* to attempt a connection.
*/
private void reconnectionScheduler() {
if (DEBUG_LOGGING) {
Log.d(LOG_TAG, "reconnectionScheduler");
}
this.connectionScheduler = Executors.newSingleThreadScheduledExecutor();
final Runnable processQueue = new Runnable() {
@Override
public void run() {
if (DEBUG_LOGGING) {
Log.d(LOG_TAG, "re-connection attempt being started, attempt: " + XMPPConnectionManager.this.reconnectionAttempts);
}
XMPPConnectionManager.this.startEventProcessor();
XMPPConnectionManager.this.addEventToQueue(ConnectionEvent.Start);
}
};
final ScheduledFuture processQueueTimer = connectionScheduler.scheduleAtFixedRate(processQueue, MANUAL_RECONNECTION_DELAY, MANUAL_RECONNECTION_INTERVAL, TimeUnit.MILLISECONDS);
}
}