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