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