1package eu.siacs.conversations.services;
2
3import java.text.ParseException;
4import java.text.SimpleDateFormat;
5import java.util.Collections;
6import java.util.Comparator;
7import java.util.Date;
8import java.util.Hashtable;
9import java.util.List;
10import java.util.Locale;
11import java.util.Random;
12
13import org.openintents.openpgp.util.OpenPgpApi;
14import org.openintents.openpgp.util.OpenPgpServiceConnection;
15
16import net.java.otr4j.OtrException;
17import net.java.otr4j.session.Session;
18import net.java.otr4j.session.SessionStatus;
19
20import eu.siacs.conversations.crypto.PgpEngine;
21import eu.siacs.conversations.crypto.PgpEngine.OpenPgpException;
22import eu.siacs.conversations.entities.Account;
23import eu.siacs.conversations.entities.Contact;
24import eu.siacs.conversations.entities.Conversation;
25import eu.siacs.conversations.entities.Message;
26import eu.siacs.conversations.entities.MucOptions;
27import eu.siacs.conversations.entities.MucOptions.OnRenameListener;
28import eu.siacs.conversations.entities.Presences;
29import eu.siacs.conversations.persistance.DatabaseBackend;
30import eu.siacs.conversations.persistance.OnPhoneContactsMerged;
31import eu.siacs.conversations.ui.OnAccountListChangedListener;
32import eu.siacs.conversations.ui.OnConversationListChangedListener;
33import eu.siacs.conversations.ui.OnRosterFetchedListener;
34import eu.siacs.conversations.utils.ExceptionHelper;
35import eu.siacs.conversations.utils.MessageParser;
36import eu.siacs.conversations.utils.OnPhoneContactsLoadedListener;
37import eu.siacs.conversations.utils.PhoneHelper;
38import eu.siacs.conversations.utils.UIHelper;
39import eu.siacs.conversations.xml.Element;
40import eu.siacs.conversations.xmpp.OnBindListener;
41import eu.siacs.conversations.xmpp.OnIqPacketReceived;
42import eu.siacs.conversations.xmpp.OnJinglePacketReceived;
43import eu.siacs.conversations.xmpp.OnMessagePacketReceived;
44import eu.siacs.conversations.xmpp.OnPresencePacketReceived;
45import eu.siacs.conversations.xmpp.OnStatusChanged;
46import eu.siacs.conversations.xmpp.OnTLSExceptionReceived;
47import eu.siacs.conversations.xmpp.XmppConnection;
48import eu.siacs.conversations.xmpp.stanzas.IqPacket;
49import eu.siacs.conversations.xmpp.stanzas.MessagePacket;
50import eu.siacs.conversations.xmpp.stanzas.PresencePacket;
51import eu.siacs.conversations.xmpp.stanzas.jingle.JinglePacket;
52import android.app.AlarmManager;
53import android.app.PendingIntent;
54import android.app.Service;
55import android.content.Context;
56import android.content.Intent;
57import android.content.SharedPreferences;
58import android.database.ContentObserver;
59import android.database.DatabaseUtils;
60import android.net.ConnectivityManager;
61import android.net.NetworkInfo;
62import android.os.Binder;
63import android.os.Bundle;
64import android.os.IBinder;
65import android.os.PowerManager;
66import android.os.PowerManager.WakeLock;
67import android.os.SystemClock;
68import android.preference.PreferenceManager;
69import android.provider.ContactsContract;
70import android.util.Log;
71
72public class XmppConnectionService extends Service {
73
74 protected static final String LOGTAG = "xmppService";
75 public DatabaseBackend databaseBackend;
76
77 public long startDate;
78
79 private static final int PING_MAX_INTERVAL = 300;
80 private static final int PING_MIN_INTERVAL = 10;
81 private static final int PING_TIMEOUT = 5;
82 private static final int CONNECT_TIMEOUT = 60;
83
84 private List<Account> accounts;
85 private List<Conversation> conversations = null;
86
87 public OnConversationListChangedListener convChangedListener = null;
88 private OnAccountListChangedListener accountChangedListener = null;
89 private OnTLSExceptionReceived tlsException = null;
90
91 public void setOnTLSExceptionReceivedListener(
92 OnTLSExceptionReceived listener) {
93 tlsException = listener;
94 }
95
96 private Random mRandom = new Random(System.currentTimeMillis());
97
98 private ContentObserver contactObserver = new ContentObserver(null) {
99 @Override
100 public void onChange(boolean selfChange) {
101 super.onChange(selfChange);
102 Log.d(LOGTAG, "contact list has changed");
103 mergePhoneContactsWithRoster(null);
104 }
105 };
106
107 private XmppConnectionService service = this;
108
109 private final IBinder mBinder = new XmppConnectionBinder();
110 private OnMessagePacketReceived messageListener = new OnMessagePacketReceived() {
111
112 @Override
113 public void onMessagePacketReceived(Account account,
114 MessagePacket packet) {
115 Message message = null;
116 boolean notify = true;
117 if ((packet.getType() == MessagePacket.TYPE_CHAT)) {
118 String pgpBody = MessageParser.getPgpBody(packet);
119 if (pgpBody != null) {
120 message = MessageParser.parsePgpChat(pgpBody, packet,
121 account, service);
122 message.markUnread();
123 } else if (packet.hasChild("body")
124 && (packet.getBody().startsWith("?OTR"))) {
125 message = MessageParser.parseOtrChat(packet, account,
126 service);
127 if (message != null) {
128 message.markUnread();
129 }
130 } else if (packet.hasChild("body")) {
131 message = MessageParser.parsePlainTextChat(packet, account,
132 service);
133 message.markUnread();
134 } else if (packet.hasChild("received")
135 || (packet.hasChild("sent"))) {
136 message = MessageParser.parseCarbonMessage(packet, account,
137 service);
138 if (message != null) {
139 message.getConversation().markRead();
140 }
141 notify = false;
142 }
143
144 } else if (packet.getType() == MessagePacket.TYPE_GROUPCHAT) {
145 message = MessageParser
146 .parseGroupchat(packet, account, service);
147 if (message != null) {
148 if (message.getStatus() == Message.STATUS_RECIEVED) {
149 message.markUnread();
150 } else {
151 message.getConversation().markRead();
152 notify = false;
153 }
154 }
155 } else if (packet.getType() == MessagePacket.TYPE_ERROR) {
156 message = MessageParser.parseError(packet, account, service);
157 } else if (packet.getType() == MessagePacket.TYPE_NORMAL) {
158 if (packet.hasChild("x")) {
159 Element x = packet.findChild("x");
160 if (x.hasChild("invite")) {
161 findOrCreateConversation(account, packet.getFrom(),
162 true);
163 if (convChangedListener != null) {
164 convChangedListener.onConversationListChanged();
165 }
166 Log.d(LOGTAG,
167 "invitation received to " + packet.getFrom());
168 }
169
170 } else {
171 // Log.d(LOGTAG, "unparsed message " + packet.toString());
172 }
173 }
174 if ((message == null) || (message.getBody() == null)) {
175 return;
176 }
177 if (packet.hasChild("delay")) {
178 try {
179 String stamp = packet.findChild("delay").getAttribute(
180 "stamp");
181 stamp = stamp.replace("Z", "+0000");
182 Date date = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ")
183 .parse(stamp);
184 message.setTime(date.getTime());
185 } catch (ParseException e) {
186 Log.d(LOGTAG, "error trying to parse date" + e.getMessage());
187 }
188 }
189 Conversation conversation = message.getConversation();
190 conversation.getMessages().add(message);
191 if (packet.getType() != MessagePacket.TYPE_ERROR) {
192 databaseBackend.createMessage(message);
193 }
194 if (convChangedListener != null) {
195 convChangedListener.onConversationListChanged();
196 } else {
197 UIHelper.updateNotification(getApplicationContext(),
198 getConversations(), message.getConversation(), notify);
199 }
200 }
201 };
202 private OnStatusChanged statusListener = new OnStatusChanged() {
203
204 @Override
205 public void onStatusChanged(Account account) {
206 if (accountChangedListener != null) {
207 accountChangedListener.onAccountListChangedListener();
208 }
209 if (account.getStatus() == Account.STATUS_ONLINE) {
210 List<Conversation> conversations = getConversations();
211 for (int i = 0; i < conversations.size(); ++i) {
212 if (conversations.get(i).getAccount() == account) {
213 sendUnsendMessages(conversations.get(i));
214 }
215 }
216 scheduleWakeupCall(PING_MAX_INTERVAL, true);
217 } else if (account.getStatus() == Account.STATUS_OFFLINE) {
218 if (!account.isOptionSet(Account.OPTION_DISABLED)) {
219 int timeToReconnect = mRandom.nextInt(50) + 10;
220 scheduleWakeupCall(timeToReconnect, false);
221 }
222
223 } else if (account.getStatus() == Account.STATUS_REGISTRATION_SUCCESSFULL) {
224 databaseBackend.updateAccount(account);
225 reconnectAccount(account, true);
226 }
227 }
228 };
229
230 private OnPresencePacketReceived presenceListener = new OnPresencePacketReceived() {
231
232 @Override
233 public void onPresencePacketReceived(Account account,
234 PresencePacket packet) {
235 if (packet.hasChild("x","http://jabber.org/protocol/muc#user")) {
236 Conversation muc = findMuc(
237 packet.getAttribute("from").split("/")[0], account);
238 if (muc != null) {
239 muc.getMucOptions().processPacket(packet);
240 } else {
241 Log.d(LOGTAG,account.getJid()+": could not find muc for received muc package "+packet.toString());
242 }
243 } else if (packet.hasChild("x","http://jabber.org/protocol/muc")) {
244 Conversation muc = findMuc(packet.getAttribute("from").split("/")[0], account);
245 if (muc != null) {
246 Log.d(LOGTAG,account.getJid()+": reading muc status packet "+packet.toString());
247 int error = muc.getMucOptions().getError();
248 muc.getMucOptions().processPacket(packet);
249 if ((muc.getMucOptions().getError() != error)
250 && (convChangedListener != null)) {
251 Log.d(LOGTAG, "muc error status changed");
252 convChangedListener.onConversationListChanged();
253 }
254 }
255 } else {
256 String[] fromParts = packet.getAttribute("from").split("/");
257 String type = packet.getAttribute("type");
258 if (fromParts[0].equals(account.getJid())) {
259 if (fromParts.length==2) {
260 if (type == null) {
261 account.updatePresence(fromParts[1],Presences.parseShow(packet.findChild("show")));
262 } else if (type.equals("unavailable")) {
263 account.removePresence(fromParts[1]);
264 }
265 }
266
267 } else {
268 Contact contact = findContact(account, fromParts[0]);
269 if (contact == null) {
270 // most likely roster not synced
271 return;
272 }
273 if (type == null) {
274 if (fromParts.length == 2) {
275 contact.updatePresence(fromParts[1], Presences.parseShow(packet.findChild("show")));
276 PgpEngine pgp = getPgpEngine();
277 if (pgp != null) {
278 Element x = packet.findChild("x","jabber:x:signed");
279 if (x != null) {
280 try {
281 contact.setPgpKeyId(pgp.fetchKeyId(
282 packet.findChild("status")
283 .getContent(), x
284 .getContent()));
285 } catch (OpenPgpException e) {
286 Log.d(LOGTAG, "faulty pgp. just ignore");
287 }
288 }
289 }
290 databaseBackend.updateContact(contact);
291 } else {
292 // Log.d(LOGTAG,"presence without resource "+packet.toString());
293 }
294 } else if (type.equals("unavailable")) {
295 if (fromParts.length != 2) {
296 // Log.d(LOGTAG,"received presence with no resource "+packet.toString());
297 } else {
298 contact.removePresence(fromParts[1]);
299 databaseBackend.updateContact(contact);
300 }
301 } else if (type.equals("subscribe")) {
302 if (contact
303 .getSubscriptionOption(Contact.Subscription.PREEMPTIVE_GRANT)) {
304 sendPresenceUpdatesTo(contact);
305 contact.setSubscriptionOption(Contact.Subscription.FROM);
306 contact.resetSubscriptionOption(Contact.Subscription.PREEMPTIVE_GRANT);
307 replaceContactInConversation(contact.getJid(),
308 contact);
309 databaseBackend.updateContact(contact);
310 if ((contact
311 .getSubscriptionOption(Contact.Subscription.ASKING))
312 && (!contact
313 .getSubscriptionOption(Contact.Subscription.TO))) {
314 requestPresenceUpdatesFrom(contact);
315 }
316 } else {
317 // TODO: ask user to handle it maybe
318 }
319 } else {
320 //Log.d(LOGTAG, packet.toString());
321 }
322 replaceContactInConversation(contact.getJid(), contact);
323 }
324 }
325 }
326 };
327
328 private OnIqPacketReceived unknownIqListener = new OnIqPacketReceived() {
329
330 @Override
331 public void onIqPacketReceived(Account account, IqPacket packet) {
332 if (packet.hasChild("query")) {
333 Element query = packet.findChild("query");
334 String xmlns = query.getAttribute("xmlns");
335 if ((xmlns != null) && (xmlns.equals("jabber:iq:roster"))) {
336 processRosterItems(account, query);
337 mergePhoneContactsWithRoster(null);
338 }
339 }
340 }
341 };
342
343 private OnJinglePacketReceived jingleListener = new OnJinglePacketReceived() {
344
345 @Override
346 public void onJinglePacketReceived(Account account, JinglePacket packet) {
347 Log.d(LOGTAG,account.getJid()+": jingle packet received"+packet.toString());
348 }
349 };
350
351 private OpenPgpServiceConnection pgpServiceConnection;
352 private PgpEngine mPgpEngine = null;
353 private Intent pingIntent;
354 private PendingIntent pendingPingIntent = null;
355 private WakeLock wakeLock;
356
357 public PgpEngine getPgpEngine() {
358 if (pgpServiceConnection.isBound()) {
359 if (this.mPgpEngine == null) {
360 this.mPgpEngine = new PgpEngine(new OpenPgpApi(
361 getApplicationContext(),
362 pgpServiceConnection.getService()));
363 }
364 return mPgpEngine;
365 } else {
366 return null;
367 }
368
369 }
370
371 protected Conversation findMuc(String name, Account account) {
372 for (Conversation conversation : this.conversations) {
373 if (conversation.getContactJid().split("/")[0].equals(name)
374 && (conversation.getAccount() == account)) {
375 return conversation;
376 }
377 }
378 return null;
379 }
380
381 private void processRosterItems(Account account, Element elements) {
382 String version = elements.getAttribute("ver");
383 if (version != null) {
384 account.setRosterVersion(version);
385 databaseBackend.updateAccount(account);
386 }
387 for (Element item : elements.getChildren()) {
388 if (item.getName().equals("item")) {
389 String jid = item.getAttribute("jid");
390 String subscription = item.getAttribute("subscription");
391 Contact contact = databaseBackend.findContact(account, jid);
392 if (contact == null) {
393 if (!subscription.equals("remove")) {
394 String name = item.getAttribute("name");
395 if (name == null) {
396 name = jid.split("@")[0];
397 }
398 contact = new Contact(account, name, jid, null);
399 contact.parseSubscriptionFromElement(item);
400 databaseBackend.createContact(contact);
401 }
402 } else {
403 if (subscription.equals("remove")) {
404 databaseBackend.deleteContact(contact);
405 replaceContactInConversation(contact.getJid(), null);
406 } else {
407 contact.parseSubscriptionFromElement(item);
408 databaseBackend.updateContact(contact);
409 replaceContactInConversation(contact.getJid(), contact);
410 }
411 }
412 }
413 }
414 }
415
416 private void replaceContactInConversation(String jid, Contact contact) {
417 List<Conversation> conversations = getConversations();
418 for (int i = 0; i < conversations.size(); ++i) {
419 if ((conversations.get(i).getContactJid().equals(jid))) {
420 conversations.get(i).setContact(contact);
421 break;
422 }
423 }
424 }
425
426 public class XmppConnectionBinder extends Binder {
427 public XmppConnectionService getService() {
428 return XmppConnectionService.this;
429 }
430 }
431
432 @Override
433 public int onStartCommand(Intent intent, int flags, int startId) {
434 this.wakeLock.acquire();
435 // Log.d(LOGTAG,"calling start service. caller was:"+intent.getAction());
436 ConnectivityManager cm = (ConnectivityManager) getApplicationContext()
437 .getSystemService(Context.CONNECTIVITY_SERVICE);
438 NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
439 boolean isConnected = activeNetwork != null
440 && activeNetwork.isConnected();
441
442 for (Account account : accounts) {
443 if (!account.isOptionSet(Account.OPTION_DISABLED)) {
444 if (!isConnected) {
445 account.setStatus(Account.STATUS_NO_INTERNET);
446 if (statusListener != null) {
447 statusListener.onStatusChanged(account);
448 }
449 } else {
450 if (account.getStatus() == Account.STATUS_NO_INTERNET) {
451 account.setStatus(Account.STATUS_OFFLINE);
452 if (statusListener != null) {
453 statusListener.onStatusChanged(account);
454 }
455 }
456
457 // TODO 3 remaining cases
458 if (account.getStatus() == Account.STATUS_ONLINE) {
459 long lastReceived = account.getXmppConnection().lastPaketReceived;
460 long lastSent = account.getXmppConnection().lastPingSent;
461 if (lastSent - lastReceived >= PING_TIMEOUT * 1000) {
462 Log.d(LOGTAG, account.getJid() + ": ping timeout");
463 this.reconnectAccount(account, true);
464 } else if (SystemClock.elapsedRealtime() - lastReceived >= PING_MIN_INTERVAL * 1000) {
465 account.getXmppConnection().sendPing();
466 account.getXmppConnection().lastPingSent = SystemClock
467 .elapsedRealtime();
468 this.scheduleWakeupCall(2, false);
469 }
470 } else if (account.getStatus() == Account.STATUS_OFFLINE) {
471 if (account.getXmppConnection() == null) {
472 account.setXmppConnection(this
473 .createConnection(account));
474 }
475 account.getXmppConnection().lastPingSent = SystemClock
476 .elapsedRealtime();
477 new Thread(account.getXmppConnection()).start();
478 } else if ((account.getStatus() == Account.STATUS_CONNECTING)
479 && ((SystemClock.elapsedRealtime() - account
480 .getXmppConnection().lastConnect) / 1000 >= CONNECT_TIMEOUT)) {
481 Log.d(LOGTAG, account.getJid()
482 + ": time out during connect reconnecting");
483 reconnectAccount(account, true);
484 } else {
485 Log.d(LOGTAG,
486 "seconds since last connect:"
487 + ((SystemClock.elapsedRealtime() - account
488 .getXmppConnection().lastConnect) / 1000));
489 Log.d(LOGTAG,
490 account.getJid() + ": status="
491 + account.getStatus());
492 // TODO notify user of ssl cert problem or auth problem
493 // or what ever
494 }
495 // in any case. reschedule wakup call
496 this.scheduleWakeupCall(PING_MAX_INTERVAL, true);
497 }
498 if (accountChangedListener != null) {
499 accountChangedListener.onAccountListChangedListener();
500 }
501 }
502 }
503 if (wakeLock.isHeld()) {
504 wakeLock.release();
505 }
506 return START_STICKY;
507 }
508
509 @Override
510 public void onCreate() {
511 ExceptionHelper.init(getApplicationContext());
512 databaseBackend = DatabaseBackend.getInstance(getApplicationContext());
513 this.accounts = databaseBackend.getAccounts();
514
515 this.getConversations();
516
517 getContentResolver().registerContentObserver(
518 ContactsContract.Contacts.CONTENT_URI, true, contactObserver);
519 this.pgpServiceConnection = new OpenPgpServiceConnection(
520 getApplicationContext(), "org.sufficientlysecure.keychain");
521 this.pgpServiceConnection.bindToService();
522
523 PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
524 this.wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
525 "XmppConnection");
526 }
527
528 @Override
529 public void onDestroy() {
530 super.onDestroy();
531 for (Account account : accounts) {
532 if (account.getXmppConnection() != null) {
533 disconnect(account, true);
534 }
535 }
536 }
537
538 protected void scheduleWakeupCall(int seconds, boolean ping) {
539 long timeToWake = SystemClock.elapsedRealtime() + seconds * 1000;
540 Context context = getApplicationContext();
541 AlarmManager alarmManager = (AlarmManager) context
542 .getSystemService(Context.ALARM_SERVICE);
543
544 if (ping) {
545 if (this.pingIntent == null) {
546 this.pingIntent = new Intent(context, EventReceiver.class);
547 this.pingIntent.setAction("ping");
548 this.pingIntent.putExtra("time", timeToWake);
549 this.pendingPingIntent = PendingIntent.getBroadcast(context, 0,
550 this.pingIntent, 0);
551 alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
552 timeToWake, pendingPingIntent);
553 // Log.d(LOGTAG,"schedule ping in "+seconds+" seconds");
554 } else {
555 long scheduledTime = this.pingIntent.getLongExtra("time", 0);
556 if (scheduledTime < SystemClock.elapsedRealtime()
557 || (scheduledTime > timeToWake)) {
558 this.pingIntent.putExtra("time", timeToWake);
559 alarmManager.cancel(this.pendingPingIntent);
560 this.pendingPingIntent = PendingIntent.getBroadcast(
561 context, 0, this.pingIntent, 0);
562 alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
563 timeToWake, pendingPingIntent);
564 // Log.d(LOGTAG,"reschedule old ping to ping in "+seconds+" seconds");
565 }
566 }
567 } else {
568 Intent intent = new Intent(context, EventReceiver.class);
569 intent.setAction("ping_check");
570 PendingIntent alarmIntent = PendingIntent.getBroadcast(context, 0,
571 intent, 0);
572 alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, timeToWake,
573 alarmIntent);
574 }
575
576 }
577
578 public XmppConnection createConnection(Account account) {
579 SharedPreferences sharedPref = PreferenceManager
580 .getDefaultSharedPreferences(getApplicationContext());
581 account.setResource(sharedPref.getString("resource", "mobile").toLowerCase(Locale.getDefault()));
582 XmppConnection connection = new XmppConnection(account, this.wakeLock);
583 connection.setOnMessagePacketReceivedListener(this.messageListener);
584 connection.setOnStatusChangedListener(this.statusListener);
585 connection.setOnPresencePacketReceivedListener(this.presenceListener);
586 connection
587 .setOnUnregisteredIqPacketReceivedListener(this.unknownIqListener);
588 connection.setOnJinglePacketReceivedListener(this.jingleListener);
589 connection
590 .setOnTLSExceptionReceivedListener(new OnTLSExceptionReceived() {
591
592 @Override
593 public void onTLSExceptionReceived(String fingerprint,
594 Account account) {
595 Log.d(LOGTAG, "tls exception arrived in service");
596 if (tlsException != null) {
597 tlsException.onTLSExceptionReceived(fingerprint,
598 account);
599 }
600 }
601 });
602 connection.setOnBindListener(new OnBindListener() {
603
604 @Override
605 public void onBind(Account account) {
606 databaseBackend.clearPresences(account);
607 account.clearPresences(); // self presences
608 if (account.getXmppConnection().hasFeatureRosterManagment()) {
609 updateRoster(account, null);
610 }
611 connectMultiModeConversations(account);
612 if (convChangedListener != null) {
613 convChangedListener.onConversationListChanged();
614 }
615 }
616 });
617 return connection;
618 }
619
620 synchronized public void sendMessage(Message message, String presence) {
621 Account account = message.getConversation().getAccount();
622 Conversation conv = message.getConversation();
623 boolean saveInDb = false;
624 boolean addToConversation = false;
625 if (account.getStatus() == Account.STATUS_ONLINE) {
626 MessagePacket packet;
627 if (message.getEncryption() == Message.ENCRYPTION_OTR) {
628 if (!conv.hasValidOtrSession()) {
629 // starting otr session. messages will be send later
630 conv.startOtrSession(getApplicationContext(), presence,true);
631 } else if (conv.getOtrSession().getSessionStatus() == SessionStatus.ENCRYPTED) {
632 // otr session aleary exists, creating message packet
633 // accordingly
634 packet = prepareMessagePacket(account, message,
635 conv.getOtrSession());
636 account.getXmppConnection().sendMessagePacket(packet);
637 message.setStatus(Message.STATUS_SEND);
638 }
639 saveInDb = true;
640 addToConversation = true;
641 } else if (message.getEncryption() == Message.ENCRYPTION_PGP) {
642 message.getConversation().endOtrIfNeeded();
643 long keyId = message.getConversation().getContact()
644 .getPgpKeyId();
645 packet = new MessagePacket();
646 packet.setType(MessagePacket.TYPE_CHAT);
647 packet.setFrom(message.getConversation().getAccount()
648 .getFullJid());
649 packet.setTo(message.getCounterpart());
650 packet.setBody("This is an XEP-0027 encryted message");
651 packet.addChild("x","jabber:x:encrypted").setContent(this.getPgpEngine().encrypt(keyId,
652 message.getBody()));
653 account.getXmppConnection().sendMessagePacket(packet);
654 message.setStatus(Message.STATUS_SEND);
655 message.setEncryption(Message.ENCRYPTION_DECRYPTED);
656 saveInDb = true;
657 addToConversation = true;
658 } else {
659 message.getConversation().endOtrIfNeeded();
660 // don't encrypt
661 if (message.getConversation().getMode() == Conversation.MODE_SINGLE) {
662 message.setStatus(Message.STATUS_SEND);
663 saveInDb = true;
664 addToConversation = true;
665 }
666
667 packet = prepareMessagePacket(account, message, null);
668 account.getXmppConnection().sendMessagePacket(packet);
669 }
670 } else {
671 // account is offline
672 saveInDb = true;
673 addToConversation = true;
674
675 }
676 if (saveInDb) {
677 databaseBackend.createMessage(message);
678 }
679 if (addToConversation) {
680 conv.getMessages().add(message);
681 if (convChangedListener != null) {
682 convChangedListener.onConversationListChanged();
683 }
684 }
685
686 }
687
688 private void sendUnsendMessages(Conversation conversation) {
689 for (int i = 0; i < conversation.getMessages().size(); ++i) {
690 if ((conversation.getMessages().get(i).getStatus() == Message.STATUS_UNSEND)&&(conversation.getMessages().get(i).getEncryption() == Message.ENCRYPTION_NONE)) {
691 Message message = conversation.getMessages().get(i);
692 MessagePacket packet = prepareMessagePacket(
693 conversation.getAccount(), message, null);
694 conversation.getAccount().getXmppConnection()
695 .sendMessagePacket(packet);
696 message.setStatus(Message.STATUS_SEND);
697 if (conversation.getMode() == Conversation.MODE_SINGLE) {
698 databaseBackend.updateMessage(message);
699 } else {
700 databaseBackend.deleteMessage(message);
701 conversation.getMessages().remove(i);
702 i--;
703 }
704 }
705 }
706 }
707
708 public MessagePacket prepareMessagePacket(Account account, Message message,
709 Session otrSession) {
710 MessagePacket packet = new MessagePacket();
711 if (message.getConversation().getMode() == Conversation.MODE_SINGLE) {
712 packet.setType(MessagePacket.TYPE_CHAT);
713 if (otrSession != null) {
714 try {
715 packet.setBody(otrSession.transformSending(message
716 .getBody()));
717 } catch (OtrException e) {
718 Log.d(LOGTAG,
719 account.getJid()
720 + ": could not encrypt message to "
721 + message.getCounterpart());
722 }
723 packet.addChild("private","urn:xmpp:carbons:2");
724 packet.addChild("no-copy","urn:xmpp:hints");
725 packet.setTo(otrSession.getSessionID().getAccountID() + "/"
726 + otrSession.getSessionID().getUserID());
727 packet.setFrom(account.getFullJid());
728 } else {
729 packet.setBody(message.getBody());
730 packet.setTo(message.getCounterpart());
731 packet.setFrom(account.getJid());
732 }
733 } else if (message.getConversation().getMode() == Conversation.MODE_MULTI) {
734 packet.setType(MessagePacket.TYPE_GROUPCHAT);
735 packet.setBody(message.getBody());
736 packet.setTo(message.getCounterpart().split("/")[0]);
737 packet.setFrom(account.getJid());
738 }
739 return packet;
740 }
741
742 private void getRoster(Account account,
743 final OnRosterFetchedListener listener) {
744 List<Contact> contacts = databaseBackend.getContactsByAccount(account);
745 for (int i = 0; i < contacts.size(); ++i) {
746 contacts.get(i).setAccount(account);
747 }
748 if (listener != null) {
749 listener.onRosterFetched(contacts);
750 }
751 }
752
753 public List<Contact> getRoster(Account account) {
754 List<Contact> contacts = databaseBackend.getContactsByAccount(account);
755 for (int i = 0; i < contacts.size(); ++i) {
756 contacts.get(i).setAccount(account);
757 }
758 return contacts;
759 }
760
761 public void updateRoster(final Account account,
762 final OnRosterFetchedListener listener) {
763 IqPacket iqPacket = new IqPacket(IqPacket.TYPE_GET);
764 if (!"".equals(account.getRosterVersion())) {
765 Log.d(LOGTAG, account.getJid() + ": fetching roster version "
766 + account.getRosterVersion());
767 } else {
768 Log.d(LOGTAG, account.getJid() + ": fetching roster");
769 }
770 iqPacket.query("jabber:iq:roster").setAttribute("ver", account.getRosterVersion());
771 account.getXmppConnection().sendIqPacket(iqPacket,
772 new OnIqPacketReceived() {
773
774 @Override
775 public void onIqPacketReceived(final Account account,
776 IqPacket packet) {
777 Element roster = packet.findChild("query");
778 if (roster != null) {
779 Log.d(LOGTAG, account.getJid()
780 + ": processing roster");
781 processRosterItems(account, roster);
782 StringBuilder mWhere = new StringBuilder();
783 mWhere.append("jid NOT IN(");
784 List<Element> items = roster.getChildren();
785 for (int i = 0; i < items.size(); ++i) {
786 mWhere.append(DatabaseUtils
787 .sqlEscapeString(items.get(i)
788 .getAttribute("jid")));
789 if (i != items.size() - 1) {
790 mWhere.append(",");
791 }
792 }
793 mWhere.append(") and accountUuid = \"");
794 mWhere.append(account.getUuid());
795 mWhere.append("\"");
796 List<Contact> contactsToDelete = databaseBackend
797 .getContacts(mWhere.toString());
798 for (Contact contact : contactsToDelete) {
799 databaseBackend.deleteContact(contact);
800 replaceContactInConversation(contact.getJid(),
801 null);
802 }
803
804 } else {
805 Log.d(LOGTAG, account.getJid()
806 + ": empty roster returend");
807 }
808 mergePhoneContactsWithRoster(new OnPhoneContactsMerged() {
809
810 @Override
811 public void phoneContactsMerged() {
812 if (listener != null) {
813 getRoster(account, listener);
814 }
815 }
816 });
817 }
818 });
819 }
820
821 public void mergePhoneContactsWithRoster(
822 final OnPhoneContactsMerged listener) {
823 PhoneHelper.loadPhoneContacts(getApplicationContext(),
824 new OnPhoneContactsLoadedListener() {
825 @Override
826 public void onPhoneContactsLoaded(
827 Hashtable<String, Bundle> phoneContacts) {
828 List<Contact> contacts = databaseBackend
829 .getContactsByAccount(null);
830 for (int i = 0; i < contacts.size(); ++i) {
831 Contact contact = contacts.get(i);
832 if (phoneContacts.containsKey(contact.getJid())) {
833 Bundle phoneContact = phoneContacts.get(contact
834 .getJid());
835 String systemAccount = phoneContact
836 .getInt("phoneid")
837 + "#"
838 + phoneContact.getString("lookup");
839 contact.setSystemAccount(systemAccount);
840 contact.setPhotoUri(phoneContact
841 .getString("photouri"));
842 contact.setDisplayName(phoneContact
843 .getString("displayname"));
844 databaseBackend.updateContact(contact);
845 replaceContactInConversation(contact.getJid(),
846 contact);
847 } else {
848 if ((contact.getSystemAccount() != null)
849 || (contact.getProfilePhoto() != null)) {
850 contact.setSystemAccount(null);
851 contact.setPhotoUri(null);
852 databaseBackend.updateContact(contact);
853 replaceContactInConversation(
854 contact.getJid(), contact);
855 }
856 }
857 }
858 if (listener != null) {
859 listener.phoneContactsMerged();
860 }
861 }
862 });
863 }
864
865 public List<Conversation> getConversations() {
866 if (this.conversations == null) {
867 Hashtable<String, Account> accountLookupTable = new Hashtable<String, Account>();
868 for (Account account : this.accounts) {
869 accountLookupTable.put(account.getUuid(), account);
870 }
871 this.conversations = databaseBackend
872 .getConversations(Conversation.STATUS_AVAILABLE);
873 for (Conversation conv : this.conversations) {
874 Account account = accountLookupTable.get(conv.getAccountUuid());
875 conv.setAccount(account);
876 conv.setContact(findContact(account, conv.getContactJid()));
877 conv.setMessages(databaseBackend.getMessages(conv, 50));
878 }
879 }
880 Collections.sort(this.conversations, new Comparator<Conversation>() {
881 @Override
882 public int compare(Conversation lhs, Conversation rhs) {
883 return (int) (rhs.getLatestMessage().getTimeSent() - lhs
884 .getLatestMessage().getTimeSent());
885 }
886 });
887 return this.conversations;
888 }
889
890 public List<Account> getAccounts() {
891 return this.accounts;
892 }
893
894 public Contact findContact(Account account, String jid) {
895 Contact contact = databaseBackend.findContact(account, jid);
896 if (contact != null) {
897 contact.setAccount(account);
898 }
899 return contact;
900 }
901
902 public Conversation findOrCreateConversation(Account account, String jid,
903 boolean muc) {
904 for (Conversation conv : this.getConversations()) {
905 if ((conv.getAccount().equals(account))
906 && (conv.getContactJid().split("/")[0].equals(jid))) {
907 return conv;
908 }
909 }
910 Conversation conversation = databaseBackend.findConversation(account,
911 jid);
912 if (conversation != null) {
913 conversation.setStatus(Conversation.STATUS_AVAILABLE);
914 conversation.setAccount(account);
915 if (muc) {
916 conversation.setMode(Conversation.MODE_MULTI);
917 } else {
918 conversation.setMode(Conversation.MODE_SINGLE);
919 }
920 conversation.setMessages(databaseBackend.getMessages(conversation,
921 50));
922 this.databaseBackend.updateConversation(conversation);
923 conversation.setContact(findContact(account,
924 conversation.getContactJid()));
925 } else {
926 String conversationName;
927 Contact contact = findContact(account, jid);
928 if (contact != null) {
929 conversationName = contact.getDisplayName();
930 } else {
931 conversationName = jid.split("@")[0];
932 }
933 if (muc) {
934 conversation = new Conversation(conversationName, account, jid,
935 Conversation.MODE_MULTI);
936 } else {
937 conversation = new Conversation(conversationName, account, jid,
938 Conversation.MODE_SINGLE);
939 }
940 conversation.setContact(contact);
941 this.databaseBackend.createConversation(conversation);
942 }
943 this.conversations.add(conversation);
944 if ((account.getStatus() == Account.STATUS_ONLINE)&&(conversation.getMode() == Conversation.MODE_MULTI)) {
945 joinMuc(conversation);
946 }
947 if (this.convChangedListener != null) {
948 this.convChangedListener.onConversationListChanged();
949 }
950 return conversation;
951 }
952
953 public void archiveConversation(Conversation conversation) {
954 if (conversation.getMode() == Conversation.MODE_MULTI) {
955 leaveMuc(conversation);
956 } else {
957 conversation.endOtrIfNeeded();
958 }
959 this.databaseBackend.updateConversation(conversation);
960 this.conversations.remove(conversation);
961 if (this.convChangedListener != null) {
962 this.convChangedListener.onConversationListChanged();
963 }
964 }
965
966 public int getConversationCount() {
967 return this.databaseBackend.getConversationCount();
968 }
969
970 public void createAccount(Account account) {
971 databaseBackend.createAccount(account);
972 this.accounts.add(account);
973 this.reconnectAccount(account, false);
974 if (accountChangedListener != null)
975 accountChangedListener.onAccountListChangedListener();
976 }
977
978 public void deleteContact(Contact contact) {
979 IqPacket iq = new IqPacket(IqPacket.TYPE_SET);
980 Element query = iq.query("jabber:iq:roster");
981 query.addChild("item").setAttribute("jid", contact.getJid()).setAttribute("subscription", "remove");
982 contact.getAccount().getXmppConnection().sendIqPacket(iq, null);
983 replaceContactInConversation(contact.getJid(), null);
984 databaseBackend.deleteContact(contact);
985 }
986
987 public void updateAccount(Account account) {
988 databaseBackend.updateAccount(account);
989 reconnectAccount(account, false);
990 if (accountChangedListener != null)
991 accountChangedListener.onAccountListChangedListener();
992 }
993
994 public void deleteAccount(Account account) {
995 if (account.getXmppConnection() != null) {
996 this.disconnect(account, true);
997 }
998 databaseBackend.deleteAccount(account);
999 this.accounts.remove(account);
1000 if (accountChangedListener != null)
1001 accountChangedListener.onAccountListChangedListener();
1002 }
1003
1004 public void setOnConversationListChangedListener(
1005 OnConversationListChangedListener listener) {
1006 this.convChangedListener = listener;
1007 }
1008
1009 public void removeOnConversationListChangedListener() {
1010 this.convChangedListener = null;
1011 }
1012
1013 public void setOnAccountListChangedListener(
1014 OnAccountListChangedListener listener) {
1015 this.accountChangedListener = listener;
1016 }
1017
1018 public void removeOnAccountListChangedListener() {
1019 this.accountChangedListener = null;
1020 }
1021
1022 public void connectMultiModeConversations(Account account) {
1023 List<Conversation> conversations = getConversations();
1024 for (int i = 0; i < conversations.size(); i++) {
1025 Conversation conversation = conversations.get(i);
1026 if ((conversation.getMode() == Conversation.MODE_MULTI)
1027 && (conversation.getAccount() == account)) {
1028 joinMuc(conversation);
1029 }
1030 }
1031 }
1032
1033 public void joinMuc(Conversation conversation) {
1034 String[] mucParts = conversation.getContactJid().split("/");
1035 String muc;
1036 String nick;
1037 if (mucParts.length == 2) {
1038 muc = mucParts[0];
1039 nick = mucParts[1];
1040 } else {
1041 muc = mucParts[0];
1042 nick = conversation.getAccount().getUsername();
1043 }
1044 PresencePacket packet = new PresencePacket();
1045 packet.setAttribute("to", muc + "/" + nick);
1046 Element x = new Element("x");
1047 x.setAttribute("xmlns", "http://jabber.org/protocol/muc");
1048 if (conversation.getMessages().size() != 0) {
1049 long lastMsgTime = conversation.getLatestMessage().getTimeSent();
1050 long diff = (System.currentTimeMillis() - lastMsgTime) / 1000 - 1;
1051 x.addChild("history").setAttribute("seconds", diff + "");
1052 }
1053 packet.addChild(x);
1054 Log.d(LOGTAG,conversation.getAccount().getJid()+": joining muc "+packet.toString());
1055 conversation.getAccount().getXmppConnection()
1056 .sendPresencePacket(packet);
1057 }
1058
1059 private OnRenameListener renameListener = null;
1060
1061 public void setOnRenameListener(OnRenameListener listener) {
1062 this.renameListener = listener;
1063 }
1064
1065 public void renameInMuc(final Conversation conversation, final String nick) {
1066 final MucOptions options = conversation.getMucOptions();
1067 if (options.online()) {
1068 options.setOnRenameListener(new OnRenameListener() {
1069
1070 @Override
1071 public void onRename(boolean success) {
1072 if (renameListener != null) {
1073 renameListener.onRename(success);
1074 }
1075 if (success) {
1076 databaseBackend.updateConversation(conversation);
1077 }
1078 }
1079 });
1080 options.flagAboutToRename();
1081 PresencePacket packet = new PresencePacket();
1082 packet.setAttribute("to",
1083 conversation.getContactJid().split("/")[0] + "/" + nick);
1084 packet.setAttribute("from", conversation.getAccount().getFullJid());
1085
1086 conversation.getAccount().getXmppConnection()
1087 .sendPresencePacket(packet, null);
1088 } else {
1089 String jid = conversation.getContactJid().split("/")[0] + "/"
1090 + nick;
1091 conversation.setContactJid(jid);
1092 databaseBackend.updateConversation(conversation);
1093 if (conversation.getAccount().getStatus() == Account.STATUS_ONLINE) {
1094 joinMuc(conversation);
1095 }
1096 }
1097 }
1098
1099 public void leaveMuc(Conversation conversation) {
1100 PresencePacket packet = new PresencePacket();
1101 packet.setAttribute("to", conversation.getContactJid().split("/")[0] + "/" + conversation.getMucOptions().getNick());
1102 packet.setAttribute("from", conversation.getAccount().getFullJid());
1103 packet.setAttribute("type", "unavailable");
1104 Log.d(LOGTAG,"send leaving muc " + packet);
1105 conversation.getAccount().getXmppConnection()
1106 .sendPresencePacket(packet);
1107 conversation.getMucOptions().setOffline();
1108 }
1109
1110 public void disconnect(Account account, boolean force) {
1111 if ((account.getStatus() == Account.STATUS_ONLINE)
1112 || (account.getStatus() == Account.STATUS_DISABLED)) {
1113 if (!force) {
1114 List<Conversation> conversations = getConversations();
1115 for (int i = 0; i < conversations.size(); i++) {
1116 Conversation conversation = conversations.get(i);
1117 if (conversation.getAccount() == account) {
1118 if (conversation.getMode() == Conversation.MODE_MULTI) {
1119 leaveMuc(conversation);
1120 } else {
1121 conversation.endOtrIfNeeded();
1122 }
1123 }
1124 }
1125 }
1126 account.getXmppConnection().disconnect(force);
1127 }
1128 }
1129
1130 @Override
1131 public IBinder onBind(Intent intent) {
1132 return mBinder;
1133 }
1134
1135 public void updateContact(Contact contact) {
1136 databaseBackend.updateContact(contact);
1137 replaceContactInConversation(contact.getJid(), contact);
1138 }
1139
1140 public void updateMessage(Message message) {
1141 databaseBackend.updateMessage(message);
1142 }
1143
1144 public void createContact(Contact contact) {
1145 SharedPreferences sharedPref = PreferenceManager
1146 .getDefaultSharedPreferences(getApplicationContext());
1147 boolean autoGrant = sharedPref.getBoolean("grant_new_contacts", true);
1148 if (autoGrant) {
1149 contact.setSubscriptionOption(Contact.Subscription.PREEMPTIVE_GRANT);
1150 contact.setSubscriptionOption(Contact.Subscription.ASKING);
1151 }
1152 databaseBackend.createContact(contact);
1153 IqPacket iq = new IqPacket(IqPacket.TYPE_SET);
1154 Element query = new Element("query");
1155 query.setAttribute("xmlns", "jabber:iq:roster");
1156 Element item = new Element("item");
1157 item.setAttribute("jid", contact.getJid());
1158 item.setAttribute("name", contact.getJid());
1159 query.addChild(item);
1160 iq.addChild(query);
1161 Account account = contact.getAccount();
1162 account.getXmppConnection().sendIqPacket(iq, null);
1163 if (autoGrant) {
1164 requestPresenceUpdatesFrom(contact);
1165 }
1166 replaceContactInConversation(contact.getJid(), contact);
1167 }
1168
1169 public void requestPresenceUpdatesFrom(Contact contact) {
1170 // Requesting a Subscription type=subscribe
1171 PresencePacket packet = new PresencePacket();
1172 packet.setAttribute("type", "subscribe");
1173 packet.setAttribute("to", contact.getJid());
1174 packet.setAttribute("from", contact.getAccount().getJid());
1175 Log.d(LOGTAG, packet.toString());
1176 contact.getAccount().getXmppConnection().sendPresencePacket(packet);
1177 }
1178
1179 public void stopPresenceUpdatesFrom(Contact contact) {
1180 // Unsubscribing type='unsubscribe'
1181 PresencePacket packet = new PresencePacket();
1182 packet.setAttribute("type", "unsubscribe");
1183 packet.setAttribute("to", contact.getJid());
1184 packet.setAttribute("from", contact.getAccount().getJid());
1185 Log.d(LOGTAG, packet.toString());
1186 contact.getAccount().getXmppConnection().sendPresencePacket(packet);
1187 }
1188
1189 public void stopPresenceUpdatesTo(Contact contact) {
1190 // Canceling a Subscription type=unsubscribed
1191 PresencePacket packet = new PresencePacket();
1192 packet.setAttribute("type", "unsubscribed");
1193 packet.setAttribute("to", contact.getJid());
1194 packet.setAttribute("from", contact.getAccount().getJid());
1195 Log.d(LOGTAG, packet.toString());
1196 contact.getAccount().getXmppConnection().sendPresencePacket(packet);
1197 }
1198
1199 public void sendPresenceUpdatesTo(Contact contact) {
1200 // type='subscribed'
1201 PresencePacket packet = new PresencePacket();
1202 packet.setAttribute("type", "subscribed");
1203 packet.setAttribute("to", contact.getJid());
1204 packet.setAttribute("from", contact.getAccount().getJid());
1205 Log.d(LOGTAG, packet.toString());
1206 contact.getAccount().getXmppConnection().sendPresencePacket(packet);
1207 }
1208
1209 public void sendPgpPresence(Account account, String signature) {
1210 PresencePacket packet = new PresencePacket();
1211 packet.setAttribute("from", account.getFullJid());
1212 Element status = new Element("status");
1213 status.setContent("online");
1214 packet.addChild(status);
1215 Element x = new Element("x");
1216 x.setAttribute("xmlns", "jabber:x:signed");
1217 x.setContent(signature);
1218 packet.addChild(x);
1219 account.getXmppConnection().sendPresencePacket(packet);
1220 }
1221
1222 public void generatePgpAnnouncement(Account account)
1223 throws PgpEngine.UserInputRequiredException {
1224 if (account.getStatus() == Account.STATUS_ONLINE) {
1225 String signature = getPgpEngine().generateSignature("online");
1226 account.setKey("pgp_signature", signature);
1227 databaseBackend.updateAccount(account);
1228 sendPgpPresence(account, signature);
1229 }
1230 }
1231
1232 public void updateConversation(Conversation conversation) {
1233 this.databaseBackend.updateConversation(conversation);
1234 }
1235
1236 public Contact findContact(String uuid) {
1237 Contact contact = this.databaseBackend.getContact(uuid);
1238 for (Account account : getAccounts()) {
1239 if (contact.getAccountUuid().equals(account.getUuid())) {
1240 contact.setAccount(account);
1241 }
1242 }
1243 return contact;
1244 }
1245
1246 public void removeOnTLSExceptionReceivedListener() {
1247 this.tlsException = null;
1248 }
1249
1250 // TODO dont let thread sleep but schedule wake up
1251 public void reconnectAccount(final Account account, final boolean force) {
1252 new Thread(new Runnable() {
1253
1254 @Override
1255 public void run() {
1256 if (account.getXmppConnection() != null) {
1257 disconnect(account, force);
1258 }
1259 if (!account.isOptionSet(Account.OPTION_DISABLED)) {
1260 if (account.getXmppConnection() == null) {
1261 account.setXmppConnection(createConnection(account));
1262 }
1263 Thread thread = new Thread(account.getXmppConnection());
1264 thread.start();
1265 scheduleWakeupCall((int) (CONNECT_TIMEOUT * 1.2), false);
1266 }
1267 }
1268 }).start();
1269 }
1270
1271 public void updateConversationInGui() {
1272 if (convChangedListener != null) {
1273 convChangedListener.onConversationListChanged();
1274 }
1275 }
1276
1277 public void sendConversationSubject(Conversation conversation,
1278 String subject) {
1279 MessagePacket packet = new MessagePacket();
1280 packet.setType(MessagePacket.TYPE_GROUPCHAT);
1281 packet.setTo(conversation.getContactJid().split("/")[0]);
1282 Element subjectChild = new Element("subject");
1283 subjectChild.setContent(subject);
1284 packet.addChild(subjectChild);
1285 packet.setFrom(conversation.getAccount().getJid());
1286 Account account = conversation.getAccount();
1287 if (account.getStatus() == Account.STATUS_ONLINE) {
1288 account.getXmppConnection().sendMessagePacket(packet);
1289 }
1290 }
1291
1292 public void inviteToConference(Conversation conversation,
1293 List<Contact> contacts) {
1294 for (Contact contact : contacts) {
1295 MessagePacket packet = new MessagePacket();
1296 packet.setTo(conversation.getContactJid().split("/")[0]);
1297 packet.setFrom(conversation.getAccount().getFullJid());
1298 Element x = new Element("x");
1299 x.setAttribute("xmlns", "http://jabber.org/protocol/muc#user");
1300 Element invite = new Element("invite");
1301 invite.setAttribute("to", contact.getJid());
1302 x.addChild(invite);
1303 packet.addChild(x);
1304 Log.d(LOGTAG, packet.toString());
1305 conversation.getAccount().getXmppConnection()
1306 .sendMessagePacket(packet);
1307 }
1308
1309 }
1310}