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