1package eu.siacs.conversations.xmpp;
2
3import java.io.IOException;
4import java.io.InputStream;
5import java.io.OutputStream;
6import java.math.BigInteger;
7import java.net.Socket;
8import java.net.UnknownHostException;
9import java.security.KeyManagementException;
10import java.security.KeyStore;
11import java.security.KeyStoreException;
12import java.security.MessageDigest;
13import java.security.NoSuchAlgorithmException;
14import java.security.SecureRandom;
15import java.security.cert.CertPathValidatorException;
16import java.security.cert.CertificateException;
17import java.security.cert.X509Certificate;
18import java.util.HashSet;
19import java.util.Hashtable;
20import java.util.List;
21
22import javax.net.ssl.SSLContext;
23import javax.net.ssl.SSLSocket;
24import javax.net.ssl.SSLSocketFactory;
25import javax.net.ssl.TrustManager;
26import javax.net.ssl.TrustManagerFactory;
27import javax.net.ssl.X509TrustManager;
28
29import org.json.JSONException;
30import org.xmlpull.v1.XmlPullParserException;
31
32import android.os.Bundle;
33import android.os.PowerManager;
34import android.os.SystemClock;
35import android.util.Log;
36import eu.siacs.conversations.entities.Account;
37import eu.siacs.conversations.utils.CryptoHelper;
38import eu.siacs.conversations.utils.DNSHelper;
39import eu.siacs.conversations.xml.Element;
40import eu.siacs.conversations.xml.Tag;
41import eu.siacs.conversations.xml.TagWriter;
42import eu.siacs.conversations.xml.XmlReader;
43import eu.siacs.conversations.xmpp.stanzas.AbstractStanza;
44import eu.siacs.conversations.xmpp.stanzas.IqPacket;
45import eu.siacs.conversations.xmpp.stanzas.MessagePacket;
46import eu.siacs.conversations.xmpp.stanzas.PresencePacket;
47import eu.siacs.conversations.xmpp.stanzas.streammgmt.AckPacket;
48import eu.siacs.conversations.xmpp.stanzas.streammgmt.EnablePacket;
49import eu.siacs.conversations.xmpp.stanzas.streammgmt.RequestPacket;
50import eu.siacs.conversations.xmpp.stanzas.streammgmt.ResumePacket;
51
52public class XmppConnection implements Runnable {
53
54 protected Account account;
55 private static final String LOGTAG = "xmppService";
56
57 private PowerManager.WakeLock wakeLock;
58
59 private SecureRandom random = new SecureRandom();
60
61 private Socket socket;
62 private XmlReader tagReader;
63 private TagWriter tagWriter;
64
65 private boolean shouldBind = true;
66 private boolean shouldAuthenticate = true;
67 private Element streamFeatures;
68 private HashSet<String> discoFeatures = new HashSet<String>();
69
70 private String streamId = null;
71
72 private int stanzasReceived = 0;
73 private int stanzasSent = 0;
74
75 public long lastPaketReceived = 0;
76 public long lastPingSent = 0;
77 public long lastConnect = 0;
78 public long lastSessionStarted = 0;
79
80 private static final int PACKET_IQ = 0;
81 private static final int PACKET_MESSAGE = 1;
82 private static final int PACKET_PRESENCE = 2;
83
84 private Hashtable<String, PacketReceived> packetCallbacks = new Hashtable<String, PacketReceived>();
85 private OnPresencePacketReceived presenceListener = null;
86 private OnIqPacketReceived unregisteredIqListener = null;
87 private OnMessagePacketReceived messageListener = null;
88 private OnStatusChanged statusListener = null;
89 private OnTLSExceptionReceived tlsListener;
90
91 public XmppConnection(Account account, PowerManager pm) {
92 this.account = account;
93 wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
94 "XmppConnection");
95 tagReader = new XmlReader(wakeLock);
96 tagWriter = new TagWriter();
97 }
98
99 protected void changeStatus(int nextStatus) {
100 if (account.getStatus() != nextStatus) {
101 account.setStatus(nextStatus);
102 if (statusListener != null) {
103 statusListener.onStatusChanged(account);
104 }
105 }
106 }
107
108 protected void connect() {
109 Log.d(LOGTAG,account.getJid()+ ": connecting");
110 lastConnect = SystemClock.elapsedRealtime();
111 try {
112 tagReader = new XmlReader(wakeLock);
113 tagWriter = new TagWriter();
114 packetCallbacks.clear();
115 this.changeStatus(Account.STATUS_CONNECTING);
116 Bundle namePort = DNSHelper.getSRVRecord(account.getServer());
117 String srvRecordServer = namePort.getString("name");
118 int srvRecordPort = namePort.getInt("port");
119 if (srvRecordServer != null) {
120 Log.d(LOGTAG, account.getJid() + ": using values from dns "
121 + srvRecordServer + ":" + srvRecordPort);
122 socket = new Socket(srvRecordServer, srvRecordPort);
123 } else {
124 socket = new Socket(account.getServer(), 5222);
125 }
126 OutputStream out = socket.getOutputStream();
127 tagWriter.setOutputStream(out);
128 InputStream in = socket.getInputStream();
129 tagReader.setInputStream(in);
130 tagWriter.beginDocument();
131 sendStartStream();
132 Tag nextTag;
133 while ((nextTag = tagReader.readTag()) != null) {
134 if (nextTag.isStart("stream")) {
135 processStream(nextTag);
136 break;
137 } else {
138 Log.d(LOGTAG, "found unexpected tag: " + nextTag.getName());
139 return;
140 }
141 }
142 if (socket.isConnected()) {
143 socket.close();
144 }
145 } catch (UnknownHostException e) {
146 this.changeStatus(Account.STATUS_SERVER_NOT_FOUND);
147 if (wakeLock.isHeld()) {
148 wakeLock.release();
149 }
150 return;
151 } catch (IOException e) {
152 if (account.getStatus() != Account.STATUS_TLS_ERROR) {
153 this.changeStatus(Account.STATUS_OFFLINE);
154 }
155 if (wakeLock.isHeld()) {
156 wakeLock.release();
157 }
158 return;
159 } catch (XmlPullParserException e) {
160 this.changeStatus(Account.STATUS_OFFLINE);
161 Log.d(LOGTAG, "xml exception " + e.getMessage());
162 if (wakeLock.isHeld()) {
163 wakeLock.release();
164 }
165 return;
166 }
167
168 }
169
170 @Override
171 public void run() {
172 connect();
173 }
174
175 private void processStream(Tag currentTag) throws XmlPullParserException,
176 IOException {
177 Tag nextTag = tagReader.readTag();
178 while ((nextTag != null) && (!nextTag.isEnd("stream"))) {
179 if (nextTag.isStart("error")) {
180 processStreamError(nextTag);
181 } else if (nextTag.isStart("features")) {
182 processStreamFeatures(nextTag);
183 if ((streamFeatures.getChildren().size() == 1)
184 && (streamFeatures.hasChild("starttls"))
185 && (!account.isOptionSet(Account.OPTION_USETLS))) {
186 changeStatus(Account.STATUS_SERVER_REQUIRES_TLS);
187 }
188 } else if (nextTag.isStart("proceed")) {
189 switchOverToTls(nextTag);
190 } else if (nextTag.isStart("success")) {
191 Log.d(LOGTAG, account.getJid()
192 + ": logged in");
193 tagReader.readTag();
194 tagReader.reset();
195 sendStartStream();
196 processStream(tagReader.readTag());
197 break;
198 } else if (nextTag.isStart("failure")) {
199 tagReader.readElement(nextTag);
200 changeStatus(Account.STATUS_UNAUTHORIZED);
201 } else if (nextTag.isStart("enabled")) {
202 this.stanzasSent = 0;
203 Element enabled = tagReader.readElement(nextTag);
204 if ("true".equals(enabled.getAttribute("resume"))) {
205 this.streamId = enabled.getAttribute("id");
206 Log.d(LOGTAG,account.getJid()+": stream managment enabled (resumable)");
207 } else {
208 Log.d(LOGTAG,account.getJid()+": stream managment enabled");
209 }
210 this.lastSessionStarted = SystemClock.elapsedRealtime();
211 this.stanzasReceived = 0;
212 RequestPacket r = new RequestPacket();
213 tagWriter.writeStanzaAsync(r);
214 } else if (nextTag.isStart("resumed")) {
215 tagReader.readElement(nextTag);
216 changeStatus(Account.STATUS_ONLINE);
217 Log.d(LOGTAG,account.getJid()+": session resumed");
218 } else if (nextTag.isStart("r")) {
219 tagReader.readElement(nextTag);
220 AckPacket ack = new AckPacket(this.stanzasReceived);
221 //Log.d(LOGTAG,ack.toString());
222 tagWriter.writeStanzaAsync(ack);
223 } else if (nextTag.isStart("a")) {
224 Element ack = tagReader.readElement(nextTag);
225 lastPaketReceived = SystemClock.elapsedRealtime();
226 int serverSequence = Integer.parseInt(ack.getAttribute("h"));
227 if (serverSequence>this.stanzasSent) {
228 this.stanzasSent = serverSequence;
229 }
230 //Log.d(LOGTAG,"server ack"+ack.toString()+" ("+this.stanzasSent+")");
231 } else if (nextTag.isStart("failed")) {
232 tagReader.readElement(nextTag);
233 Log.d(LOGTAG,account.getJid()+": resumption failed");
234 streamId = null;
235 if (account.getStatus() != Account.STATUS_ONLINE) {
236 sendBindRequest();
237 }
238 } else if (nextTag.isStart("iq")) {
239 processIq(nextTag);
240 } else if (nextTag.isStart("message")) {
241 processMessage(nextTag);
242 } else if (nextTag.isStart("presence")) {
243 processPresence(nextTag);
244 } else {
245 Log.d(LOGTAG, "found unexpected tag: " + nextTag.getName()
246 + " as child of " + currentTag.getName());
247 }
248 nextTag = tagReader.readTag();
249 }
250 if (account.getStatus() == Account.STATUS_ONLINE) {
251 account.setStatus(Account.STATUS_OFFLINE);
252 if (statusListener != null) {
253 statusListener.onStatusChanged(account);
254 }
255 }
256 }
257
258 private Element processPacket(Tag currentTag, int packetType)
259 throws XmlPullParserException, IOException {
260 Element element;
261 switch (packetType) {
262 case PACKET_IQ:
263 element = new IqPacket();
264 break;
265 case PACKET_MESSAGE:
266 element = new MessagePacket();
267 break;
268 case PACKET_PRESENCE:
269 element = new PresencePacket();
270 break;
271 default:
272 return null;
273 }
274 element.setAttributes(currentTag.getAttributes());
275 Tag nextTag = tagReader.readTag();
276 while (!nextTag.isEnd(element.getName())) {
277 if (!nextTag.isNo()) {
278 Element child = tagReader.readElement(nextTag);
279 element.addChild(child);
280 }
281 nextTag = tagReader.readTag();
282 }
283 ++stanzasReceived;
284 lastPaketReceived = SystemClock.elapsedRealtime();
285 return element;
286 }
287
288 private void processIq(Tag currentTag) throws XmlPullParserException,
289 IOException {
290 IqPacket packet = (IqPacket) processPacket(currentTag, PACKET_IQ);
291 if (packetCallbacks.containsKey(packet.getId())) {
292 if (packetCallbacks.get(packet.getId()) instanceof OnIqPacketReceived) {
293 ((OnIqPacketReceived) packetCallbacks.get(packet.getId()))
294 .onIqPacketReceived(account, packet);
295 }
296
297 packetCallbacks.remove(packet.getId());
298 } else if (this.unregisteredIqListener != null) {
299 this.unregisteredIqListener.onIqPacketReceived(account, packet);
300 }
301 }
302
303 private void processMessage(Tag currentTag) throws XmlPullParserException,
304 IOException {
305 MessagePacket packet = (MessagePacket) processPacket(currentTag,
306 PACKET_MESSAGE);
307 String id = packet.getAttribute("id");
308 if ((id != null) && (packetCallbacks.containsKey(id))) {
309 if (packetCallbacks.get(id) instanceof OnMessagePacketReceived) {
310 ((OnMessagePacketReceived) packetCallbacks.get(id))
311 .onMessagePacketReceived(account, packet);
312 }
313 packetCallbacks.remove(id);
314 } else if (this.messageListener != null) {
315 this.messageListener.onMessagePacketReceived(account, packet);
316 }
317 }
318
319 private void processPresence(Tag currentTag) throws XmlPullParserException,
320 IOException {
321 PresencePacket packet = (PresencePacket) processPacket(currentTag,
322 PACKET_PRESENCE);
323 String id = packet.getAttribute("id");
324 if ((id != null) && (packetCallbacks.containsKey(id))) {
325 if (packetCallbacks.get(id) instanceof OnPresencePacketReceived) {
326 ((OnPresencePacketReceived) packetCallbacks.get(id))
327 .onPresencePacketReceived(account, packet);
328 }
329 packetCallbacks.remove(id);
330 } else if (this.presenceListener != null) {
331 this.presenceListener.onPresencePacketReceived(account, packet);
332 }
333 }
334
335 private void sendStartTLS() throws IOException {
336 Tag startTLS = Tag.empty("starttls");
337 startTLS.setAttribute("xmlns", "urn:ietf:params:xml:ns:xmpp-tls");
338 tagWriter.writeTag(startTLS);
339 }
340
341 private void switchOverToTls(Tag currentTag) throws XmlPullParserException,
342 IOException {
343 Tag nextTag = tagReader.readTag(); // should be proceed end tag
344 try {
345 SSLContext sc = SSLContext.getInstance("TLS");
346 TrustManagerFactory tmf = TrustManagerFactory
347 .getInstance(TrustManagerFactory.getDefaultAlgorithm());
348 // Initialise the TMF as you normally would, for example:
349 // tmf.in
350 try {
351 tmf.init((KeyStore) null);
352 } catch (KeyStoreException e1) {
353 // TODO Auto-generated catch block
354 e1.printStackTrace();
355 }
356
357 TrustManager[] trustManagers = tmf.getTrustManagers();
358 final X509TrustManager origTrustmanager = (X509TrustManager) trustManagers[0];
359
360 TrustManager[] wrappedTrustManagers = new TrustManager[] { new X509TrustManager() {
361
362 @Override
363 public void checkClientTrusted(X509Certificate[] chain,
364 String authType) throws CertificateException {
365 origTrustmanager.checkClientTrusted(chain, authType);
366 }
367
368 @Override
369 public void checkServerTrusted(X509Certificate[] chain,
370 String authType) throws CertificateException {
371 try {
372 origTrustmanager.checkServerTrusted(chain, authType);
373 } catch (CertificateException e) {
374 if (e.getCause() instanceof CertPathValidatorException) {
375 String sha;
376 try {
377 MessageDigest sha1 = MessageDigest.getInstance("SHA1");
378 sha1.update(chain[0].getEncoded());
379 sha = CryptoHelper.bytesToHex(sha1.digest());
380 if (!sha.equals(account.getSSLFingerprint())) {
381 changeStatus(Account.STATUS_TLS_ERROR);
382 if (tlsListener!=null) {
383 tlsListener.onTLSExceptionReceived(sha,account);
384 }
385 throw new CertificateException();
386 }
387 } catch (NoSuchAlgorithmException e1) {
388 // TODO Auto-generated catch block
389 e1.printStackTrace();
390 }
391 } else {
392 throw new CertificateException();
393 }
394 }
395 }
396
397 @Override
398 public X509Certificate[] getAcceptedIssuers() {
399 return origTrustmanager.getAcceptedIssuers();
400 }
401
402 } };
403 sc.init(null, wrappedTrustManagers, null);
404 SSLSocketFactory factory = sc.getSocketFactory();
405 SSLSocket sslSocket = (SSLSocket) factory.createSocket(socket,
406 socket.getInetAddress().getHostAddress(), socket.getPort(),
407 true);
408 tagReader.setInputStream(sslSocket.getInputStream());
409 tagWriter.setOutputStream(sslSocket.getOutputStream());
410 sendStartStream();
411 Log.d(LOGTAG,account.getJid()+": TLS connection established");
412 processStream(tagReader.readTag());
413 sslSocket.close();
414 } catch (NoSuchAlgorithmException e1) {
415 // TODO Auto-generated catch block
416 e1.printStackTrace();
417 } catch (KeyManagementException e) {
418 // TODO Auto-generated catch block
419 e.printStackTrace();
420 }
421 }
422
423 private void sendSaslAuth() throws IOException, XmlPullParserException {
424 String saslString = CryptoHelper.saslPlain(account.getUsername(),
425 account.getPassword());
426 Element auth = new Element("auth");
427 auth.setAttribute("xmlns", "urn:ietf:params:xml:ns:xmpp-sasl");
428 auth.setAttribute("mechanism", "PLAIN");
429 auth.setContent(saslString);
430 tagWriter.writeElement(auth);
431 }
432
433 private void processStreamFeatures(Tag currentTag)
434 throws XmlPullParserException, IOException {
435 this.streamFeatures = tagReader.readElement(currentTag);
436 if (this.streamFeatures.hasChild("starttls")
437 && account.isOptionSet(Account.OPTION_USETLS)) {
438 sendStartTLS();
439 } else if (this.streamFeatures.hasChild("mechanisms")
440 && shouldAuthenticate) {
441 sendSaslAuth();
442 } else if (this.streamFeatures.hasChild("sm") && streamId != null) {
443 Log.d(LOGTAG,"found old stream id. trying to remuse");
444 ResumePacket resume = new ResumePacket(this.streamId,stanzasReceived);
445 this.tagWriter.writeStanzaAsync(resume);
446 } else if (this.streamFeatures.hasChild("bind") && shouldBind) {
447 sendBindRequest();
448 if (this.streamFeatures.hasChild("session")) {
449 Log.d(LOGTAG,"sending session");
450 IqPacket startSession = new IqPacket(IqPacket.TYPE_SET);
451 Element session = new Element("session");
452 session.setAttribute("xmlns",
453 "urn:ietf:params:xml:ns:xmpp-session");
454 session.setContent("");
455 startSession.addChild(session);
456 this.sendIqPacket(startSession, null);
457 }
458 }
459 }
460
461 private void sendInitialPresence() {
462 PresencePacket packet = new PresencePacket();
463 packet.setAttribute("from", account.getFullJid());
464 if (account.getKeys().has("pgp_signature")) {
465 try {
466 String signature = account.getKeys().getString("pgp_signature");
467 Element status = new Element("status");
468 status.setContent("online");
469 packet.addChild(status);
470 Element x = new Element("x");
471 x.setAttribute("xmlns", "jabber:x:signed");
472 x.setContent(signature);
473 packet.addChild(x);
474 } catch (JSONException e) {
475 //
476 }
477 }
478 this.sendPresencePacket(packet);
479 }
480
481 private void sendBindRequest() throws IOException {
482 IqPacket iq = new IqPacket(IqPacket.TYPE_SET);
483 Element bind = new Element("bind");
484 bind.setAttribute("xmlns", "urn:ietf:params:xml:ns:xmpp-bind");
485 Element resource = new Element("resource");
486 resource.setContent("Conversations");
487 bind.addChild(resource);
488 iq.addChild(bind);
489 this.sendIqPacket(iq, new OnIqPacketReceived() {
490 @Override
491 public void onIqPacketReceived(Account account, IqPacket packet) {
492 String resource = packet.findChild("bind").findChild("jid")
493 .getContent().split("/")[1];
494 account.setResource(resource);
495 account.setStatus(Account.STATUS_ONLINE);
496 if (streamFeatures.hasChild("sm")) {
497 EnablePacket enable = new EnablePacket();
498 tagWriter.writeStanzaAsync(enable);
499 }
500 sendInitialPresence();
501 sendServiceDiscovery();
502 if (statusListener != null) {
503 statusListener.onStatusChanged(account);
504 }
505 }
506 });
507 }
508
509 private void sendServiceDiscovery() {
510 IqPacket iq = new IqPacket(IqPacket.TYPE_GET);
511 iq.setAttribute("to", account.getServer());
512 Element query = new Element("query");
513 query.setAttribute("xmlns", "http://jabber.org/protocol/disco#info");
514 iq.addChild(query);
515 this.sendIqPacket(iq, new OnIqPacketReceived() {
516
517 @Override
518 public void onIqPacketReceived(Account account, IqPacket packet) {
519 if (packet.hasChild("query")) {
520 List<Element> elements = packet.findChild("query")
521 .getChildren();
522 for (int i = 0; i < elements.size(); ++i) {
523 if (elements.get(i).getName().equals("feature")) {
524 discoFeatures.add(elements.get(i).getAttribute(
525 "var"));
526 }
527 }
528 }
529 if (discoFeatures.contains("urn:xmpp:carbons:2")) {
530 sendEnableCarbons();
531 }
532 }
533 });
534 }
535
536 private void sendEnableCarbons() {
537 IqPacket iq = new IqPacket(IqPacket.TYPE_SET);
538 Element enable = new Element("enable");
539 enable.setAttribute("xmlns", "urn:xmpp:carbons:2");
540 iq.addChild(enable);
541 this.sendIqPacket(iq, new OnIqPacketReceived() {
542
543 @Override
544 public void onIqPacketReceived(Account account, IqPacket packet) {
545 if (!packet.hasChild("error")) {
546 Log.d(LOGTAG, account.getJid()
547 + ": successfully enabled carbons");
548 } else {
549 Log.d(LOGTAG, account.getJid()
550 + ": error enableing carbons " + packet.toString());
551 }
552 }
553 });
554 }
555
556 private void processStreamError(Tag currentTag) {
557 Log.d(LOGTAG, "processStreamError");
558 }
559
560 private void sendStartStream() throws IOException {
561 Tag stream = Tag.start("stream:stream");
562 stream.setAttribute("from", account.getJid());
563 stream.setAttribute("to", account.getServer());
564 stream.setAttribute("version", "1.0");
565 stream.setAttribute("xml:lang", "en");
566 stream.setAttribute("xmlns", "jabber:client");
567 stream.setAttribute("xmlns:stream", "http://etherx.jabber.org/streams");
568 tagWriter.writeTag(stream);
569 }
570
571 private String nextRandomId() {
572 return new BigInteger(50, random).toString(32);
573 }
574
575 public void sendIqPacket(IqPacket packet, OnIqPacketReceived callback) {
576 String id = nextRandomId();
577 packet.setAttribute("id", id);
578 this.sendPacket(packet, callback);
579 }
580
581 public void sendMessagePacket(MessagePacket packet) {
582 this.sendPacket(packet, null);
583 }
584
585 public void sendMessagePacket(MessagePacket packet,
586 OnMessagePacketReceived callback) {
587 this.sendPacket(packet, callback);
588 }
589
590 public void sendPresencePacket(PresencePacket packet) {
591 this.sendPacket(packet, null);
592 }
593
594 public void sendPresencePacket(PresencePacket packet,
595 OnPresencePacketReceived callback) {
596 this.sendPacket(packet, callback);
597 }
598
599 private synchronized void sendPacket(final AbstractStanza packet, PacketReceived callback) {
600 // TODO dont increment stanza count if packet = request packet or ack;
601 ++stanzasSent;
602 tagWriter.writeStanzaAsync(packet);
603 if (callback != null) {
604 if (packet.getId()==null) {
605 packet.setId(nextRandomId());
606 }
607 packetCallbacks.put(packet.getId(), callback);
608 }
609 }
610
611 public void sendPing() {
612 if (streamFeatures.hasChild("sm")) {
613 Log.d(LOGTAG,account.getJid()+": sending r as ping");
614 tagWriter.writeStanzaAsync(new RequestPacket());
615 } else {
616 Log.d(LOGTAG,account.getJid()+": sending iq as ping");
617 IqPacket iq = new IqPacket(IqPacket.TYPE_GET);
618 Element ping = new Element("ping");
619 iq.setAttribute("from",account.getFullJid());
620 ping.setAttribute("xmlns", "urn:xmpp:ping");
621 iq.addChild(ping);
622 this.sendIqPacket(iq, null);
623 }
624 }
625
626 public void setOnMessagePacketReceivedListener(
627 OnMessagePacketReceived listener) {
628 this.messageListener = listener;
629 }
630
631 public void setOnUnregisteredIqPacketReceivedListener(
632 OnIqPacketReceived listener) {
633 this.unregisteredIqListener = listener;
634 }
635
636 public void setOnPresencePacketReceivedListener(
637 OnPresencePacketReceived listener) {
638 this.presenceListener = listener;
639 }
640
641 public void setOnStatusChangedListener(OnStatusChanged listener) {
642 this.statusListener = listener;
643 }
644
645 public void setOnTLSExceptionReceivedListener(OnTLSExceptionReceived listener) {
646 this.tlsListener = listener;
647 }
648
649 public void disconnect(boolean force) {
650 Log.d(LOGTAG,"disconnecting");
651 try {
652 if (force) {
653 socket.close();
654 return;
655 }
656 tagWriter.finish();
657 while(!tagWriter.finished()) {
658 //Log.d(LOGTAG,"not yet finished");
659 Thread.sleep(100);
660 }
661 tagWriter.writeTag(Tag.end("stream:stream"));
662 } catch (IOException e) {
663 Log.d(LOGTAG,"io exception during disconnect");
664 } catch (InterruptedException e) {
665 Log.d(LOGTAG,"interupted while waiting for disconnect");
666 }
667 }
668
669 public boolean hasFeatureRosterManagment() {
670 if (this.streamFeatures==null) {
671 return false;
672 } else {
673 return this.streamFeatures.hasChild("ver");
674 }
675 }
676
677 public void r() {
678 this.tagWriter.writeStanzaAsync(new RequestPacket());
679 }
680
681 public int getReceivedStanzas() {
682 return this.stanzasReceived;
683 }
684
685 public int getSentStanzas() {
686 return this.stanzasSent;
687 }
688}