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