/***************************************************************************
* Copyright 2006-2016 by Christian Ihle *
* contact@kouchat.net *
* *
* This file is part of KouChat. *
* *
* KouChat is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Lesser General Public License as *
* published by the Free Software Foundation, either version 3 of *
* the License, or (at your option) any later version. *
* *
* KouChat 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 *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with KouChat. *
* If not, see <http://www.gnu.org/licenses/>. *
***************************************************************************/
package net.usikkert.kouchat.misc;
import java.util.List;
import net.usikkert.kouchat.Constants;
import net.usikkert.kouchat.autocomplete.AutoCompleter;
import net.usikkert.kouchat.autocomplete.CommandAutoCompleteList;
import net.usikkert.kouchat.autocomplete.UserAutoCompleteList;
import net.usikkert.kouchat.event.NetworkConnectionListener;
import net.usikkert.kouchat.jmx.JMXBeanLoader;
import net.usikkert.kouchat.message.CoreMessages;
import net.usikkert.kouchat.net.AsyncMessageResponderWrapper;
import net.usikkert.kouchat.net.DefaultMessageResponder;
import net.usikkert.kouchat.net.DefaultPrivateMessageResponder;
import net.usikkert.kouchat.net.FileReceiver;
import net.usikkert.kouchat.net.FileSender;
import net.usikkert.kouchat.net.FileToSend;
import net.usikkert.kouchat.net.MessageParser;
import net.usikkert.kouchat.net.MessageResponder;
import net.usikkert.kouchat.net.NetworkMessages;
import net.usikkert.kouchat.net.NetworkService;
import net.usikkert.kouchat.net.PrivateMessageParser;
import net.usikkert.kouchat.net.PrivateMessageResponder;
import net.usikkert.kouchat.net.TransferList;
import net.usikkert.kouchat.settings.Settings;
import net.usikkert.kouchat.settings.SettingsSaver;
import net.usikkert.kouchat.ui.UserInterface;
import net.usikkert.kouchat.util.DateTools;
import net.usikkert.kouchat.util.TimerTools;
import net.usikkert.kouchat.util.Tools;
import net.usikkert.kouchat.util.Validate;
/**
* This controller gives access to the network and the state of the
* application, like the user list and the topic.
* <br><br>
* When changing state, use the methods available <strong>here</strong> instead
* of doing it manually, to make sure the state is consistent.
* <br><br>
* To connect to the network, use {@link #logOn()}.
*
* @author Christian Ihle
*/
public class Controller implements NetworkConnectionListener {
/** The time to wait after the network is up before logon is set as completed. */
private static final int LOGON_DELAY = 1500;
private final DateTools dateTools = new DateTools();
private final TimerTools timerTools = new TimerTools();
private final ChatState chatState;
private final UserListController userListController;
private final NetworkService networkService;
private final NetworkMessages networkMessages;
private final IdleThread idleThread;
private final TransferList tList;
private final WaitingList wList;
private final User me;
private final UserInterface ui;
private final MessageController msgController;
private final Settings settings;
private final SettingsSaver settingsSaver;
private final DayTimer dayTimer;
private final Thread shutdownHook;
private final CoreMessages coreMessages;
private final ErrorHandler errorHandler;
/**
* Constructor. Initializes the controller.
*
* <p>Use {@link #start()} and {@link #logOn()} to connect to the network.</p>
*
* @param ui The active user interface object.
* @param settings The settings to use.
* @param settingsSaver The saver to use for storing settings.
* @param coreMessages The core messages to use.
* @param errorHandler The error handler to use.
*/
public Controller(final UserInterface ui, final Settings settings, final SettingsSaver settingsSaver,
final CoreMessages coreMessages, final ErrorHandler errorHandler) {
Validate.notNull(ui, "User interface can not be null");
Validate.notNull(settings, "Settings can not be null");
Validate.notNull(settingsSaver, "Settings saver can not be null");
Validate.notNull(coreMessages, "Core messages can not be null");
Validate.notNull(errorHandler, "Error handler can not be null");
this.ui = ui;
this.settings = settings;
this.settingsSaver = settingsSaver;
this.coreMessages = coreMessages;
this.errorHandler = errorHandler;
shutdownHook = new Thread("ControllerShutdownHook") {
@Override
public void run() {
logOff(false);
doShutdown();
}
};
Runtime.getRuntime().addShutdownHook(shutdownHook);
me = settings.getMe();
userListController = new UserListController(settings);
chatState = new ChatState();
tList = new TransferList();
wList = new WaitingList();
idleThread = new IdleThread(this, ui, settings);
dayTimer = new DayTimer(ui);
networkService = new NetworkService(settings, errorHandler);
final MessageResponder msgResponder = new DefaultMessageResponder(this, ui, settings, coreMessages);
final AsyncMessageResponderWrapper msgResponderWrapper = new AsyncMessageResponderWrapper(msgResponder, this);
final PrivateMessageResponder privmsgResponder = new DefaultPrivateMessageResponder(this, ui, settings);
final MessageParser msgParser = new MessageParser(msgResponderWrapper, settings);
networkService.registerMessageReceiverListener(msgParser);
final PrivateMessageParser privmsgParser = new PrivateMessageParser(privmsgResponder, settings);
networkService.registerUDPReceiverListener(privmsgParser);
networkMessages = new NetworkMessages(networkService, settings);
networkService.registerNetworkConnectionListener(this);
msgController = ui.getMessageController();
}
/**
* Starts background threads and shows welcome messages in the user interface.
*/
public void start() {
dayTimer.startTimer();
idleThread.start();
msgController.showSystemMessage(coreMessages.getMessage("core.startup.systemMessage.welcome",
Constants.APP_NAME, Constants.APP_VERSION));
final String date = dateTools.currentDateToString(coreMessages.getMessage("core.dateFormat.today"));
msgController.showSystemMessage(coreMessages.getMessage("core.startup.systemMessage.todayIs", date));
}
/**
* Gets the current topic.
*
* @return The current topic.
*/
public Topic getTopic() {
return chatState.getTopic();
}
/**
* Gets the list of online users.
*
* @return The user list.
*/
public UserList getUserList() {
return userListController.getUserList();
}
/**
* Returns if the application user wrote the last time
* {@link #changeWriting(int, boolean)} was called.
*
* @return If the user wrote.
* @see ChatState#isWrote()
*/
public boolean isWrote() {
return chatState.isWrote();
}
/**
* Updates the write state for the user. This is useful to see which
* users are currently writing.
*
* If the user is the application user, messages will be sent to the
* other clients to notify of changes.
*
* @param code The user code for the user to update.
* @param writing True if the user is writing.
*/
public void changeWriting(final int code, final boolean writing) {
userListController.changeWriting(code, writing);
if (code == me.getCode()) {
chatState.setWrote(writing);
if (writing) {
networkMessages.sendWritingMessage();
} else {
networkMessages.sendStoppedWritingMessage();
}
}
}
/**
* Updates whether the user is currently writing or not. This makes sure a star is shown
* by the nick name in the user list, and sends a notice to other users so they can show the same thing.
*
* @param isCurrentlyWriting If the application user is currently writing.
*/
public void updateMeWriting(final boolean isCurrentlyWriting) {
if (isCurrentlyWriting) {
if (!isWrote()) {
changeWriting(me.getCode(), true);
}
}
else {
if (isWrote()) {
changeWriting(me.getCode(), false);
}
}
}
/**
* Sets the application user as away with the specified away message.
*
* @param awayMessage The away message to use. Can not be empty.
* @throws CommandException If the away message is empty, or the application user could not be set as away.
*/
public void goAway(final String awayMessage) throws CommandException {
if (Tools.isEmpty(awayMessage)) {
throw new CommandException(coreMessages.getMessage("core.away.error.missingAwayMessage"));
}
changeAwayStatus(me.getCode(), true, awayMessage);
ui.changeAway(true);
msgController.showSystemMessage(coreMessages.getMessage("core.away.systemMessage.wentAway", me.getAwayMsg()));
}
/**
* Sets the application user as back from away.
*
* @throws CommandException If the application user could not be set as back from away.
*/
public void comeBack() throws CommandException {
changeAwayStatus(me.getCode(), false, "");
ui.changeAway(false);
msgController.showSystemMessage(coreMessages.getMessage("core.away.systemMessage.cameBack"));
}
/**
* Updates the away status and the away message for the user.
*
* @param code The user code for the user to update.
* @param away If the user is away or not.
* @param awaymsg The away message for that user. Will be trimmed.
* @throws CommandException If there is no connection to the network,
* or the user tries to set an away message that is to long.
*/
public void changeAwayStatus(final int code, final boolean away, final String awaymsg) throws CommandException {
if (code == me.getCode() && !isLoggedOn()) {
throw new CommandException(coreMessages.getMessage("core.away.error.notConnected"));
} else if (Tools.getBytes(awaymsg) > Constants.MESSAGE_MAX_BYTES) {
throw new CommandException(coreMessages.getMessage("core.away.error.awayMessageTooLong",
Constants.MESSAGE_MAX_BYTES));
}
final String trimmedAwayMessage = awaymsg.trim();
if (code == me.getCode()) {
if (away) {
networkMessages.sendAwayMessage(trimmedAwayMessage);
} else {
networkMessages.sendBackMessage();
}
}
userListController.changeAwayStatus(code, away, trimmedAwayMessage);
}
/**
* Checks if the nick is in use by another user.
*
* @param nick The nick to check.
* @return True if the nick is already in use.
*/
public boolean isNickInUse(final String nick) {
return userListController.isNickNameInUse(nick);
}
/**
* Checks if the user with that user code is already in the user list.
*
* @param code The user code of the user to check.
* @return True if the user is not in the user list.
*/
public boolean isNewUser(final int code) {
return userListController.isNewUser(code);
}
/**
* Changes the nick for the application user, sends a message over the
* network to notify the other clients of the change, and saves the changes.
*
* @param newNick The new nick for the application user.
* @throws CommandException If the user is away.
*/
public void changeMyNick(final String newNick) throws CommandException {
if (me.isAway()) {
throw new CommandException(coreMessages.getMessage("core.nick.error.meIsAway"));
}
networkMessages.sendNickMessage(newNick);
changeNick(me.getCode(), newNick);
saveSettings();
}
/**
* Changes the nick of the user.
*
* @param code The user code for the user.
* @param nick The new nick for the user.
*/
public void changeNick(final int code, final String nick) {
userListController.changeNickName(code, nick);
}
/**
* Saves the current settings.
*/
public void saveSettings() {
settingsSaver.saveSettings();
}
/**
* Gets the user with the specified user code.
*
* @param code The user code for the user.
* @return The user with the specified user code, or <em>null</em> if not found.
*/
public User getUser(final int code) {
return userListController.getUser(code);
}
/**
* Gets the user with the specified nick name.
*
* @param nick The nick name to check for.
* @return The user with the specified nick name, or <em>null</em> if not found.
*/
public User getUser(final String nick) {
return userListController.getUser(nick);
}
/**
* Sends the necessary network messages to log the user onto the network
* and query for the users and state.
*/
private void sendLogOn() {
networkMessages.sendLogonMessage();
networkMessages.sendClient();
networkMessages.sendExposeMessage();
networkMessages.sendGetTopicMessage();
}
/**
* This should be run after a successful logon, to update the connection state.
*/
private void runDelayedLogon() {
timerTools.scheduleTimerTask("DelayedLogonTimer", new DelayedLogonTask(networkService, chatState), LOGON_DELAY);
}
/**
* Logs this client onto the network.
*/
public void logOn() {
if (!networkService.isConnectionWorkerAlive()) {
networkService.connect();
}
}
/**
* Logs this client off the network.
*
* <br /><br />
*
* <strong>Note:</strong> removeUsers should not be true when called
* from a ShutdownHook, as that will lead to a deadlock. See
* http://bugs.sun.com/bugdatabase/view_bug.do;?bug_id=6261550 for details.
*
* @param removeUsers Set to true to remove users from the user list.
*/
public void logOff(final boolean removeUsers) {
networkMessages.sendLogoffMessage();
chatState.setLoggedOn(false);
chatState.setLogonCompleted(false);
networkService.disconnect();
getTopic().resetTopic();
if (removeUsers) {
removeAllUsers();
} else {
closeAllUserResources();
}
me.reset();
}
/**
* Cancels all file transfers, sets all users as logged off,
* and removes them from the user list.
*/
private void removeAllUsers() {
final UserList userList = getUserList();
for (int i = 0; i < userList.size(); i++) {
final User user = userList.get(i);
if (!user.isMe()) {
removeUser(user, coreMessages.getMessage("core.network.systemMessage.meLogOff"));
i--;
}
}
}
/**
* Removes a user from the user list and cleans up the state. This is done when a user logs off or times out.
*
* <p>All file transfers are cancelled, logs are closed, and private chats will be notified with a system message.</p>
*
* @param user The user to remove.
* @param privateSystemMessage The system message to show in the private chat window for that user.
*/
public void removeUser(final User user, final String privateSystemMessage) {
final UserList userList = getUserList();
user.setOnline(false);
cancelFileTransfers(user);
userList.remove(user);
if (user.getPrivchat() != null) {
msgController.showPrivateSystemMessage(user, privateSystemMessage);
user.getPrivchat().setLoggedOff();
}
closePrivateChatLogger(user);
}
private void closeAllUserResources() {
final UserList userList = getUserList();
for (int i = 0; i < userList.size(); i++) {
final User user = userList.get(i);
cancelFileTransfers(user);
closePrivateChatLogger(user);
}
}
private void closePrivateChatLogger(final User user) {
if (user.getPrivateChatLogger() != null) {
user.getPrivateChatLogger().close();
}
}
/**
* Cancels all file transfers for that user.
*
* @param user The user to cancel for.
*/
public void cancelFileTransfers(final User user) {
final List<FileSender> fsList = tList.getFileSenders(user);
final List<FileReceiver> frList = tList.getFileReceivers(user);
for (final FileSender fs : fsList) {
fs.cancel();
tList.removeFileSender(fs);
}
for (final FileReceiver fr : frList) {
fr.cancel();
tList.removeFileReceiver(fr);
}
}
/**
* Prepares the application for shutdown.
* Should <strong>only</strong> be called when the application shuts down.
*/
public void shutdown() {
doShutdown();
Runtime.getRuntime().removeShutdownHook(shutdownHook); // This throws exception if called from the shutdown hook
}
private void doShutdown() {
idleThread.stopThread();
dayTimer.stopTimer();
msgController.shutdown();
}
/**
* Sends a message over the network, asking the other clients to identify
* themselves.
*/
public void sendExposeMessage() {
networkMessages.sendExposeMessage();
}
/**
* Sends a message over the network to identify this client.
*/
public void sendExposingMessage() {
networkMessages.sendExposingMessage();
}
/**
* Sends a message over the network to ask for the current topic.
*/
public void sendGetTopicMessage() {
networkMessages.sendGetTopicMessage();
}
/**
* Sends a message over the network to notify other clients that this
* client is still alive.
*/
public void sendIdleMessage() {
if (isConnected()) {
networkMessages.sendIdleMessage();
}
}
/**
* Sends a chat message over the network, to all the other users.
*
* @param msg The message to send.
* @throws CommandException If there is no connection to the network,
* or the application user is away,
* or the message is empty,
* or the message is too long.
*/
public void sendChatMessage(final String msg) throws CommandException {
if (!isConnected()) {
throw new CommandException(coreMessages.getMessage("core.chatMessage.error.notConnected"));
} else if (me.isAway()) {
throw new CommandException(coreMessages.getMessage("core.chatMessage.error.meIsAway"));
} else if (msg.trim().length() == 0) {
throw new CommandException(coreMessages.getMessage("core.chatMessage.error.emptyMessage"));
} else if (Tools.getBytes(msg) > Constants.MESSAGE_MAX_BYTES) {
throw new CommandException(coreMessages.getMessage("core.chatMessage.error.messageTooLong",
Constants.MESSAGE_MAX_BYTES));
} else {
networkMessages.sendChatMessage(msg);
}
}
/**
* Sends a message over the network with the current topic.
*/
public void sendTopicRequestedMessage() {
networkMessages.sendTopicRequestedMessage(getTopic());
}
/**
* Changes the topic, and sends a notification to the other clients.
*
* @param newTopic The new topic to set.
* @throws CommandException If there is no connection to the network,
* or the application user is away,
* or the topic is too long.
*/
public void changeTopic(final String newTopic) throws CommandException {
if (!isLoggedOn()) {
throw new CommandException(coreMessages.getMessage("core.topic.error.notConnected"));
} else if (me.isAway()) {
throw new CommandException(coreMessages.getMessage("core.topic.error.meIsAway"));
} else if (Tools.getBytes(newTopic) > Constants.MESSAGE_MAX_BYTES) {
throw new CommandException(coreMessages.getMessage("core.topic.error.messageTooLong",
Constants.MESSAGE_MAX_BYTES));
}
final long time = System.currentTimeMillis();
final Topic newTopicObj = new Topic(newTopic, me.getNick(), time);
networkMessages.sendTopicChangeMessage(newTopicObj);
final Topic topic = getTopic();
topic.changeTopic(newTopicObj);
}
/**
* Sends a message over the network to notify the other clients that
* a client has tried to logon using the nick name of the
* application user.
*
* @param nick The nick that is already in use by the application user.
*/
public void sendNickCrashMessage(final String nick) {
networkMessages.sendNickCrashMessage(nick);
}
/**
* Sends a message over the network to notify the file sender that you
* aborted the file transfer.
*
* @param user The user sending a file.
* @param fileHash The unique hash code of the file.
* @param fileName The name of the file.
*/
public void sendFileAbort(final User user, final int fileHash, final String fileName) {
networkMessages.sendFileAbort(user, fileHash, fileName);
}
/**
* Sends a message over the network to notify the file sender that you
* accepted the file transfer.
*
* @param user The user sending a file.
* @param port The port the file sender can connect to on this client
* to start the file transfer.
* @param fileHash The unique hash code of the file.
* @param fileName The name of the file.
* @throws CommandException If the message was not sent successfully.
*/
public void sendFileAccept(final User user, final int port, final int fileHash, final String fileName) throws CommandException {
networkMessages.sendFileAccept(user, port, fileHash, fileName);
}
/**
* Sends a message over the network to notify another user that the
* application user wants to send a file.
*
* @param user The user asked to receive a file.
* @param file The file to send.
* @throws CommandException If the specified user is the application user,
* or there is no connection to the network,
* or the application user is away,
* or the specified user is away,
* or the file name is too long.
*/
public void sendFile(final User user, final FileToSend file) throws CommandException {
Validate.notNull(user, "User can not be null");
Validate.notNull(file, "File can not be null");
if (user.isMe()) {
throw new CommandException(coreMessages.getMessage("core.sendFile.error.isMe"));
} else if (!isConnected()) {
throw new CommandException(coreMessages.getMessage("core.sendFile.error.notConnected"));
} else if (me.isAway()) {
throw new CommandException(coreMessages.getMessage("core.sendFile.error.meIsAway"));
} else if (user.isAway()) {
throw new CommandException(coreMessages.getMessage("core.sendFile.error.userIsAway"));
} else if (Tools.getBytes(file.getName()) > Constants.MESSAGE_MAX_BYTES) {
throw new CommandException(coreMessages.getMessage("core.sendFile.error.messageTooLong",
Constants.MESSAGE_MAX_BYTES));
} else {
networkMessages.sendFile(user, file);
}
}
/**
* Gets the list of current transfers.
*
* @return The list of transfers.
*/
public TransferList getTransferList() {
return tList;
}
/**
* Gets the list of unidentified users.
*
* @return The list of unidentified users.
*/
public WaitingList getWaitingList() {
return wList;
}
/**
* If any users have timed out because of missed idle messages, then
* send a message over the network to ask all clients to identify
* themselves again.
*/
public void updateAfterTimeout() {
if (userListController.isTimeoutUsers()) {
networkMessages.sendExposeMessage();
}
}
/**
* Sends a message over the network with more information about this client.
*/
public void sendClientInfo() {
networkMessages.sendClient();
}
/**
* Sends a private chat message over the network, to the specified user.
*
* @param privmsg The private message to send.
* @param user The user to send the private message to.
* @throws CommandException If there is no connection to the network,
* or the application user is away,
* or the private message is empty,
* or the private message is too long,
* or the specified user has no port to send the private message to,
* or the specified user is away or offline.
*/
public void sendPrivateMessage(final String privmsg, final User user) throws CommandException {
if (!isConnected()) {
throw new CommandException(coreMessages.getMessage("core.privateChatMessage.error.notConnected"));
} else if (me.isAway()) {
throw new CommandException(coreMessages.getMessage("core.privateChatMessage.error.meIsAway"));
} else if (privmsg.trim().length() == 0) {
throw new CommandException(coreMessages.getMessage("core.privateChatMessage.error.emptyMessage"));
} else if (Tools.getBytes(privmsg) > Constants.MESSAGE_MAX_BYTES) {
throw new CommandException(coreMessages.getMessage("core.privateChatMessage.error.messageTooLong",
Constants.MESSAGE_MAX_BYTES));
} else if (user.getPrivateChatPort() == 0) {
throw new CommandException(coreMessages.getMessage("core.privateChatMessage.error.noPortNumber"));
} else if (user.isAway()) {
throw new CommandException(coreMessages.getMessage("core.privateChatMessage.error.userIsAway"));
} else if (!user.isOnline()) {
throw new CommandException(coreMessages.getMessage("core.privateChatMessage.error.userIsOffline"));
} else if (settings.isNoPrivateChat()) {
throw new CommandException(coreMessages.getMessage("core.privateChatMessage.error.privateChatDisabled"));
} else {
networkMessages.sendPrivateMessage(privmsg, user);
}
}
/**
* Updates if the user has unread private messages for the
* application user.
*
* @param code The user code for the user to update.
* @param newMsg True if the user has unread private messages.
*/
public void changeNewMessage(final int code, final boolean newMsg) {
userListController.changeNewMessage(code, newMsg);
}
/**
* Returns if the client is logged on to the chat and connected to the network.
*
* @return True if the client is connected.
*/
public boolean isConnected() {
return networkService.isNetworkUp() && isLoggedOn();
}
/**
* Checks the state of the network, and tries to keep the best possible
* network connection up.
*/
public void checkNetwork() {
networkService.checkNetwork();
}
/**
* Returns if the client is logged on to the chat.
*
* @return True if the client is logged on to the chat.
*/
public boolean isLoggedOn() {
return chatState.isLoggedOn();
}
/**
* Creates a new instance of the {@link AutoCompleter}, with
* a {@link CommandAutoCompleteList} and a {@link UserAutoCompleteList}.
*
* @return A new instance of a ready-to-use AutoCompleter.
*/
public AutoCompleter getAutoCompleter() {
final AutoCompleter autoCompleter = new AutoCompleter();
autoCompleter.addAutoCompleteList(new CommandAutoCompleteList());
autoCompleter.addAutoCompleteList(new UserAutoCompleteList(getUserList()));
return autoCompleter;
}
@Override
public void beforeNetworkCameUp() {
// Nothing to do here
}
/**
* Makes sure the application reacts when the network is available.
*
* @param silent If true, wont show the "you are connected..." message to the user.
*/
@Override
public void networkCameUp(final boolean silent) {
// Network came up after a logon
if (!isLoggedOn()) {
runDelayedLogon();
sendLogOn();
}
// Network came up after a timeout
else {
ui.showTopic();
if (!silent) {
msgController.showSystemMessage(coreMessages.getMessage("core.network.systemMessage.connectionBack"));
}
networkMessages.sendTopicRequestedMessage(getTopic());
networkMessages.sendExposingMessage();
networkMessages.sendGetTopicMessage();
networkMessages.sendExposeMessage();
networkMessages.sendIdleMessage();
}
}
/**
* Makes sure the application reacts when the network is unavailable.
*
* @param silent If true, wont show the "you lost contact..." message to the user.
*/
@Override
public void networkWentDown(final boolean silent) {
ui.showTopic();
if (isLoggedOn()) {
if (!silent) {
msgController.showSystemMessage(coreMessages.getMessage("core.network.systemMessage.connectionLost"));
}
}
else {
msgController.showSystemMessage(coreMessages.getMessage("core.network.systemMessage.meLogOff"));
}
}
/**
* Gets the chat state.
*
* @return The chat state.
*/
public ChatState getChatState() {
return chatState;
}
/**
* Creates an instance of a JMX bean loader, and returns it.
*
* @return A JMX bean loader.
*/
public JMXBeanLoader createJMXBeanLoader() {
return new JMXBeanLoader(this, networkService.getConnectionWorker(), settings, errorHandler);
}
public void registerNetworkConnectionListener(final NetworkConnectionListener listener) {
networkService.registerNetworkConnectionListener(listener);
}
}