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