reworked account managment. now status display actually works

Daniel Gultsch created

Change summary

res/layout/account_row.xml                               |   6 
src/de/gultsch/chat/entities/Account.java                |  21 +
src/de/gultsch/chat/services/XmppConnectionService.java  | 162 +++++----
src/de/gultsch/chat/ui/ManageAccountActivity.java        | 108 ++++--
src/de/gultsch/chat/ui/OnAccountListChangedListener.java |   5 
src/de/gultsch/chat/xml/TagWriter.java                   |  51 ++
src/de/gultsch/chat/xmpp/OnStatusChanged.java            |   7 
src/de/gultsch/chat/xmpp/XmppConnection.java             |  85 +++-
8 files changed, 286 insertions(+), 159 deletions(-)

Detailed changes

res/layout/account_row.xml 🔗

@@ -35,16 +35,16 @@
                 android:layout_height="wrap_content"
                 android:text="Status: "
                 android:textStyle="bold"
-               	android:textSize="14sp" />
+               	android:textSize="16sp" />
 
             <TextView
                 android:id="@+id/account_status"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
                 android:textColor="#669900"
-                android:text="Online"
+                android:text="unknown"
                 android:textStyle="bold"
-                android:textSize="14sp"/>
+                android:textSize="16sp"/>
 
         </LinearLayout>
 

src/de/gultsch/chat/entities/Account.java 🔗

