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