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