@@ -18,11 +18,20 @@ public class Account  extends AbstractEntity{
 	
 	public static final int OPTION_USETLS = 0;
 	
+	public static final int STATUS_OFFLINE = 0;
+	public static final int STATUS_ONLINE = 1;
+	public static final int STATUS_UNAUTHORIZED = 2;
+	public static final int STATUS_NOINTERNET = 3;
+	public static final int STATUS_TLS_ERROR = 4;
+	public static final int STATUS_SERVER_NOT_FOUND = 5;
+	
 	protected String username;
 	protected String server;
 	protected String password;
 	protected int options;
 	protected String rosterVersion;
+	protected String resource;
+	protected int status = 0;
 	
 	protected boolean online = false;
 	
@@ -70,8 +79,16 @@ public class Account  extends AbstractEntity{
 		this.password = password;
 	}
 	
-	public boolean isOnline() {
-		return online;
+	public void setStatus(int status) {
+		this.status = status;
+	}
+	
+	public int getStatus() {
+		return this.status;
+	}
+	
+	public void setResource(String resource) {
+		this.resource = resource;
 	}
 	
 	public String getJid() {

src/de/gultsch/chat/services/XmppConnectionService.java 🔗

@@ -1,6 +1,5 @@
 package de.gultsch.chat.services;
 
-import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Hashtable;
 import java.util.List;
@@ -10,7 +9,7 @@ import de.gultsch.chat.entities.Contact;
 import de.gultsch.chat.entities.Conversation;
 import de.gultsch.chat.entities.Message;
 import de.gultsch.chat.persistance.DatabaseBackend;
-import de.gultsch.chat.ui.ConversationActivity;
+import de.gultsch.chat.ui.OnAccountListChangedListener;
 import de.gultsch.chat.ui.OnConversationListChangedListener;
 import de.gultsch.chat.ui.OnRosterFetchedListener;
 import de.gultsch.chat.utils.UIHelper;
@@ -19,20 +18,15 @@ import de.gultsch.chat.xmpp.IqPacket;
 import de.gultsch.chat.xmpp.MessagePacket;
 import de.gultsch.chat.xmpp.OnIqPacketReceived;
 import de.gultsch.chat.xmpp.OnMessagePacketReceived;
+import de.gultsch.chat.xmpp.OnStatusChanged;
 import de.gultsch.chat.xmpp.XmppConnection;
-import android.R;
-import android.R.dimen;
 import android.app.NotificationManager;
-import android.app.PendingIntent;
 import android.app.Service;
 import android.content.Context;
 import android.content.Intent;
-import android.content.res.Resources;
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.PowerManager;
-import android.support.v4.app.NotificationCompat;
-import android.support.v4.app.TaskStackBuilder;
 import android.util.Log;
 
 public class XmppConnectionService extends Service {
@@ -48,6 +42,7 @@ public class XmppConnectionService extends Service {
 	private Hashtable<Account, XmppConnection> connections = new Hashtable<Account, XmppConnection>();
 
 	private OnConversationListChangedListener convChangedListener = null;
+	private OnAccountListChangedListener accountChangedListener = null;
 
 	private final IBinder mBinder = new XmppConnectionBinder();
 	private OnMessagePacketReceived messageListener = new OnMessagePacketReceived() {
@@ -79,6 +74,16 @@ public class XmppConnectionService extends Service {
 			}
 		}
 	};
+	private OnStatusChanged statusListener = new OnStatusChanged() {
+		
+		@Override
+		public void onStatusChanged(Account account) {
+			Log.d(LOGTAG,account.getJid()+" changed status to "+account.getStatus());
+			if (accountChangedListener != null) {
+				accountChangedListener.onAccountListChangedListener();
+			}
+		}
+	};
 
 	public class XmppConnectionBinder extends Binder {
 		public XmppConnectionService getService() {
@@ -88,15 +93,10 @@ public class XmppConnectionService extends Service {
 
 	@Override
 	public int onStartCommand(Intent intent, int flags, int startId) {
-		PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
 		for (Account account : accounts) {
 			if (!connections.containsKey(account)) {
-				XmppConnection connection = new XmppConnection(account, pm);
-				connection
-						.setOnMessagePacketReceivedListener(this.messageListener);
-				Thread thread = new Thread(connection);
-				thread.start();
-				this.connections.put(account, connection);
+				
+				this.connections.put(account, this.createConnection(account));
 			}
 		}
 		return START_STICKY;
@@ -107,6 +107,17 @@ public class XmppConnectionService extends Service {
 		databaseBackend = DatabaseBackend.getInstance(getApplicationContext());
 		this.accounts = databaseBackend.getAccounts();
 	}
+	
+	public XmppConnection createConnection(Account account) {
+		PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
+		XmppConnection connection = new XmppConnection(account, pm);
+		connection
+				.setOnMessagePacketReceivedListener(this.messageListener);
+		connection.setOnStatusChangedListener(this.statusListener );
+		Thread thread = new Thread(connection);
+		thread.start();
+		return connection;
+	}
 
 	@Override
 	public IBinder onBind(Intent intent) {
@@ -114,68 +125,49 @@ public class XmppConnectionService extends Service {
 	}
 
 	public void sendMessage(final Account account, final Message message) {
-		new Thread() {
-			@Override
-			public void run() {
-				Log.d(LOGTAG, "sending message for " + account.getJid()
-						+ " to: " + message.getCounterpart());
-				databaseBackend.createMessage(message);
-				MessagePacket packet = new MessagePacket();
-				packet.setType(MessagePacket.TYPE_CHAT);
-				packet.setTo(message.getCounterpart());
-				packet.setFrom(account.getJid());
-				packet.setBody(message.getBody());
-				try {
-					connections.get(account).sendMessagePacket(packet);
-					message.setStatus(Message.STATUS_SEND);
-					databaseBackend.updateMessage(message);
-				} catch (IOException e) {
-					Log.d(LOGTAG,
-							"io exception during send. message is in database. will try again later");
-				}
-			}
-		}.start();
+		Log.d(LOGTAG, "sending message for " + account.getJid() + " to: "
+				+ message.getCounterpart());
+		databaseBackend.createMessage(message);
+		MessagePacket packet = new MessagePacket();
+		packet.setType(MessagePacket.TYPE_CHAT);
+		packet.setTo(message.getCounterpart());
+		packet.setFrom(account.getJid());
+		packet.setBody(message.getBody());
+		connections.get(account).sendMessagePacket(packet);
+		message.setStatus(Message.STATUS_SEND);
+		databaseBackend.updateMessage(message);
 	}
 
 	public void getRoster(final Account account,
 			final OnRosterFetchedListener listener) {
-		new Thread() {
-			@Override
-			public void run() {
-				IqPacket iqPacket = new IqPacket(IqPacket.TYPE_GET);
-				Element query = new Element("query");
-				query.setAttribute("xmlns", "jabber:iq:roster");
-				query.setAttribute("ver", "");
-				iqPacket.addChild(query);
-				try {
-					connections.get(account).sendIqPacket(iqPacket,
-							new OnIqPacketReceived() {
+		IqPacket iqPacket = new IqPacket(IqPacket.TYPE_GET);
+		Element query = new Element("query");
+		query.setAttribute("xmlns", "jabber:iq:roster");
+		query.setAttribute("ver", "");
+		iqPacket.addChild(query);
+		connections.get(account).sendIqPacket(iqPacket,
+				new OnIqPacketReceived() {
 
-								@Override
-								public void onIqPacketReceived(Account account,
-										IqPacket packet) {
-									Element roster = packet.findChild("query");
-									List<Contact> contacts = new ArrayList<Contact>();
-									for (Element item : roster.getChildren()) {
-										String name = item.getAttribute("name");
-										String jid = item.getAttribute("jid");
-										if (name == null) {
-											name = jid.split("@")[0];
-										}
-										Contact contact = new Contact(account,
-												name, jid, null);
-										contacts.add(contact);
-									}
-									if (listener != null) {
-										listener.onRosterFetched(contacts);
-									}
-								}
-							});
-				} catch (IOException e) {
-					Log.d(LOGTAG, "io error during roster fetch");
-				}
-			}
-		}.start();
+					@Override
+					public void onIqPacketReceived(Account account,
+							IqPacket packet) {
+						Element roster = packet.findChild("query");
+						List<Contact> contacts = new ArrayList<Contact>();
+						for (Element item : roster.getChildren()) {
+							String name = item.getAttribute("name");
+							String jid = item.getAttribute("jid");
+							if (name == null) {
+								name = jid.split("@")[0];
+							}
+							Contact contact = new Contact(account, name, jid,
+									null);
+							contacts.add(contact);
+						}
+						if (listener != null) {
+							listener.onRosterFetched(contacts);
+						}
+					}
+				});
 	}
 
 	public void addConversation(Conversation conversation) {
@@ -249,14 +241,32 @@ public class XmppConnectionService extends Service {
 
 	public void createAccount(Account account) {
 		databaseBackend.createAccount(account);
+		this.accounts.add(account);
+		this.connections.put(account, this.createConnection(account));
+		if (accountChangedListener!=null) accountChangedListener.onAccountListChangedListener();
 	}
 
 	public void updateAccount(Account account) {
 		databaseBackend.updateAccount(account);
+		XmppConnection connection = this.connections.get(account);
+		if (connection != null) {
+			connection.disconnect();
+			this.connections.remove(account);
+		}
+		this.connections.put(account, this.createConnection(account));
+		if (accountChangedListener!=null) accountChangedListener.onAccountListChangedListener();
 	}
 
 	public void deleteAccount(Account account) {
+		Log.d(LOGTAG,"called delete account");
+		if (this.connections.containsKey(account)) {
+			Log.d(LOGTAG,"found connection. disconnecting");
+			this.connections.get(account).disconnect();
+			this.connections.remove(account);
+			this.accounts.remove(account);
+		}
 		databaseBackend.deleteAccount(account);
+		if (accountChangedListener!=null) accountChangedListener.onAccountListChangedListener();
 	}
 
 	public void setOnConversationListChangedListener(
@@ -267,4 +277,12 @@ public class XmppConnectionService extends Service {
 	public void removeOnConversationListChangedListener() {
 		this.convChangedListener = null;
 	}
+	
+	public void setOnAccountListChangedListener(OnAccountListChangedListener listener) {
+		this.accountChangedListener = listener;
+	}
+	
+	public void removeOnAccountListChangedListener() {
+		this.accountChangedListener = null;
+	}
 }

src/de/gultsch/chat/ui/ManageAccountActivity.java 🔗

@@ -6,7 +6,6 @@ import java.util.List;
 import de.gultsch.chat.R;
 import de.gultsch.chat.entities.Account;
 import de.gultsch.chat.ui.EditAccount.EditAccountListener;
-import android.app.ActionBar;
 import android.app.Activity;
 import android.content.Context;
 import android.content.Intent;
@@ -25,28 +24,72 @@ import android.widget.TextView;
 
 public class ManageAccountActivity extends XmppActivity {
 
-	
 	protected List<Account> accountList = new ArrayList<Account>();
 	protected ListView accountListView;
 	protected ArrayAdapter<Account> accountListViewAdapter;
-	
+	protected OnAccountListChangedListener accountChanged = new OnAccountListChangedListener() {
+
+		@Override
+		public void onAccountListChangedListener() {
+			Log.d("xmppService", "ui on account list changed listener");
+			accountList.clear();
+			accountList.addAll(xmppConnectionService.getAccounts());
+			runOnUiThread(new Runnable() {
+
+				@Override
+				public void run() {
+					if (accountList.size() == 1) {
+						startActivity(new Intent(getApplicationContext(),
+								NewConversationActivity.class));
+					}
+					accountListViewAdapter.notifyDataSetChanged();
+				}
+			});
+		}
+	};
+
 	@Override
 	protected void onCreate(Bundle savedInstanceState) {
 
 		super.onCreate(savedInstanceState);
 
 		setContentView(R.layout.manage_accounts);
-		
+
 		accountListView = (ListView) findViewById(R.id.account_list);
-		accountListViewAdapter = new ArrayAdapter<Account>(getApplicationContext(), R.layout.account_row, this.accountList) {
+		accountListViewAdapter = new ArrayAdapter<Account>(
+				getApplicationContext(), R.layout.account_row, this.accountList) {
 			@Override
 			public View getView(int position, View view, ViewGroup parent) {
+				Account account = getItem(position);
 				if (view == null) {
 					LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
 					view = (View) inflater.inflate(R.layout.account_row, null);
 				}
-					((TextView) view.findViewById(R.id.account_jid)).setText(getItem(position).getJid());
-				
+				((TextView) view.findViewById(R.id.account_jid))
+						.setText(account.getJid());
+				TextView statusView = (TextView) view
+						.findViewById(R.id.account_status);
+				switch (account.getStatus()) {
+				case Account.STATUS_ONLINE:
+					statusView.setText("online");
+					statusView.setTextColor(0xFF83b600);
+					break;
+				case Account.STATUS_OFFLINE:
+					statusView.setText("offline");
+					statusView.setTextColor(0xFFe92727);
+					break;
+				case Account.STATUS_UNAUTHORIZED:
+					statusView.setText("unauthorized");
+					statusView.setTextColor(0xFFe92727);
+					break;
+				case Account.STATUS_SERVER_NOT_FOUND:
+					statusView.setText("server not found");
+					statusView.setTextColor(0xFFe92727);
+					break;
+				default:
+					break;
+				}
+
 				return view;
 			}
 		};
@@ -54,12 +97,12 @@ public class ManageAccountActivity extends XmppActivity {
 		accountListView.setOnItemClickListener(new OnItemClickListener() {
 
 			@Override
-			public void onItemClick(AdapterView<?> arg0, View view, int position,
-					long arg3) {
+			public void onItemClick(AdapterView<?> arg0, View view,
+					int position, long arg3) {
 				EditAccount dialog = new EditAccount();
 				dialog.setAccount(accountList.get(position));
 				dialog.setEditAccountListener(new EditAccountListener() {
-					
+
 					@Override
 					public void onAccountEdited(Account account) {
 						xmppConnectionService.updateAccount(account);
@@ -67,41 +110,27 @@ public class ManageAccountActivity extends XmppActivity {
 
 					@Override
 					public void onAccountDelete(Account account) {
-						
-						Log.d("gultsch","deleting account:"+account.getJid());
-						
 						xmppConnectionService.deleteAccount(account);
-						
-						//dont bother finding the right account in the frontend list. just reload
-						accountList.clear();
-						accountList.addAll(xmppConnectionService.getAccounts());
-						
-						accountListViewAdapter.notifyDataSetChanged();
-						
 					}
 				});
-				dialog.show(getFragmentManager(),"edit_account");
+				dialog.show(getFragmentManager(), "edit_account");
 			}
 		});
 	}
 
 	@Override
-	public void onStart() {
-		super.onStart();
+	protected void onStop() {
+		super.onStop();
 		if (xmppConnectionServiceBound) {
-			this.accountList.clear();
-			this.accountList.addAll(xmppConnectionService
-					.getAccounts());
-			accountListViewAdapter.notifyDataSetChanged();
-			if (this.accountList.size() == 0) {
-				getActionBar().setDisplayHomeAsUpEnabled(false);
-			}
+			xmppConnectionService.removeOnAccountListChangedListener();
+			unbindService(mConnection);
+			xmppConnectionServiceBound = false;
 		}
 	}
-	
+
 	@Override
 	void onBackendConnected() {
-		Log.d("gultsch","called on backend connected");
+		xmppConnectionService.setOnAccountListChangedListener(accountChanged);
 		this.accountList.clear();
 		this.accountList.addAll(xmppConnectionService.getAccounts());
 		accountListViewAdapter.notifyDataSetChanged();
@@ -110,14 +139,14 @@ public class ManageAccountActivity extends XmppActivity {
 			addAccount();
 		}
 	}
-	
+
 	@Override
 	public boolean onCreateOptionsMenu(Menu menu) {
 		// Inflate the menu; this adds items to the action bar if it is present.
 		getMenuInflater().inflate(R.menu.manageaccounts, menu);
 		return true;
 	}
-	
+
 	@Override
 	public boolean onOptionsItemSelected(MenuItem item) {
 		switch (item.getItemId()) {
@@ -137,23 +166,18 @@ public class ManageAccountActivity extends XmppActivity {
 		final Activity activity = this;
 		EditAccount dialog = new EditAccount();
 		dialog.setEditAccountListener(new EditAccountListener() {
-			
+
 			@Override
 			public void onAccountEdited(Account account) {
 				xmppConnectionService.createAccount(account);
-				accountList.add(account);
-				accountListViewAdapter.notifyDataSetChanged();
 				activity.getActionBar().setDisplayHomeAsUpEnabled(true);
-				if (accountList.size() == 1) {
-					activity.startActivity(new Intent(activity,NewConversationActivity.class));
-				}
 			}
 
 			@Override
 			public void onAccountDelete(Account account) {
-				//this will never be called
+				// this will never be called
 			}
 		});
-		dialog.show(getFragmentManager(),"add_account");
+		dialog.show(getFragmentManager(), "add_account");
 	}
 }

src/de/gultsch/chat/xml/TagWriter.java 🔗

@@ -3,12 +3,37 @@ package de.gultsch.chat.xml;
 import java.io.IOException;
 import java.io.OutputStream;
 import java.io.OutputStreamWriter;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
 
 import android.util.Log;
 
 public class TagWriter {
 	
-	OutputStreamWriter writer;
+	private OutputStreamWriter outputStream;
+	private LinkedBlockingQueue<String> writeQueue = new LinkedBlockingQueue<String>();
+	private Thread writer = new Thread() {
+		public boolean shouldStop = false;
+		@Override
+		public void run() {
+			while(!shouldStop) {
+				try {
+					String output = writeQueue.take();
+					outputStream.write(output);
+					outputStream.flush();
+				} catch (IOException e) {
+					Log.d("xmppService", "error writing to stream");
+				} catch (InterruptedException e) {
+					
+				}
+			}
+		}
+	};
+	
 	
 	public TagWriter() {
 		
@@ -16,31 +41,29 @@ public class TagWriter {
 	
 	public TagWriter(OutputStream out) {
 		this.setOutputStream(out);
+		writer.start();
 	}
 	
 	public void setOutputStream(OutputStream out) {
-		this.writer = new OutputStreamWriter(out);
+		this.outputStream = new OutputStreamWriter(out);
+		if (!writer.isAlive()) writer.start();
 	}
 	
-	public TagWriter beginDocument() throws IOException {
-		writer.write("<?xml version='1.0'?>");
+	public TagWriter beginDocument() {
+		writeQueue.add("<?xml version='1.0'?>");
 		return this;
 	}
 	
-	public TagWriter writeTag(Tag tag) throws IOException {
-		writer.write(tag.toString());
+	public TagWriter writeTag(Tag tag) {
+		writeQueue.add(tag.toString());
 		return this;
 	}
-	
-	public void flush() throws IOException {
-		writer.flush();
-	}
 
-	public void writeString(String string) throws IOException {
-		writer.write(string);
+	public void writeString(String string) {
+		writeQueue.add(string);
 	}
 
-	public void writeElement(Element element) throws IOException {
-		writer.write(element.toString());
+	public void writeElement(Element element) {
+		writeQueue.add(element.toString());
 	}
 }

src/de/gultsch/chat/xmpp/XmppConnection.java 🔗

@@ -39,7 +39,7 @@ public class XmppConnection implements Runnable {
 	private boolean isTlsEncrypted = false;
 	private boolean isAuthenticated = false;
 	//private boolean shouldUseTLS = false;
-	private boolean shouldReConnect = true;
+	private boolean shouldConnect = true;
 	private boolean shouldBind = true;
 	private boolean shouldAuthenticate = true;
 	private Element streamFeatures;
@@ -52,8 +52,7 @@ public class XmppConnection implements Runnable {
 	private OnPresencePacketReceived presenceListener = null;
 	private OnIqPacketReceived unregisteredIqListener = null;
 	private OnMessagePacketReceived messageListener = null;
-	
-	private String resource = null;
+	private OnStatusChanged statusListener = null;
 
 	public XmppConnection(Account account, PowerManager pm) {
 		this.account = account;
@@ -66,7 +65,6 @@ public class XmppConnection implements Runnable {
 	protected void connect() {
 		try {
 			socket = new Socket(account.getServer(), 5222);
-			Log.d(LOGTAG, "starting new socket");
 			OutputStream out = socket.getOutputStream();
 			tagWriter.setOutputStream(out);
 			InputStream in = socket.getInputStream();
@@ -77,40 +75,54 @@ public class XmppConnection implements Runnable {
 			while ((nextTag = tagReader.readTag()) != null) {
 				if (nextTag.isStart("stream")) {
 					processStream(nextTag);
+					break;
 				} else {
 					Log.d(LOGTAG, "found unexpected tag: " + nextTag.getName());
 					return;
 				}
 			}
+			if (socket.isConnected()) {
+				socket.close();
+			}
 		} catch (UnknownHostException e) {
-			Log.d(LOGTAG,account.getJid()+": error during connect. unknown host");
+			account.setStatus(Account.STATUS_SERVER_NOT_FOUND);
 			return;
 		} catch (IOException e) {
-			Log.d(LOGTAG, account.getJid()+": error during connect. io exception. falscher port?");
-			return;
+			if (shouldConnect) {
+				Log.d(LOGTAG,account.getJid()+": connection lost");
+				account.setStatus(Account.STATUS_OFFLINE);
+				if (statusListener!=null) {
+					statusListener.onStatusChanged(account);
+				}
+			}
 		} catch (XmlPullParserException e) {
 			Log.d(LOGTAG,"xml exception "+e.getMessage());
 			return;
 		}
+		
 	}
 
 	@Override
 	public void run() {
-		while(shouldReConnect) {
+		shouldConnect = true;
+		while(shouldConnect) {
 			connect();
 			try {
-				Thread.sleep(30000);
+				if (shouldConnect) {
+					Thread.sleep(30000);
+				}
 			} catch (InterruptedException e) {
 				// TODO Auto-generated catch block
 				e.printStackTrace();
 			}
 		}
+		Log.d(LOGTAG,"end run");
 	}
 
 	private void processStream(Tag currentTag) throws XmlPullParserException,
 			IOException {
-		Tag nextTag;
-		while (!(nextTag = tagReader.readTag()).isEnd("stream")) {
+		Tag nextTag = tagReader.readTag();
+		while ((nextTag != null) && (!nextTag.isEnd("stream"))) {
 			if (nextTag.isStart("error")) {
 				processStreamError(nextTag);
 			} else if (nextTag.isStart("features")) {
@@ -124,6 +136,12 @@ public class XmppConnection implements Runnable {
 				tagReader.reset();
 				sendStartStream();
 				processStream(tagReader.readTag());
+				break;
+			} else if(nextTag.isStart("failure")) {
+				Element failure = tagReader.readElement(nextTag);
+				Log.d(LOGTAG,"read failure element"+failure.toString());
+				account.setStatus(Account.STATUS_UNAUTHORIZED);
+				tagWriter.writeTag(Tag.end("stream"));
 			} else if (nextTag.isStart("iq")) {
 				processIq(nextTag);
 			} else if (nextTag.isStart("message")) {
@@ -134,6 +152,13 @@ public class XmppConnection implements Runnable {
 				Log.d(LOGTAG, "found unexpected tag: " + nextTag.getName()
 						+ " as child of " + currentTag.getName());
 			}
+			nextTag = tagReader.readTag();
+		}
+		if (account.getStatus() == Account.STATUS_ONLINE) {
+			account.setStatus(Account.STATUS_OFFLINE);
+			if (statusListener!=null) {
+				statusListener.onStatusChanged(account);
+			}
 		}
 	}
 	
@@ -190,11 +215,11 @@ public class XmppConnection implements Runnable {
 		}
 	}
 
-	private void sendStartTLS() throws XmlPullParserException, IOException {
+	private void sendStartTLS() {
 		Tag startTLS = Tag.empty("starttls");
 		startTLS.setAttribute("xmlns", "urn:ietf:params:xml:ns:xmpp-tls");
 		Log.d(LOGTAG,account.getJid()+": sending starttls");
-		tagWriter.writeTag(startTLS).flush();
+		tagWriter.writeTag(startTLS);
 	}
 
 	private void switchOverToTls(Tag currentTag) throws XmlPullParserException,
@@ -213,6 +238,7 @@ public class XmppConnection implements Runnable {
 			isTlsEncrypted = true;
 			sendStartStream();
 			processStream(tagReader.readTag());
+			sslSocket.close();
 		} catch (IOException e) {
 			Log.d(LOGTAG, account.getJid()+": error on ssl '" + e.getMessage()+"'");
 		}
@@ -227,7 +253,6 @@ public class XmppConnection implements Runnable {
 		auth.setContent(saslString);
 		Log.d(LOGTAG,account.getJid()+": sending sasl "+auth.toString());
 		tagWriter.writeElement(auth);
-		tagWriter.flush();
 	}
 
 	private void processStreamFeatures(Tag currentTag)
@@ -249,12 +274,10 @@ public class XmppConnection implements Runnable {
 				startSession.addChild(session);
 				sendIqPacket(startSession, null);
 				tagWriter.writeElement(startSession);
-				tagWriter.flush();
 			}
 			Element presence = new Element("presence");
 			
 			tagWriter.writeElement(presence);
-			tagWriter.flush();
 		}
 	}
 
@@ -266,8 +289,12 @@ public class XmppConnection implements Runnable {
 		this.sendIqPacket(iq, new OnIqPacketReceived() {	
 			@Override
 			public void onIqPacketReceived(Account account, IqPacket packet) {
-				resource = packet.findChild("bind").findChild("jid").getContent().split("/")[1];
-				Log.d(LOGTAG,account.getJid()+": new resource is "+resource);
+				String resource = packet.findChild("bind").findChild("jid").getContent().split("/")[1];
+				account.setResource(resource);
+				account.setStatus(Account.STATUS_ONLINE);
+				if (statusListener!=null) {
+					statusListener.onStatusChanged(account);
+				}
 			}
 		});
 	}
@@ -276,7 +303,7 @@ public class XmppConnection implements Runnable {
 		Log.d(LOGTAG, "processStreamError");
 	}
 
-	private void sendStartStream() throws IOException {
+	private void sendStartStream() {
 		Tag stream = Tag.start("stream");
 		stream.setAttribute("from", account.getJid());
 		stream.setAttribute("to", account.getServer());
@@ -284,32 +311,29 @@ public class XmppConnection implements Runnable {
 		stream.setAttribute("xml:lang", "en");
 		stream.setAttribute("xmlns", "jabber:client");
 		stream.setAttribute("xmlns:stream", "http://etherx.jabber.org/streams");
-		tagWriter.writeTag(stream).flush();
+		tagWriter.writeTag(stream);
 	}
 
 	private String nextRandomId() {
 		return new BigInteger(50, random).toString(32);
 	}
 	
-	public void sendIqPacket(IqPacket packet, OnIqPacketReceived callback) throws IOException {
+	public void sendIqPacket(IqPacket packet, OnIqPacketReceived callback) {
 		String id = nextRandomId();
 		packet.setAttribute("id",id);
 		tagWriter.writeElement(packet);
 		if (callback != null) {
 			iqPacketCallbacks.put(id, callback);
 		}
-		tagWriter.flush();
 		Log.d(LOGTAG,account.getJid()+": sending: "+packet.toString());
 	}
 	
-	public void sendMessagePacket(MessagePacket packet) throws IOException {
+	public void sendMessagePacket(MessagePacket packet){
 		tagWriter.writeElement(packet);
-		tagWriter.flush();
 	}
 	
-	public void sendPresencePacket(PresencePacket packet) throws IOException {
+	public void sendPresencePacket(PresencePacket packet)  {
 		tagWriter.writeElement(packet);
-		tagWriter.flush();
 	}
 	
 	public void setOnMessagePacketReceivedListener(OnMessagePacketReceived listener) {
@@ -323,4 +347,13 @@ public class XmppConnection implements Runnable {
 	public void setOnPresencePacketReceivedListener(OnPresencePacketReceived listener) {
 		this.presenceListener = listener;
 	}
+	
+	public void setOnStatusChangedListener(OnStatusChanged listener) {
+		this.statusListener = listener;
+	}
+
+	public void disconnect() {
+		shouldConnect = false;
+		tagWriter.writeTag(Tag.end("stream"));
+	}
 }