write contacts on system shutdown

Daniel Gultsch created

Change summary

AndroidManifest.xml                                            |   1 
src/eu/siacs/conversations/entities/Contact.java               |   1 
src/eu/siacs/conversations/persistance/DatabaseBackend.java    |   1 
src/eu/siacs/conversations/services/XmppConnectionService.java |  24 
src/eu/siacs/conversations/xmpp/XmppConnection.java            | 303 ++-
5 files changed, 198 insertions(+), 132 deletions(-)

Detailed changes

AndroidManifest.xml 🔗

@@ -32,6 +32,7 @@
             <intent-filter>
                 <action android:name="android.intent.action.BOOT_COMPLETED" />
                 <action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
+                <action android:name="android.intent.action.ACTION_SHUTDOWN" />
             </intent-filter>
         </receiver>
 

src/eu/siacs/conversations/entities/Contact.java 🔗

@@ -287,5 +287,6 @@ public class Contact {
 		public static final int PREEMPTIVE_GRANT = 4;
 		public static final int IN_ROSTER = 8;
 		public static final int PENDING_SUBSCRIPTION_REQUEST = 16;
+		public static final int DIRTY_PUSH = 32;
 	}
 }

src/eu/siacs/conversations/services/XmppConnectionService.java 🔗

@@ -508,8 +508,12 @@ public class XmppConnectionService extends Service {
 	@Override
 	public int onStartCommand(Intent intent, int flags, int startId) {
 		this.wakeLock.acquire();
-		if ((intent!=null)&&(intent.getAction()!=null)&&(intent.getAction().equals(ACTION_MERGE_PHONE_CONTACTS))) {
+		if ((intent!=null)&&(ACTION_MERGE_PHONE_CONTACTS.equals(intent.getAction()))) {
 			mergePhoneContactsWithRoster();
+			return START_STICKY;
+		} else if ((intent!=null)&&(Intent.ACTION_SHUTDOWN.equals(intent.getAction()))){
+			logoutAndSave();
+			return START_NOT_STICKY;
 		}
 		ConnectivityManager cm = (ConnectivityManager) getApplicationContext()
 				.getSystemService(Context.CONNECTIVITY_SERVICE);
@@ -617,10 +621,15 @@ public class XmppConnectionService extends Service {
 		for (Account account : accounts) {
 			databaseBackend.writeRoster(account.getRoster());
 			if (account.getXmppConnection() != null) {
-				disconnect(account, true);
+				disconnect(account, false);
 			}
 		}
+		Context context = getApplicationContext();
+		AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
+		Intent intent = new Intent(context, EventReceiver.class);
+		alarmManager.cancel(PendingIntent.getBroadcast(context, 0, intent, 0));
 		Log.d(LOGTAG,"good bye");
+		stopSelf();
 	}
 
 	protected void scheduleWakeupCall(int seconds, boolean ping) {
@@ -1193,10 +1202,15 @@ public class XmppConnectionService extends Service {
 	}
 	
 	public void pushContactToServer(Contact contact) {
-		IqPacket iq = new IqPacket(IqPacket.TYPE_SET);
-		iq.query("jabber:iq:roster").addChild(contact.asElement());
 		Account account = contact.getAccount();
-		account.getXmppConnection().sendIqPacket(iq, null);
+		if (account.getStatus() == Account.STATUS_ONLINE) {
+			IqPacket iq = new IqPacket(IqPacket.TYPE_SET);
+			iq.query("jabber:iq:roster").addChild(contact.asElement());
+			account.getXmppConnection().sendIqPacket(iq, null);
+			contact.resetOption(Contact.Options.DIRTY_PUSH);
+		} else {
+			contact.setOption(Contact.Options.DIRTY_PUSH);
+		}
 	}
 	
 	public void deleteContactOnServer(Contact contact) {

src/eu/siacs/conversations/xmpp/XmppConnection.java 🔗

@@ -77,15 +77,15 @@ public class XmppConnection implements Runnable {
 
 	private String streamId = null;
 	private int smVersion = 3;
-	
+
 	private int stanzasReceived = 0;
 	private int stanzasSent = 0;
-	
+
 	public long lastPaketReceived = 0;
 	public long lastPingSent = 0;
 	public long lastConnect = 0;
 	public long lastSessionStarted = 0;
-	
+
 	private int attempt = 0;
 
 	private static final int PACKET_IQ = 0;
@@ -103,13 +103,17 @@ public class XmppConnection implements Runnable {
 
 	public XmppConnection(Account account, PowerManager pm) {
 		this.account = account;
-		this.wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,account.getJid());
+		this.wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
+				account.getJid());
 		tagWriter = new TagWriter();
 	}
 
 	protected void changeStatus(int nextStatus) {
 		if (account.getStatus() != nextStatus) {
-			if ((nextStatus == Account.STATUS_OFFLINE)&&(account.getStatus() != Account.STATUS_CONNECTING)&&(account.getStatus() != Account.STATUS_ONLINE)&&(account.getStatus() != Account.STATUS_DISABLED)) {
+			if ((nextStatus == Account.STATUS_OFFLINE)
+					&& (account.getStatus() != Account.STATUS_CONNECTING)
+					&& (account.getStatus() != Account.STATUS_ONLINE)
+					&& (account.getStatus() != Account.STATUS_DISABLED)) {
 				return;
 			}
 			if (nextStatus == Account.STATUS_ONLINE) {
@@ -123,18 +127,19 @@ public class XmppConnection implements Runnable {
 	}
 
 	protected void connect() {
-		Log.d(LOGTAG,account.getJid()+ ": connecting");
+		Log.d(LOGTAG, account.getJid() + ": connecting");
 		lastConnect = SystemClock.elapsedRealtime();
 		this.attempt++;
 		try {
-			shouldAuthenticate = shouldBind = !account.isOptionSet(Account.OPTION_REGISTER);
+			shouldAuthenticate = shouldBind = !account
+					.isOptionSet(Account.OPTION_REGISTER);
 			tagReader = new XmlReader(wakeLock);
 			tagWriter = new TagWriter();
 			packetCallbacks.clear();
 			this.changeStatus(Account.STATUS_CONNECTING);
 			Bundle namePort = DNSHelper.getSRVRecord(account.getServer());
 			if ("timeout".equals(namePort.getString("error"))) {
-				Log.d(LOGTAG,account.getJid()+": dns timeout");
+				Log.d(LOGTAG, account.getJid() + ": dns timeout");
 				this.changeStatus(Account.STATUS_OFFLINE);
 				return;
 			}
@@ -144,12 +149,12 @@ public class XmppConnection implements Runnable {
 			if (srvRecordServer != null) {
 				if (srvIpServer != null) {
 					Log.d(LOGTAG, account.getJid() + ": using values from dns "
-						+ srvRecordServer + "[" + srvIpServer + "]:"
-						+ srvRecordPort);
+							+ srvRecordServer + "[" + srvIpServer + "]:"
+							+ srvRecordPort);
 					socket = new Socket(srvIpServer, srvRecordPort);
 				} else {
 					Log.d(LOGTAG, account.getJid() + ": using values from dns "
-						+ srvRecordServer + ":" + srvRecordPort);
+							+ srvRecordServer + ":" + srvRecordPort);
 					socket = new Socket(srvRecordServer, srvRecordPort);
 				}
 			} else {
@@ -229,8 +234,7 @@ public class XmppConnection implements Runnable {
 			} else if (nextTag.isStart("compressed")) {
 				switchOverToZLib(nextTag);
 			} else if (nextTag.isStart("success")) {
-				Log.d(LOGTAG, account.getJid()
-						+ ": logged in");
+				Log.d(LOGTAG, account.getJid() + ": logged in");
 				tagReader.readTag();
 				tagReader.reset();
 				sendStartStream();
@@ -242,17 +246,21 @@ public class XmppConnection implements Runnable {
 			} else if (nextTag.isStart("challenge")) {
 				String challange = tagReader.readElement(nextTag).getContent();
 				Element response = new Element("response");
-				response.setAttribute("xmlns", "urn:ietf:params:xml:ns:xmpp-sasl");
-				response.setContent(CryptoHelper.saslDigestMd5(account, challange));
+				response.setAttribute("xmlns",
+						"urn:ietf:params:xml:ns:xmpp-sasl");
+				response.setContent(CryptoHelper.saslDigestMd5(account,
+						challange));
 				tagWriter.writeElement(response);
 			} else if (nextTag.isStart("enabled")) {
 				this.stanzasSent = 0;
 				Element enabled = tagReader.readElement(nextTag);
 				if ("true".equals(enabled.getAttribute("resume"))) {
 					this.streamId = enabled.getAttribute("id");
-					Log.d(LOGTAG,account.getJid()+": stream managment("+smVersion+") enabled (resumable)");
+					Log.d(LOGTAG, account.getJid() + ": stream managment("
+							+ smVersion + ") enabled (resumable)");
 				} else {
-					Log.d(LOGTAG,account.getJid()+": stream managment("+smVersion+") enabled");
+					Log.d(LOGTAG, account.getJid() + ": stream managment("
+							+ smVersion + ") enabled");
 				}
 				this.lastSessionStarted = SystemClock.elapsedRealtime();
 				this.stanzasReceived = 0;
@@ -260,26 +268,26 @@ public class XmppConnection implements Runnable {
 				tagWriter.writeStanzaAsync(r);
 			} else if (nextTag.isStart("resumed")) {
 				lastPaketReceived = SystemClock.elapsedRealtime();
-				Log.d(LOGTAG,account.getJid()+": session resumed");
+				Log.d(LOGTAG, account.getJid() + ": session resumed");
 				tagReader.readElement(nextTag);
 				sendPing();
 				changeStatus(Account.STATUS_ONLINE);
 			} else if (nextTag.isStart("r")) {
 				tagReader.readElement(nextTag);
-				AckPacket ack = new AckPacket(this.stanzasReceived,smVersion);
-				//Log.d(LOGTAG,ack.toString());
+				AckPacket ack = new AckPacket(this.stanzasReceived, smVersion);
+				// Log.d(LOGTAG,ack.toString());
 				tagWriter.writeStanzaAsync(ack);
 			} else if (nextTag.isStart("a")) {
 				Element ack = tagReader.readElement(nextTag);
 				lastPaketReceived = SystemClock.elapsedRealtime();
 				int serverSequence = Integer.parseInt(ack.getAttribute("h"));
-				if (serverSequence>this.stanzasSent) {
+				if (serverSequence > this.stanzasSent) {
 					this.stanzasSent = serverSequence;
 				}
-				//Log.d(LOGTAG,"server ack"+ack.toString()+" ("+this.stanzasSent+")");
+				// Log.d(LOGTAG,"server ack"+ack.toString()+" ("+this.stanzasSent+")");
 			} else if (nextTag.isStart("failed")) {
 				tagReader.readElement(nextTag);
-				Log.d(LOGTAG,account.getJid()+": resumption failed");
+				Log.d(LOGTAG, account.getJid() + ": resumption failed");
 				streamId = null;
 				if (account.getStatus() != Account.STATUS_ONLINE) {
 					sendBindRequest();
@@ -325,7 +333,8 @@ public class XmppConnection implements Runnable {
 		while (!nextTag.isEnd(element.getName())) {
 			if (!nextTag.isNo()) {
 				Element child = tagReader.readElement(nextTag);
-				if ((packetType == PACKET_IQ)&&("jingle".equals(child.getName()))) {
+				if ((packetType == PACKET_IQ)
+						&& ("jingle".equals(child.getName()))) {
 					element = new JinglePacket();
 					element.setAttributes(currentTag.getAttributes());
 				}
@@ -341,14 +350,15 @@ public class XmppConnection implements Runnable {
 	private void processIq(Tag currentTag) throws XmlPullParserException,
 			IOException {
 		IqPacket packet = (IqPacket) processPacket(currentTag, PACKET_IQ);
-		
+
 		if (packet.getId() == null) {
-			return; //an iq packet without id is definitely invalid
+			return; // an iq packet without id is definitely invalid
 		}
-		
+
 		if (packet instanceof JinglePacket) {
-			if (this.jingleListener !=null) {
-				this.jingleListener.onJinglePacketReceived(account, (JinglePacket) packet);
+			if (this.jingleListener != null) {
+				this.jingleListener.onJinglePacketReceived(account,
+						(JinglePacket) packet);
 			}
 		} else {
 			if (packetCallbacks.containsKey(packet.getId())) {
@@ -356,7 +366,7 @@ public class XmppConnection implements Runnable {
 					((OnIqPacketReceived) packetCallbacks.get(packet.getId()))
 							.onIqPacketReceived(account, packet);
 				}
-	
+
 				packetCallbacks.remove(packet.getId());
 			} else if (this.unregisteredIqListener != null) {
 				this.unregisteredIqListener.onIqPacketReceived(account, packet);
@@ -403,15 +413,18 @@ public class XmppConnection implements Runnable {
 		tagWriter.writeElement(compress);
 	}
 
-	private void switchOverToZLib(Tag currentTag) throws XmlPullParserException,
-			IOException, NoSuchAlgorithmException {
+	private void switchOverToZLib(Tag currentTag)
+			throws XmlPullParserException, IOException,
+			NoSuchAlgorithmException {
 		tagReader.readTag(); // read tag close
 
-		tagWriter.setOutputStream(new ZLibOutputStream(tagWriter.getOutputStream()));
-		tagReader.setInputStream(new ZLibInputStream(tagReader.getInputStream()));
+		tagWriter.setOutputStream(new ZLibOutputStream(tagWriter
+				.getOutputStream()));
+		tagReader
+				.setInputStream(new ZLibInputStream(tagReader.getInputStream()));
 
 		sendStartStream();
-		Log.d(LOGTAG,account.getJid()+": compression enabled");
+		Log.d(LOGTAG, account.getJid() + ": compression enabled");
 		processStream(tagReader.readTag());
 	}
 
@@ -457,13 +470,15 @@ public class XmppConnection implements Runnable {
 						if (e.getCause() instanceof CertPathValidatorException) {
 							String sha;
 							try {
-								MessageDigest sha1 = MessageDigest.getInstance("SHA1");
+								MessageDigest sha1 = MessageDigest
+										.getInstance("SHA1");
 								sha1.update(chain[0].getEncoded());
 								sha = CryptoHelper.bytesToHex(sha1.digest());
 								if (!sha.equals(account.getSSLFingerprint())) {
 									changeStatus(Account.STATUS_TLS_ERROR);
-									if (tlsListener!=null) {
-										tlsListener.onTLSExceptionReceived(sha,account);
+									if (tlsListener != null) {
+										tlsListener.onTLSExceptionReceived(sha,
+												account);
 									}
 									throw new CertificateException();
 								}
@@ -486,12 +501,12 @@ public class XmppConnection implements Runnable {
 			sc.init(null, wrappedTrustManagers, null);
 			SSLSocketFactory factory = sc.getSocketFactory();
 			SSLSocket sslSocket = (SSLSocket) factory.createSocket(socket,
-						socket.getInetAddress().getHostAddress(), socket.getPort(),
-						true);
+					socket.getInetAddress().getHostAddress(), socket.getPort(),
+					true);
 			tagReader.setInputStream(sslSocket.getInputStream());
 			tagWriter.setOutputStream(sslSocket.getOutputStream());
 			sendStartStream();
-			Log.d(LOGTAG,account.getJid()+": TLS connection established");
+			Log.d(LOGTAG, account.getJid() + ": TLS connection established");
 			processStream(tagReader.readTag());
 			sslSocket.close();
 		} catch (NoSuchAlgorithmException e1) {
@@ -512,7 +527,7 @@ public class XmppConnection implements Runnable {
 		auth.setContent(saslString);
 		tagWriter.writeElement(auth);
 	}
-	
+
 	private void sendSaslAuthDigestMd5() throws IOException {
 		Element auth = new Element("auth");
 		auth.setAttribute("xmlns", "urn:ietf:params:xml:ns:xmpp-sasl");
@@ -528,21 +543,27 @@ public class XmppConnection implements Runnable {
 			sendStartTLS();
 		} else if (compressionAvailable()) {
 			sendCompressionZlib();
-		} else if (this.streamFeatures.hasChild("register")&&(account.isOptionSet(Account.OPTION_REGISTER))) {
-				sendRegistryRequest();
-		} else if (!this.streamFeatures.hasChild("register")&&(account.isOptionSet(Account.OPTION_REGISTER))) {
+		} else if (this.streamFeatures.hasChild("register")
+				&& (account.isOptionSet(Account.OPTION_REGISTER))) {
+			sendRegistryRequest();
+		} else if (!this.streamFeatures.hasChild("register")
+				&& (account.isOptionSet(Account.OPTION_REGISTER))) {
 			changeStatus(Account.STATUS_REGISTRATION_NOT_SUPPORTED);
 			disconnect(true);
 		} else if (this.streamFeatures.hasChild("mechanisms")
 				&& shouldAuthenticate) {
-			List<String> mechanisms = extractMechanisms( streamFeatures.findChild("mechanisms"));
+			List<String> mechanisms = extractMechanisms(streamFeatures
+					.findChild("mechanisms"));
 			if (mechanisms.contains("PLAIN")) {
 				sendSaslAuthPlain();
 			} else if (mechanisms.contains("DIGEST-MD5")) {
 				sendSaslAuthDigestMd5();
 			}
-		} else if (this.streamFeatures.hasChild("sm","urn:xmpp:sm:"+smVersion) && streamId != null) {
-			ResumePacket resume = new ResumePacket(this.streamId,stanzasReceived,smVersion);
+		} else if (this.streamFeatures.hasChild("sm", "urn:xmpp:sm:"
+				+ smVersion)
+				&& streamId != null) {
+			ResumePacket resume = new ResumePacket(this.streamId,
+					stanzasReceived, smVersion);
 			this.tagWriter.writeStanzaAsync(resume);
 		} else if (this.streamFeatures.hasChild("bind") && shouldBind) {
 			sendBindRequest();
@@ -550,13 +571,19 @@ public class XmppConnection implements Runnable {
 	}
 
 	private boolean compressionAvailable() {
-		if (!this.streamFeatures.hasChild("compression", "http://jabber.org/features/compress")) return false;
-		if (!ZLibOutputStream.SUPPORTED) return false;
-		if (!account.isOptionSet(Account.OPTION_USECOMPRESSION)) return false;
+		if (!this.streamFeatures.hasChild("compression",
+				"http://jabber.org/features/compress"))
+			return false;
+		if (!ZLibOutputStream.SUPPORTED)
+			return false;
+		if (!account.isOptionSet(Account.OPTION_USECOMPRESSION))
+			return false;
 
-		Element compression = this.streamFeatures.findChild("compression", "http://jabber.org/features/compress");
+		Element compression = this.streamFeatures.findChild("compression",
+				"http://jabber.org/features/compress");
 		for (Element child : compression.getChildren()) {
-			if (!"method".equals(child.getName())) continue;
+			if (!"method".equals(child.getName()))
+				continue;
 
 			if ("zlib".equalsIgnoreCase(child.getContent())) {
 				return true;
@@ -566,8 +593,9 @@ public class XmppConnection implements Runnable {
 	}
 
 	private List<String> extractMechanisms(Element stream) {
-		ArrayList<String> mechanisms = new ArrayList<String>(stream.getChildren().size());
-		for(Element child : stream.getChildren()) {
+		ArrayList<String> mechanisms = new ArrayList<String>(stream
+				.getChildren().size());
+		for (Element child : stream.getChildren()) {
 			mechanisms.add(child.getContent());
 		}
 		return mechanisms;
@@ -578,28 +606,35 @@ public class XmppConnection implements Runnable {
 		register.query("jabber:iq:register");
 		register.setTo(account.getServer());
 		sendIqPacket(register, new OnIqPacketReceived() {
-			
+
 			@Override
 			public void onIqPacketReceived(Account account, IqPacket packet) {
 				Element instructions = packet.query().findChild("instructions");
-				if (packet.query().hasChild("username")&&(packet.query().hasChild("password"))) {
+				if (packet.query().hasChild("username")
+						&& (packet.query().hasChild("password"))) {
 					IqPacket register = new IqPacket(IqPacket.TYPE_SET);
-					Element username = new Element("username").setContent(account.getUsername());
-					Element password = new Element("password").setContent(account.getPassword());
+					Element username = new Element("username")
+							.setContent(account.getUsername());
+					Element password = new Element("password")
+							.setContent(account.getPassword());
 					register.query("jabber:iq:register").addChild(username);
 					register.query().addChild(password);
 					sendIqPacket(register, new OnIqPacketReceived() {
-						
+
 						@Override
-						public void onIqPacketReceived(Account account, IqPacket packet) {
-							if (packet.getType()==IqPacket.TYPE_RESULT) {
-								account.setOption(Account.OPTION_REGISTER, false);
+						public void onIqPacketReceived(Account account,
+								IqPacket packet) {
+							if (packet.getType() == IqPacket.TYPE_RESULT) {
+								account.setOption(Account.OPTION_REGISTER,
+										false);
 								changeStatus(Account.STATUS_REGISTRATION_SUCCESSFULL);
-							} else if (packet.hasChild("error")&&(packet.findChild("error").hasChild("conflict"))){
+							} else if (packet.hasChild("error")
+									&& (packet.findChild("error")
+											.hasChild("conflict"))) {
 								changeStatus(Account.STATUS_REGISTRATION_CONFLICT);
 							} else {
 								changeStatus(Account.STATUS_REGISTRATION_FAILED);
-								Log.d(LOGTAG,packet.toString());
+								Log.d(LOGTAG, packet.toString());
 							}
 							disconnect(true);
 						}
@@ -607,7 +642,9 @@ public class XmppConnection implements Runnable {
 				} else {
 					changeStatus(Account.STATUS_REGISTRATION_FAILED);
 					disconnect(true);
-					Log.d(LOGTAG,account.getJid()+": could not register. instructions are"+instructions.getContent());
+					Log.d(LOGTAG, account.getJid()
+							+ ": could not register. instructions are"
+							+ instructions.getContent());
 				}
 			}
 		});
@@ -615,35 +652,37 @@ public class XmppConnection implements Runnable {
 
 	private void sendBindRequest() throws IOException {
 		IqPacket iq = new IqPacket(IqPacket.TYPE_SET);
-		iq.addChild("bind", "urn:ietf:params:xml:ns:xmpp-bind").addChild("resource").setContent(account.getResource());
+		iq.addChild("bind", "urn:ietf:params:xml:ns:xmpp-bind")
+				.addChild("resource").setContent(account.getResource());
 		this.sendUnboundIqPacket(iq, new OnIqPacketReceived() {
 			@Override
 			public void onIqPacketReceived(Account account, IqPacket packet) {
 				String resource = packet.findChild("bind").findChild("jid")
 						.getContent().split("/")[1];
 				account.setResource(resource);
-				if (streamFeatures.hasChild("sm","urn:xmpp:sm:3")) {
+				if (streamFeatures.hasChild("sm", "urn:xmpp:sm:3")) {
 					smVersion = 3;
 					EnablePacket enable = new EnablePacket(smVersion);
 					tagWriter.writeStanzaAsync(enable);
-				} else if (streamFeatures.hasChild("sm","urn:xmpp:sm:2")) {
+				} else if (streamFeatures.hasChild("sm", "urn:xmpp:sm:2")) {
 					smVersion = 2;
 					EnablePacket enable = new EnablePacket(smVersion);
 					tagWriter.writeStanzaAsync(enable);
 				}
 				sendServiceDiscoveryInfo(account.getServer());
 				sendServiceDiscoveryItems(account.getServer());
-				if (bindListener !=null) {
+				if (bindListener != null) {
 					bindListener.onBind(account);
 				}
-				
+
 				changeStatus(Account.STATUS_ONLINE);
 			}
 		});
 		if (this.streamFeatures.hasChild("session")) {
-			Log.d(LOGTAG,account.getJid()+": sending deprecated session");
+			Log.d(LOGTAG, account.getJid() + ": sending deprecated session");
 			IqPacket startSession = new IqPacket(IqPacket.TYPE_SET);
-			startSession.addChild("session","urn:ietf:params:xml:ns:xmpp-session");
+			startSession.addChild("session",
+					"urn:ietf:params:xml:ns:xmpp-session");
 			this.sendUnboundIqPacket(startSession, null);
 		}
 	}
@@ -660,25 +699,24 @@ public class XmppConnection implements Runnable {
 				List<String> features = new ArrayList<String>();
 				for (int i = 0; i < elements.size(); ++i) {
 					if (elements.get(i).getName().equals("feature")) {
-						features.add(elements.get(i).getAttribute(
-								"var"));
+						features.add(elements.get(i).getAttribute("var"));
 					}
 				}
 				disco.put(server, features);
-				
+
 				if (account.getServer().equals(server)) {
 					enableAdvancedStreamFeatures();
 				}
 			}
 		});
 	}
-	
+
 	private void enableAdvancedStreamFeatures() {
 		if (hasFeaturesCarbon()) {
 			sendEnableCarbons();
 		}
 	}
-	
+
 	private void sendServiceDiscoveryItems(final String server) {
 		IqPacket iq = new IqPacket(IqPacket.TYPE_GET);
 		iq.setTo(server);
@@ -690,8 +728,7 @@ public class XmppConnection implements Runnable {
 				List<Element> elements = packet.query().getChildren();
 				for (int i = 0; i < elements.size(); ++i) {
 					if (elements.get(i).getName().equals("item")) {
-						String jid = elements.get(i).getAttribute(
-								"jid");
+						String jid = elements.get(i).getAttribute("jid");
 						sendServiceDiscoveryInfo(jid);
 					}
 				}
@@ -701,7 +738,7 @@ public class XmppConnection implements Runnable {
 
 	private void sendEnableCarbons() {
 		IqPacket iq = new IqPacket(IqPacket.TYPE_SET);
-		iq.addChild("enable","urn:xmpp:carbons:2");
+		iq.addChild("enable", "urn:xmpp:carbons:2");
 		this.sendIqPacket(iq, new OnIqPacketReceived() {
 
 			@Override
@@ -737,16 +774,16 @@ public class XmppConnection implements Runnable {
 	}
 
 	public void sendIqPacket(IqPacket packet, OnIqPacketReceived callback) {
-		if (packet.getId()==null) {
+		if (packet.getId() == null) {
 			String id = nextRandomId();
 			packet.setAttribute("id", id);
 		}
 		packet.setFrom(account.getFullJid());
 		this.sendPacket(packet, callback);
 	}
-	
+
 	public void sendUnboundIqPacket(IqPacket packet, OnIqPacketReceived callback) {
-		if (packet.getId()==null) {
+		if (packet.getId() == null) {
 			String id = nextRandomId();
 			packet.setAttribute("id", id);
 		}
@@ -770,26 +807,27 @@ public class XmppConnection implements Runnable {
 			OnPresencePacketReceived callback) {
 		this.sendPacket(packet, callback);
 	}
-	
-	private synchronized void sendPacket(final AbstractStanza packet, PacketReceived callback) {
+
+	private synchronized void sendPacket(final AbstractStanza packet,
+			PacketReceived callback) {
 		// TODO dont increment stanza count if packet = request packet or ack;
 		++stanzasSent;
 		tagWriter.writeStanzaAsync(packet);
 		if (callback != null) {
-			if (packet.getId()==null) {
+			if (packet.getId() == null) {
 				packet.setId(nextRandomId());
 			}
 			packetCallbacks.put(packet.getId(), callback);
 		}
 	}
-	
+
 	public void sendPing() {
 		if (streamFeatures.hasChild("sm")) {
 			tagWriter.writeStanzaAsync(new RequestPacket(smVersion));
 		} else {
 			IqPacket iq = new IqPacket(IqPacket.TYPE_GET);
 			iq.setFrom(account.getFullJid());
-			iq.addChild("ping","urn:xmpp:ping");
+			iq.addChild("ping", "urn:xmpp:ping");
 			this.sendIqPacket(iq, null);
 		}
 	}
@@ -808,82 +846,95 @@ public class XmppConnection implements Runnable {
 			OnPresencePacketReceived listener) {
 		this.presenceListener = listener;
 	}
-	
-	public void setOnJinglePacketReceivedListener(OnJinglePacketReceived listener) {
+
+	public void setOnJinglePacketReceivedListener(
+			OnJinglePacketReceived listener) {
 		this.jingleListener = listener;
 	}
 
 	public void setOnStatusChangedListener(OnStatusChanged listener) {
 		this.statusListener = listener;
 	}
-	
-	public void setOnTLSExceptionReceivedListener(OnTLSExceptionReceived listener) {
+
+	public void setOnTLSExceptionReceivedListener(
+			OnTLSExceptionReceived listener) {
 		this.tlsListener = listener;
 	}
-	
+
 	public void setOnBindListener(OnBindListener listener) {
 		this.bindListener = listener;
 	}
 
 	public void disconnect(boolean force) {
 		changeStatus(Account.STATUS_OFFLINE);
-		Log.d(LOGTAG,"disconnecting");
+		Log.d(LOGTAG, "disconnecting");
 		try {
-		if (force) {
+			if (force) {
 				socket.close();
 				return;
-		}
-		if (tagWriter.isActive()) {
-			tagWriter.finish();
-			while(!tagWriter.finished()) {
-				//Log.d(LOGTAG,"not yet finished");
-				Thread.sleep(100);
 			}
-			tagWriter.writeTag(Tag.end("stream:stream"));
-		}
+			new Thread(new Runnable() {
+
+				@Override
+				public void run() {
+					if (tagWriter.isActive()) {
+						tagWriter.finish();
+						try {
+							while (!tagWriter.finished()) {
+								Log.d(LOGTAG, "not yet finished");
+								Thread.sleep(100);
+							}
+							tagWriter.writeTag(Tag.end("stream:stream"));
+						} catch (IOException e) {
+							Log.d(LOGTAG, "io exception during disconnect");
+						} catch (InterruptedException e) {
+							Log.d(LOGTAG, "interrupted");
+						}
+					}
+				}
+			});
 		} catch (IOException e) {
-			Log.d(LOGTAG,"io exception during disconnect");
-		} catch (InterruptedException e) {
-			Log.d(LOGTAG,"interupted while waiting for disconnect");
+			Log.d(LOGTAG, "io exception during disconnect");
 		}
 	}
-	
+
 	public boolean hasFeatureRosterManagment() {
-		if (this.streamFeatures==null) {
+		if (this.streamFeatures == null) {
 			return false;
 		} else {
 			return this.streamFeatures.hasChild("ver");
 		}
 	}
-	
+
 	public boolean hasFeatureStreamManagment() {
-		if (this.streamFeatures==null) {
+		if (this.streamFeatures == null) {
 			return false;
 		} else {
 			return this.streamFeatures.hasChild("sm");
 		}
 	}
-	
+
 	public boolean hasFeaturesCarbon() {
 		return hasDiscoFeature(account.getServer(), "urn:xmpp:carbons:2");
 	}
-	
+
 	public boolean hasDiscoFeature(String server, String feature) {
 		if (!disco.containsKey(server)) {
 			return false;
 		}
 		return disco.get(server).contains(feature);
 	}
-	
+
 	public String findDiscoItemByFeature(String feature) {
-		Iterator<Entry<String, List<String>>> it = this.disco.entrySet().iterator();
-	    while (it.hasNext()) {
-	    	Entry<String, List<String>> pairs = it.next();
-	        if (pairs.getValue().contains(feature)) {
-	        	return pairs.getKey();
-	        }
-	        it.remove();
-	    }
+		Iterator<Entry<String, List<String>>> it = this.disco.entrySet()
+				.iterator();
+		while (it.hasNext()) {
+			Entry<String, List<String>> pairs = it.next();
+			if (pairs.getValue().contains(feature)) {
+				return pairs.getKey();
+			}
+			it.remove();
+		}
 		return null;
 	}
 
@@ -894,7 +945,7 @@ public class XmppConnection implements Runnable {
 	public int getReceivedStanzas() {
 		return this.stanzasReceived;
 	}
-	
+
 	public int getSentStanzas() {
 		return this.stanzasSent;
 	}
@@ -902,13 +953,13 @@ public class XmppConnection implements Runnable {
 	public String getMucServer() {
 		return findDiscoItemByFeature("http://jabber.org/protocol/muc");
 	}
-	
+
 	public int getTimeToNextAttempt() {
-		int interval = (int) (25 * Math.pow(1.5,attempt));
+		int interval = (int) (25 * Math.pow(1.5, attempt));
 		int secondsSinceLast = (int) ((SystemClock.elapsedRealtime() - this.lastConnect) / 1000);
 		return interval - secondsSinceLast;
 	}
-	
+
 	public int getAttempt() {
 		return this.attempt;
 	}