/*
* Copyright (C) 2010 Moduad Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.androidpn.client;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.Future;
import org.jivesoftware.smack.ConnectionConfiguration;
import org.jivesoftware.smack.ConnectionListener;
import org.jivesoftware.smack.PacketListener;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode;
import org.jivesoftware.smack.filter.AndFilter;
import org.jivesoftware.smack.filter.PacketFilter;
import org.jivesoftware.smack.filter.PacketIDFilter;
import org.jivesoftware.smack.filter.PacketTypeFilter;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.packet.Packet;
import org.jivesoftware.smack.packet.Registration;
import org.jivesoftware.smack.provider.ProviderManager;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.os.Handler;
import android.util.Log;
/**
* This class is to manage the XMPP connection between client and server.
*
* @author Sehwan Noh (devnoh@gmail.com)
*/
public class XmppManager {
private static final String LOGTAG = LogUtil.makeLogTag(XmppManager.class);
private static final String XMPP_RESOURCE_NAME = "AndroidpnClient";
private Context context;
private NotificationService.TaskSubmitter taskSubmitter;
private NotificationService.TaskTracker taskTracker;
private SharedPreferences sharedPrefs;
private String xmppHost;
private int xmppPort;
private XMPPConnection connection;
private String username;
private String password;
private ConnectionListener connectionListener;
private PacketListener notificationPacketListener;
private Handler handler;
private List<Runnable> taskList;
private boolean running = false;
private Future<?> futureTask;
private Thread reconnection;
public XmppManager(NotificationService notificationService) {
context = notificationService;
taskSubmitter = notificationService.getTaskSubmitter();
taskTracker = notificationService.getTaskTracker();
sharedPrefs = notificationService.getSharedPreferences();
xmppHost = sharedPrefs.getString(Constants.XMPP_HOST, "localhost");
xmppPort = sharedPrefs.getInt(Constants.XMPP_PORT, 5222);
username = sharedPrefs.getString(Constants.XMPP_USERNAME, "");
password = sharedPrefs.getString(Constants.XMPP_PASSWORD, "");
connectionListener = new PersistentConnectionListener(this);
notificationPacketListener = new NotificationPacketListener(this);
handler = new Handler();
taskList = new ArrayList<Runnable>();
reconnection = new ReconnectionThread(this);
}
public Context getContext() {
return context;
}
public void connect() {
Log.d(LOGTAG, "connect()...");
submitLoginTask();
}
public void disconnect() {
Log.d(LOGTAG, "disconnect()...");
terminatePersistentConnection();
}
public void terminatePersistentConnection() {
Log.d(LOGTAG, "terminatePersistentConnection()...");
Runnable runnable = new Runnable() {
final XmppManager xmppManager = XmppManager.this;
public void run() {
if (xmppManager.isConnected()) {
Log.d(LOGTAG, "terminatePersistentConnection()... run()");
xmppManager.getConnection().removePacketListener(
xmppManager.getNotificationPacketListener());
xmppManager.getConnection().disconnect();
}
xmppManager.runTask();
}
};
addTask(runnable);
}
public XMPPConnection getConnection() {
return connection;
}
public void setConnection(XMPPConnection connection) {
this.connection = connection;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public ConnectionListener getConnectionListener() {
return connectionListener;
}
public PacketListener getNotificationPacketListener() {
return notificationPacketListener;
}
public void startReconnectionThread() {
synchronized (reconnection) {
if (!reconnection.isAlive()) {
reconnection.setName("Xmpp Reconnection Thread");
reconnection.start();
}
}
}
public Handler getHandler() {
return handler;
}
public void reregisterAccount() {
removeAccount();
submitLoginTask();
runTask();
}
public List<Runnable> getTaskList() {
return taskList;
}
public Future<?> getFutureTask() {
return futureTask;
}
public void runTask() {
Log.d(LOGTAG, "runTask()...");
synchronized (taskList) {
running = false;
futureTask = null;
if (!taskList.isEmpty()) {
Runnable runnable = (Runnable) taskList.get(0);
taskList.remove(0);
running = true;
futureTask = taskSubmitter.submit(runnable);
if (futureTask == null) {
taskTracker.decrease();
}
}
}
taskTracker.decrease();
Log.d(LOGTAG, "runTask()...done");
}
private String newRandomUUID() {
String uuidRaw = UUID.randomUUID().toString();
return uuidRaw.replaceAll("-", "");
}
private boolean isConnected() {
return connection != null && connection.isConnected();
}
private boolean isAuthenticated() {
return connection != null && connection.isConnected()
&& connection.isAuthenticated();
}
private boolean isRegistered() {
return sharedPrefs.contains(Constants.XMPP_USERNAME)
&& sharedPrefs.contains(Constants.XMPP_PASSWORD);
}
private void submitConnectTask() {
Log.d(LOGTAG, "submitConnectTask()...");
addTask(new ConnectTask());
}
private void submitRegisterTask() {
Log.d(LOGTAG, "submitRegisterTask()...");
submitConnectTask();
addTask(new RegisterTask());
}
private void submitLoginTask() {
Log.d(LOGTAG, "submitLoginTask()...");
submitRegisterTask();
addTask(new LoginTask());
}
private void addTask(Runnable runnable) {
Log.d(LOGTAG, "addTask(runnable)...");
taskTracker.increase();
synchronized (taskList) {
if (taskList.isEmpty() && !running) {
running = true;
futureTask = taskSubmitter.submit(runnable);
if (futureTask == null) {
taskTracker.decrease();
}
} else {
taskList.add(runnable);
}
}
Log.d(LOGTAG, "addTask(runnable)... done");
}
private void removeAccount() {
Editor editor = sharedPrefs.edit();
editor.remove(Constants.XMPP_USERNAME);
editor.remove(Constants.XMPP_PASSWORD);
editor.commit();
}
/**
* A runnable task to connect the server.
*/
private class ConnectTask implements Runnable {
final XmppManager xmppManager;
private ConnectTask() {
this.xmppManager = XmppManager.this;
}
public void run() {
Log.i(LOGTAG, "ConnectTask.run()...");
if (!xmppManager.isConnected()) {
// Create the configuration for this new connection
ConnectionConfiguration connConfig = new ConnectionConfiguration(
xmppHost, xmppPort);
// connConfig.setSecurityMode(SecurityMode.disabled);
connConfig.setSecurityMode(SecurityMode.required);
connConfig.setSASLAuthenticationEnabled(false);
connConfig.setCompressionEnabled(false);
XMPPConnection connection = new XMPPConnection(connConfig);
xmppManager.setConnection(connection);
try {
// Connect to the server
connection.connect();
Log.i(LOGTAG, "XMPP connected successfully");
// packet provider
ProviderManager.getInstance().addIQProvider("notification",
"androidpn:iq:notification",
new NotificationIQProvider());
} catch (XMPPException e) {
Log.e(LOGTAG, "XMPP connection failed", e);
}
xmppManager.runTask();
} else {
Log.i(LOGTAG, "XMPP connected already");
xmppManager.runTask();
}
}
}
/**
* A runnable task to register a new user onto the server.
*/
private class RegisterTask implements Runnable {
final XmppManager xmppManager;
private RegisterTask() {
xmppManager = XmppManager.this;
}
public void run() {
Log.i(LOGTAG, "RegisterTask.run()...");
if (!xmppManager.isRegistered()) {
final String newUsername = newRandomUUID();
final String newPassword = newRandomUUID();
Registration registration = new Registration();
PacketFilter packetFilter = new AndFilter(new PacketIDFilter(
registration.getPacketID()), new PacketTypeFilter(
IQ.class));
PacketListener packetListener = new PacketListener() {
public void processPacket(Packet packet) {
Log.d("RegisterTask.PacketListener",
"processPacket().....");
Log.d("RegisterTask.PacketListener", "packet="
+ packet.toXML());
if (packet instanceof IQ) {
IQ response = (IQ) packet;
if (response.getType() == IQ.Type.ERROR) {
if (!response.getError().toString().contains(
"409")) {
Log.e(LOGTAG,
"Unknown error while registering XMPP account! "
+ response.getError()
.getCondition());
}
} else if (response.getType() == IQ.Type.RESULT) {
xmppManager.setUsername(newUsername);
xmppManager.setPassword(newPassword);
Log.d(LOGTAG, "username=" + newUsername);
Log.d(LOGTAG, "password=" + newPassword);
Editor editor = sharedPrefs.edit();
editor.putString(Constants.XMPP_USERNAME,
newUsername);
editor.putString(Constants.XMPP_PASSWORD,
newPassword);
editor.commit();
Log
.i(LOGTAG,
"Account registered successfully");
xmppManager.runTask();
}
}
}
};
connection.addPacketListener(packetListener, packetFilter);
registration.setType(IQ.Type.SET);
// registration.setTo(xmppHost);
// Map<String, String> attributes = new HashMap<String, String>();
// attributes.put("username", rUsername);
// attributes.put("password", rPassword);
// registration.setAttributes(attributes);
registration.addAttribute("username", newUsername);
registration.addAttribute("password", newPassword);
connection.sendPacket(registration);
} else {
Log.i(LOGTAG, "Account registered already");
xmppManager.runTask();
}
}
}
/**
* A runnable task to log into the server.
*/
private class LoginTask implements Runnable {
final XmppManager xmppManager;
private LoginTask() {
this.xmppManager = XmppManager.this;
}
public void run() {
Log.i(LOGTAG, "LoginTask.run()...");
if (!xmppManager.isAuthenticated()) {
Log.d(LOGTAG, "username=" + username);
Log.d(LOGTAG, "password=" + password);
try {
xmppManager.getConnection().login(
xmppManager.getUsername(),
xmppManager.getPassword(), XMPP_RESOURCE_NAME);
Log.d(LOGTAG, "Loggedn in successfully");
// connection listener
if (xmppManager.getConnectionListener() != null) {
xmppManager.getConnection().addConnectionListener(
xmppManager.getConnectionListener());
}
// packet filter
PacketFilter packetFilter = new PacketTypeFilter(
NotificationIQ.class);
// packet listener
PacketListener packetListener = xmppManager
.getNotificationPacketListener();
connection.addPacketListener(packetListener, packetFilter);
xmppManager.runTask();
} catch (XMPPException e) {
Log.e(LOGTAG, "LoginTask.run()... xmpp error");
Log.e(LOGTAG, "Failed to login to xmpp server. Caused by: "
+ e.getMessage());
String INVALID_CREDENTIALS_ERROR_CODE = "401";
String errorMessage = e.getMessage();
if (errorMessage != null
&& errorMessage
.contains(INVALID_CREDENTIALS_ERROR_CODE)) {
xmppManager.reregisterAccount();
return;
}
xmppManager.startReconnectionThread();
} catch (Exception e) {
Log.e(LOGTAG, "LoginTask.run()... other error");
Log.e(LOGTAG, "Failed to login to xmpp server. Caused by: "
+ e.getMessage());
xmppManager.startReconnectionThread();
}
} else {
Log.i(LOGTAG, "Logged in already");
xmppManager.runTask();
}
}
}
}