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