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