1package eu.siacs.conversations.services;
2
3import java.security.SecureRandom;
4import java.text.SimpleDateFormat;
5import java.util.ArrayList;
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.TimeZone;
13import java.util.concurrent.CopyOnWriteArrayList;
14
15import org.openintents.openpgp.util.OpenPgpApi;
16import org.openintents.openpgp.util.OpenPgpServiceConnection;
17
18import de.duenndns.ssl.MemorizingTrustManager;
19
20import net.java.otr4j.OtrException;
21import net.java.otr4j.session.Session;
22import net.java.otr4j.session.SessionStatus;
23import eu.siacs.conversations.crypto.PgpEngine;
24import eu.siacs.conversations.entities.Account;
25import eu.siacs.conversations.entities.Bookmark;
26import eu.siacs.conversations.entities.Contact;
27import eu.siacs.conversations.entities.Conversation;
28import eu.siacs.conversations.entities.Message;
29import eu.siacs.conversations.entities.MucOptions;
30import eu.siacs.conversations.entities.MucOptions.OnRenameListener;
31import eu.siacs.conversations.entities.Presences;
32import eu.siacs.conversations.generator.IqGenerator;
33import eu.siacs.conversations.generator.MessageGenerator;
34import eu.siacs.conversations.generator.PresenceGenerator;
35import eu.siacs.conversations.parser.IqParser;
36import eu.siacs.conversations.parser.MessageParser;
37import eu.siacs.conversations.parser.PresenceParser;
38import eu.siacs.conversations.persistance.DatabaseBackend;
39import eu.siacs.conversations.persistance.FileBackend;
40import eu.siacs.conversations.ui.UiCallback;
41import eu.siacs.conversations.utils.CryptoHelper;
42import eu.siacs.conversations.utils.ExceptionHelper;
43import eu.siacs.conversations.utils.OnPhoneContactsLoadedListener;
44import eu.siacs.conversations.utils.PRNGFixes;
45import eu.siacs.conversations.utils.PhoneHelper;
46import eu.siacs.conversations.utils.UIHelper;
47import eu.siacs.conversations.xml.Element;
48import eu.siacs.conversations.xmpp.OnBindListener;
49import eu.siacs.conversations.xmpp.OnContactStatusChanged;
50import eu.siacs.conversations.xmpp.OnIqPacketReceived;
51import eu.siacs.conversations.xmpp.OnStatusChanged;
52import eu.siacs.conversations.xmpp.OnTLSExceptionReceived;
53import eu.siacs.conversations.xmpp.XmppConnection;
54import eu.siacs.conversations.xmpp.jingle.JingleConnectionManager;
55import eu.siacs.conversations.xmpp.jingle.OnJinglePacketReceived;
56import eu.siacs.conversations.xmpp.jingle.stanzas.JinglePacket;
57import eu.siacs.conversations.xmpp.stanzas.IqPacket;
58import eu.siacs.conversations.xmpp.stanzas.MessagePacket;
59import eu.siacs.conversations.xmpp.stanzas.PresencePacket;
60import android.annotation.SuppressLint;
61import android.app.AlarmManager;
62import android.app.PendingIntent;
63import android.app.Service;
64import android.content.Context;
65import android.content.Intent;
66import android.content.SharedPreferences;
67import android.database.ContentObserver;
68import android.net.ConnectivityManager;
69import android.net.NetworkInfo;
70import android.net.Uri;
71import android.os.Binder;
72import android.os.Bundle;
73import android.os.IBinder;
74import android.os.PowerManager;
75import android.os.PowerManager.WakeLock;
76import android.os.SystemClock;
77import android.preference.PreferenceManager;
78import android.provider.ContactsContract;
79import android.util.Log;
80
81public class XmppConnectionService extends Service {
82
83 protected static final String LOGTAG = "xmppService";
84 public DatabaseBackend databaseBackend;
85 private FileBackend fileBackend;
86
87 public long startDate;
88
89 private static final int PING_MAX_INTERVAL = 300;
90 private static final int PING_MIN_INTERVAL = 30;
91 private static final int PING_TIMEOUT = 10;
92 private static final int CONNECT_TIMEOUT = 90;
93 public static final long CARBON_GRACE_PERIOD = 60000L;
94
95 private static String ACTION_MERGE_PHONE_CONTACTS = "merge_phone_contacts";
96
97 private MemorizingTrustManager mMemorizingTrustManager;
98
99 private MessageParser mMessageParser = new MessageParser(this);
100 private PresenceParser mPresenceParser = new PresenceParser(this);
101 private IqParser mIqParser = new IqParser(this);
102 private MessageGenerator mMessageGenerator = new MessageGenerator();
103 private PresenceGenerator mPresenceGenerator = new PresenceGenerator();
104
105 private List<Account> accounts;
106 private CopyOnWriteArrayList<Conversation> conversations = null;
107 private JingleConnectionManager mJingleConnectionManager = new JingleConnectionManager(
108 this);
109
110 private OnConversationUpdate mOnConversationUpdate = null;
111 private int convChangedListenerCount = 0;
112 private OnAccountUpdate mOnAccountUpdate = null;
113 private OnRosterUpdate mOnRosterUpdate = null;
114 public OnContactStatusChanged onContactStatusChanged = new OnContactStatusChanged() {
115
116 @Override
117 public void onContactStatusChanged(Contact contact, boolean online) {
118 Conversation conversation = find(getConversations(),contact);
119 if (conversation != null) {
120 conversation.endOtrIfNeeded();
121 if (online && (contact.getPresences().size() == 1)) {
122 sendUnsendMessages(conversation);
123 }
124 }
125 }
126 };
127
128 private SecureRandom mRandom;
129
130 private ContentObserver contactObserver = new ContentObserver(null) {
131 @Override
132 public void onChange(boolean selfChange) {
133 super.onChange(selfChange);
134 Intent intent = new Intent(getApplicationContext(),
135 XmppConnectionService.class);
136 intent.setAction(ACTION_MERGE_PHONE_CONTACTS);
137 startService(intent);
138 }
139 };
140
141 private final IBinder mBinder = new XmppConnectionBinder();
142 private OnStatusChanged statusListener = new OnStatusChanged() {
143
144 @Override
145 public void onStatusChanged(Account account) {
146 if (mOnAccountUpdate != null) {
147 mOnAccountUpdate.onAccountUpdate();;
148 }
149 if (account.getStatus() == Account.STATUS_ONLINE) {
150 for(Conversation conversation : account.pendingConferenceLeaves) {
151 leaveMuc(conversation);
152 }
153 for(Conversation conversation : account.pendingConferenceJoins) {
154 joinMuc(conversation);
155 }
156 mJingleConnectionManager.cancelInTransmission();
157 List<Conversation> conversations = getConversations();
158 for (int i = 0; i < conversations.size(); ++i) {
159 if (conversations.get(i).getAccount() == account) {
160 conversations.get(i).startOtrIfNeeded();
161 sendUnsendMessages(conversations.get(i));
162 }
163 }
164 syncDirtyContacts(account);
165 scheduleWakeupCall(PING_MAX_INTERVAL, true);
166 } else if (account.getStatus() == Account.STATUS_OFFLINE) {
167 if (!account.isOptionSet(Account.OPTION_DISABLED)) {
168 int timeToReconnect = mRandom.nextInt(50) + 10;
169 scheduleWakeupCall(timeToReconnect, false);
170 }
171
172 } else if (account.getStatus() == Account.STATUS_REGISTRATION_SUCCESSFULL) {
173 databaseBackend.updateAccount(account);
174 reconnectAccount(account, true);
175 } else if ((account.getStatus() != Account.STATUS_CONNECTING)
176 && (account.getStatus() != Account.STATUS_NO_INTERNET)) {
177 int next = account.getXmppConnection().getTimeToNextAttempt();
178 Log.d(LOGTAG, account.getJid()
179 + ": error connecting account. try again in " + next
180 + "s for the "
181 + (account.getXmppConnection().getAttempt() + 1)
182 + " time");
183 scheduleWakeupCall(next, false);
184 }
185 UIHelper.showErrorNotification(getApplicationContext(),
186 getAccounts());
187 }
188 };
189
190 private OnJinglePacketReceived jingleListener = new OnJinglePacketReceived() {
191
192 @Override
193 public void onJinglePacketReceived(Account account, JinglePacket packet) {
194 mJingleConnectionManager.deliverPacket(account, packet);
195 }
196 };
197
198 private OpenPgpServiceConnection pgpServiceConnection;
199 private PgpEngine mPgpEngine = null;
200 private Intent pingIntent;
201 private PendingIntent pendingPingIntent = null;
202 private WakeLock wakeLock;
203 private PowerManager pm;
204 private OnBindListener mOnBindListener = new OnBindListener() {
205
206 @Override
207 public void onBind(final Account account) {
208 account.getRoster().clearPresences();
209 account.clearPresences(); // self presences
210 account.pendingConferenceJoins.clear();
211 account.pendingConferenceLeaves.clear();
212 fetchRosterFromServer(account);
213 fetchBookmarks(account);
214 sendPresencePacket(account, mPresenceGenerator.sendPresence(account));
215 connectMultiModeConversations(account);
216 updateConversationUi();
217 }
218 };
219
220 public PgpEngine getPgpEngine() {
221 if (pgpServiceConnection.isBound()) {
222 if (this.mPgpEngine == null) {
223 this.mPgpEngine = new PgpEngine(new OpenPgpApi(
224 getApplicationContext(),
225 pgpServiceConnection.getService()), this);
226 }
227 return mPgpEngine;
228 } else {
229 return null;
230 }
231
232 }
233
234 public FileBackend getFileBackend() {
235 return this.fileBackend;
236 }
237
238 public Message attachImageToConversation(final Conversation conversation,
239 final Uri uri, final UiCallback<Message> callback) {
240 final Message message;
241 if (conversation.getNextEncryption() == Message.ENCRYPTION_PGP) {
242 message = new Message(conversation, "",
243 Message.ENCRYPTION_DECRYPTED);
244 } else {
245 message = new Message(conversation, "",
246 conversation.getNextEncryption());
247 }
248 message.setPresence(conversation.getNextPresence());
249 message.setType(Message.TYPE_IMAGE);
250 message.setStatus(Message.STATUS_OFFERED);
251 new Thread(new Runnable() {
252
253 @Override
254 public void run() {
255 try {
256 getFileBackend().copyImageToPrivateStorage(message, uri);
257 if (conversation.getNextEncryption() == Message.ENCRYPTION_PGP) {
258 getPgpEngine().encrypt(message, callback);
259 } else {
260 callback.success(message);
261 }
262 } catch (FileBackend.ImageCopyException e) {
263 callback.error(e.getResId(), message);
264 }
265 }
266 }).start();
267 return message;
268 }
269
270 public Conversation find(Bookmark bookmark) {
271 return find(bookmark.getAccount(),bookmark.getJid());
272 }
273
274 public Conversation find(Account account, String jid) {
275 return find(getConversations(),account,jid);
276 }
277
278 public class XmppConnectionBinder extends Binder {
279 public XmppConnectionService getService() {
280 return XmppConnectionService.this;
281 }
282 }
283
284 @Override
285 public int onStartCommand(Intent intent, int flags, int startId) {
286 if ((intent != null)
287 && (ACTION_MERGE_PHONE_CONTACTS.equals(intent.getAction()))) {
288 mergePhoneContactsWithRoster();
289 return START_STICKY;
290 } else if ((intent != null)
291 && (Intent.ACTION_SHUTDOWN.equals(intent.getAction()))) {
292 logoutAndSave();
293 return START_NOT_STICKY;
294 }
295 this.wakeLock.acquire();
296 ConnectivityManager cm = (ConnectivityManager) getApplicationContext()
297 .getSystemService(Context.CONNECTIVITY_SERVICE);
298 NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
299 boolean isConnected = activeNetwork != null
300 && activeNetwork.isConnected();
301
302 for (Account account : accounts) {
303 if (!account.isOptionSet(Account.OPTION_DISABLED)) {
304 if (!isConnected) {
305 account.setStatus(Account.STATUS_NO_INTERNET);
306 if (statusListener != null) {
307 statusListener.onStatusChanged(account);
308 }
309 } else {
310 if (account.getStatus() == Account.STATUS_NO_INTERNET) {
311 account.setStatus(Account.STATUS_OFFLINE);
312 if (statusListener != null) {
313 statusListener.onStatusChanged(account);
314 }
315 }
316 if (account.getStatus() == Account.STATUS_ONLINE) {
317 long lastReceived = account.getXmppConnection().lastPaketReceived;
318 long lastSent = account.getXmppConnection().lastPingSent;
319 if (lastSent - lastReceived >= PING_TIMEOUT * 1000) {
320 Log.d(LOGTAG, account.getJid() + ": ping timeout");
321 this.reconnectAccount(account, true);
322 } else if (SystemClock.elapsedRealtime() - lastReceived >= PING_MIN_INTERVAL * 1000) {
323 account.getXmppConnection().sendPing();
324 account.getXmppConnection().lastPingSent = SystemClock
325 .elapsedRealtime();
326 this.scheduleWakeupCall(2, false);
327 }
328 } else if (account.getStatus() == Account.STATUS_OFFLINE) {
329 if (account.getXmppConnection() == null) {
330 account.setXmppConnection(this
331 .createConnection(account));
332 }
333 account.getXmppConnection().lastPingSent = SystemClock
334 .elapsedRealtime();
335 new Thread(account.getXmppConnection()).start();
336 } else if ((account.getStatus() == Account.STATUS_CONNECTING)
337 && ((SystemClock.elapsedRealtime() - account
338 .getXmppConnection().lastConnect) / 1000 >= CONNECT_TIMEOUT)) {
339 Log.d(LOGTAG, account.getJid()
340 + ": time out during connect reconnecting");
341 reconnectAccount(account, true);
342 } else {
343 if (account.getXmppConnection().getTimeToNextAttempt() <= 0) {
344 reconnectAccount(account, true);
345 }
346 }
347 // in any case. reschedule wakup call
348 this.scheduleWakeupCall(PING_MAX_INTERVAL, true);
349 }
350 if (mOnAccountUpdate != null) {
351 mOnAccountUpdate.onAccountUpdate();
352 }
353 }
354 }
355 if (wakeLock.isHeld()) {
356 try {
357 wakeLock.release();
358 } catch (RuntimeException re) {
359 }
360 }
361 return START_STICKY;
362 }
363
364 @SuppressLint("TrulyRandom")
365 @Override
366 public void onCreate() {
367 ExceptionHelper.init(getApplicationContext());
368 PRNGFixes.apply();
369 this.mRandom = new SecureRandom();
370 this.mMemorizingTrustManager = new MemorizingTrustManager(getApplicationContext());
371 this.databaseBackend = DatabaseBackend
372 .getInstance(getApplicationContext());
373 this.fileBackend = new FileBackend(getApplicationContext());
374 this.accounts = databaseBackend.getAccounts();
375
376 for (Account account : this.accounts) {
377 this.databaseBackend.readRoster(account.getRoster());
378 }
379 this.mergePhoneContactsWithRoster();
380 this.getConversations();
381
382 getContentResolver().registerContentObserver(
383 ContactsContract.Contacts.CONTENT_URI, true, contactObserver);
384 this.pgpServiceConnection = new OpenPgpServiceConnection(
385 getApplicationContext(), "org.sufficientlysecure.keychain");
386 this.pgpServiceConnection.bindToService();
387
388 this.pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
389 this.wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
390 "XmppConnectionService");
391 }
392
393 @Override
394 public void onDestroy() {
395 super.onDestroy();
396 this.logoutAndSave();
397 }
398
399 @Override
400 public void onTaskRemoved(Intent rootIntent) {
401 super.onTaskRemoved(rootIntent);
402 this.logoutAndSave();
403 }
404
405 private void logoutAndSave() {
406 for (Account account : accounts) {
407 databaseBackend.writeRoster(account.getRoster());
408 if (account.getXmppConnection() != null) {
409 disconnect(account, false);
410 }
411 }
412 Context context = getApplicationContext();
413 AlarmManager alarmManager = (AlarmManager) context
414 .getSystemService(Context.ALARM_SERVICE);
415 Intent intent = new Intent(context, EventReceiver.class);
416 alarmManager.cancel(PendingIntent.getBroadcast(context, 0, intent, 0));
417 Log.d(LOGTAG, "good bye");
418 stopSelf();
419 }
420
421 protected void scheduleWakeupCall(int seconds, boolean ping) {
422 long timeToWake = SystemClock.elapsedRealtime() + seconds * 1000;
423 Context context = getApplicationContext();
424 AlarmManager alarmManager = (AlarmManager) context
425 .getSystemService(Context.ALARM_SERVICE);
426
427 if (ping) {
428 if (this.pingIntent == null) {
429 this.pingIntent = new Intent(context, EventReceiver.class);
430 this.pingIntent.setAction("ping");
431 this.pingIntent.putExtra("time", timeToWake);
432 this.pendingPingIntent = PendingIntent.getBroadcast(context, 0,
433 this.pingIntent, 0);
434 alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
435 timeToWake, pendingPingIntent);
436 } else {
437 long scheduledTime = this.pingIntent.getLongExtra("time", 0);
438 if (scheduledTime < SystemClock.elapsedRealtime()
439 || (scheduledTime > timeToWake)) {
440 this.pingIntent.putExtra("time", timeToWake);
441 alarmManager.cancel(this.pendingPingIntent);
442 this.pendingPingIntent = PendingIntent.getBroadcast(
443 context, 0, this.pingIntent, 0);
444 alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
445 timeToWake, pendingPingIntent);
446 }
447 }
448 } else {
449 Intent intent = new Intent(context, EventReceiver.class);
450 intent.setAction("ping_check");
451 PendingIntent alarmIntent = PendingIntent.getBroadcast(context, 0,
452 intent, 0);
453 alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, timeToWake,
454 alarmIntent);
455 }
456
457 }
458
459 public XmppConnection createConnection(Account account) {
460 SharedPreferences sharedPref = getPreferences();
461 account.setResource(sharedPref.getString("resource", "mobile")
462 .toLowerCase(Locale.getDefault()));
463 XmppConnection connection = new XmppConnection(account, this);
464 connection.setOnMessagePacketReceivedListener(this.mMessageParser);
465 connection.setOnStatusChangedListener(this.statusListener);
466 connection.setOnPresencePacketReceivedListener(this.mPresenceParser);
467 connection
468 .setOnUnregisteredIqPacketReceivedListener(this.mIqParser);
469 connection.setOnJinglePacketReceivedListener(this.jingleListener);
470 connection.setOnBindListener(this.mOnBindListener);
471 return connection;
472 }
473
474 synchronized public void sendMessage(Message message) {
475 Account account = message.getConversation().getAccount();
476 Conversation conv = message.getConversation();
477 MessagePacket packet = null;
478 boolean saveInDb = true;
479 boolean send = false;
480 if (account.getStatus() == Account.STATUS_ONLINE) {
481 if (message.getType() == Message.TYPE_IMAGE) {
482 if (message.getPresence() != null) {
483 if (message.getEncryption() == Message.ENCRYPTION_OTR) {
484 if (!conv.hasValidOtrSession()
485 && (message.getPresence() != null)) {
486 conv.startOtrSession(getApplicationContext(),
487 message.getPresence(), true);
488 message.setStatus(Message.STATUS_WAITING);
489 } else if (conv.hasValidOtrSession()
490 && conv.getOtrSession().getSessionStatus() == SessionStatus.ENCRYPTED) {
491 mJingleConnectionManager
492 .createNewConnection(message);
493 } else if (message.getPresence() == null) {
494 message.setStatus(Message.STATUS_WAITING);
495 }
496 } else {
497 mJingleConnectionManager.createNewConnection(message);
498 }
499 } else {
500 message.setStatus(Message.STATUS_WAITING);
501 }
502 } else {
503 if (message.getEncryption() == Message.ENCRYPTION_OTR) {
504 if (!conv.hasValidOtrSession()
505 && (message.getPresence() != null)) {
506 conv.startOtrSession(getApplicationContext(),
507 message.getPresence(), true);
508 message.setStatus(Message.STATUS_WAITING);
509 } else if (conv.hasValidOtrSession()
510 && conv.getOtrSession().getSessionStatus() == SessionStatus.ENCRYPTED) {
511 message.setPresence(conv.getOtrSession().getSessionID()
512 .getUserID());
513 packet = mMessageGenerator.generateOtrChat(message);
514 send = true;
515 message.setStatus(Message.STATUS_SEND);
516
517 } else if (message.getPresence() == null) {
518 message.setStatus(Message.STATUS_WAITING);
519 }
520 } else if (message.getEncryption() == Message.ENCRYPTION_DECRYPTED) {
521 message.getConversation().endOtrIfNeeded();
522 failWaitingOtrMessages(message.getConversation());
523 packet = mMessageGenerator.generatePgpChat(message);
524 message.setStatus(Message.STATUS_SEND);
525 send = true;
526 } else {
527 message.getConversation().endOtrIfNeeded();
528 failWaitingOtrMessages(message.getConversation());
529 if (message.getConversation().getMode() == Conversation.MODE_SINGLE) {
530 message.setStatus(Message.STATUS_SEND);
531 }
532 packet = mMessageGenerator.generateChat(message);
533 send = true;
534 }
535 }
536 } else {
537 message.setStatus(Message.STATUS_WAITING);
538 if (message.getType() == Message.TYPE_TEXT) {
539 if (message.getEncryption() == Message.ENCRYPTION_DECRYPTED) {
540 String pgpBody = message.getEncryptedBody();
541 String decryptedBody = message.getBody();
542 message.setBody(pgpBody);
543 databaseBackend.createMessage(message);
544 saveInDb = false;
545 message.setBody(decryptedBody);
546 } else if (message.getEncryption() == Message.ENCRYPTION_OTR) {
547 if (conv.hasValidOtrSession()) {
548 message.setPresence(conv.getOtrSession().getSessionID()
549 .getUserID());
550 } else if (!conv.hasValidOtrSession()
551 && message.getPresence() != null) {
552 conv.startOtrSession(getApplicationContext(),
553 message.getPresence(), false);
554 }
555 }
556 }
557
558 }
559 if (saveInDb) {
560 databaseBackend.createMessage(message);
561 }
562 conv.getMessages().add(message);
563 updateConversationUi();
564 if ((send) && (packet != null)) {
565 sendMessagePacket(account, packet);
566 }
567
568 }
569
570 private void sendUnsendMessages(Conversation conversation) {
571 for (int i = 0; i < conversation.getMessages().size(); ++i) {
572 int status = conversation.getMessages().get(i).getStatus();
573 if (status == Message.STATUS_WAITING) {
574 resendMessage(conversation.getMessages().get(i));
575 }
576 }
577 }
578
579 private void resendMessage(Message message) {
580 Account account = message.getConversation().getAccount();
581 MessagePacket packet = null;
582 if (message.getEncryption() == Message.ENCRYPTION_OTR) {
583 Presences presences = message.getConversation().getContact()
584 .getPresences();
585 if (!message.getConversation().hasValidOtrSession()) {
586 if ((message.getPresence() != null)
587 && (presences.has(message.getPresence()))) {
588 message.getConversation().startOtrSession(
589 getApplicationContext(), message.getPresence(),
590 true);
591 } else {
592 if (presences.size() == 1) {
593 String presence = presences.asStringArray()[0];
594 message.getConversation().startOtrSession(
595 getApplicationContext(), presence, true);
596 }
597 }
598 } else {
599 if (message.getConversation().getOtrSession()
600 .getSessionStatus() == SessionStatus.ENCRYPTED) {
601 if (message.getType() == Message.TYPE_TEXT) {
602 packet = mMessageGenerator.generateOtrChat(message,
603 true);
604 } else if (message.getType() == Message.TYPE_IMAGE) {
605 mJingleConnectionManager.createNewConnection(message);
606 }
607 }
608 }
609 } else if (message.getType() == Message.TYPE_TEXT) {
610 if (message.getEncryption() == Message.ENCRYPTION_NONE) {
611 packet = mMessageGenerator.generateChat(message, true);
612 } else if ((message.getEncryption() == Message.ENCRYPTION_DECRYPTED)
613 || (message.getEncryption() == Message.ENCRYPTION_PGP)) {
614 packet = mMessageGenerator.generatePgpChat(message, true);
615 }
616 } else if (message.getType() == Message.TYPE_IMAGE) {
617 Presences presences = message.getConversation().getContact()
618 .getPresences();
619 if ((message.getPresence() != null)
620 && (presences.has(message.getPresence()))) {
621 markMessage(message, Message.STATUS_OFFERED);
622 mJingleConnectionManager.createNewConnection(message);
623 } else {
624 if (presences.size() == 1) {
625 String presence = presences.asStringArray()[0];
626 message.setPresence(presence);
627 markMessage(message, Message.STATUS_OFFERED);
628 mJingleConnectionManager.createNewConnection(message);
629 }
630 }
631 }
632 if (packet != null) {
633 sendMessagePacket(account,packet);
634 markMessage(message, Message.STATUS_SEND);
635 }
636 }
637
638 public void fetchRosterFromServer(Account account) {
639 IqPacket iqPacket = new IqPacket(IqPacket.TYPE_GET);
640 if (!"".equals(account.getRosterVersion())) {
641 Log.d(LOGTAG, account.getJid() + ": fetching roster version "
642 + account.getRosterVersion());
643 } else {
644 Log.d(LOGTAG, account.getJid() + ": fetching roster");
645 }
646 iqPacket.query("jabber:iq:roster").setAttribute("ver",
647 account.getRosterVersion());
648 account.getXmppConnection().sendIqPacket(iqPacket,
649 new OnIqPacketReceived() {
650
651 @Override
652 public void onIqPacketReceived(final Account account,
653 IqPacket packet) {
654 Element query = packet.findChild("query");
655 if (query != null) {
656 account.getRoster().markAllAsNotInRoster();
657 mIqParser.rosterItems(account, query);
658 }
659 }
660 });
661 }
662
663 public void fetchBookmarks(Account account) {
664 IqPacket iqPacket = new IqPacket(IqPacket.TYPE_GET);
665 Element query = iqPacket.query("jabber:iq:private");
666 query.addChild("storage", "storage:bookmarks");
667 OnIqPacketReceived callback = new OnIqPacketReceived() {
668
669 @Override
670 public void onIqPacketReceived(Account account, IqPacket packet) {
671 Element query = packet.query();
672 List<Bookmark> bookmarks = new ArrayList<Bookmark>();
673 Element storage = query.findChild("storage", "storage:bookmarks");
674 if (storage!=null) {
675 for(Element item : storage.getChildren()) {
676 if (item.getName().equals("conference")) {
677 Bookmark bookmark = Bookmark.parse(item,account);
678 bookmarks.add(bookmark);
679 Conversation conversation = find(bookmark);
680 if (conversation!=null) {
681 conversation.setBookmark(bookmark);
682 } else {
683 if (bookmark.autojoin()) {
684 conversation = findOrCreateConversation(account, bookmark.getJid(), true);
685 conversation.setBookmark(bookmark);
686 joinMuc(conversation);
687 }
688 }
689 }
690 }
691 }
692 account.setBookmarks(bookmarks);
693 }
694 };
695 sendIqPacket(account, iqPacket, callback);
696
697 }
698
699 public void pushBookmarks(Account account) {
700 IqPacket iqPacket = new IqPacket(IqPacket.TYPE_SET);
701 Element query = iqPacket.query("jabber:iq:private");
702 Element storage = query.addChild("storage", "storage:bookmarks");
703 for(Bookmark bookmark : account.getBookmarks()) {
704 storage.addChild(bookmark.toElement());
705 }
706 sendIqPacket(account, iqPacket,null);
707 }
708
709 private void mergePhoneContactsWithRoster() {
710 PhoneHelper.loadPhoneContacts(getApplicationContext(),
711 new OnPhoneContactsLoadedListener() {
712 @Override
713 public void onPhoneContactsLoaded(List<Bundle> phoneContacts) {
714 for (Account account : accounts) {
715 account.getRoster().clearSystemAccounts();
716 }
717 for (Bundle phoneContact : phoneContacts) {
718 for (Account account : accounts) {
719 String jid = phoneContact.getString("jid");
720 Contact contact = account.getRoster()
721 .getContact(jid);
722 String systemAccount = phoneContact
723 .getInt("phoneid")
724 + "#"
725 + phoneContact.getString("lookup");
726 contact.setSystemAccount(systemAccount);
727 contact.setPhotoUri(phoneContact
728 .getString("photouri"));
729 contact.setSystemName(phoneContact
730 .getString("displayname"));
731 }
732 }
733 }
734 });
735 }
736
737 public List<Conversation> getConversations() {
738 if (this.conversations == null) {
739 Hashtable<String, Account> accountLookupTable = new Hashtable<String, Account>();
740 for (Account account : this.accounts) {
741 accountLookupTable.put(account.getUuid(), account);
742 }
743 this.conversations = databaseBackend
744 .getConversations(Conversation.STATUS_AVAILABLE);
745 for (Conversation conv : this.conversations) {
746 Account account = accountLookupTable.get(conv.getAccountUuid());
747 conv.setAccount(account);
748 conv.setMessages(databaseBackend.getMessages(conv, 50));
749 }
750 }
751
752 return this.conversations;
753 }
754
755 public void populateWithOrderedConversations(List<Conversation> list) {
756 list.clear();
757 list.addAll(getConversations());
758 Collections.sort(list, new Comparator<Conversation>() {
759 @Override
760 public int compare(Conversation lhs, Conversation rhs) {
761 Message left = lhs.getLatestMessage();
762 Message right = rhs.getLatestMessage();
763 if (left.getTimeSent() > right.getTimeSent()) {
764 return -1;
765 } else if (left.getTimeSent() < right.getTimeSent()) {
766 return 1;
767 } else {
768 return 0;
769 }
770 }
771 });
772 }
773
774 public List<Message> getMoreMessages(Conversation conversation,
775 long timestamp) {
776 List<Message> messages = databaseBackend.getMessages(conversation, 50,
777 timestamp);
778 for (Message message : messages) {
779 message.setConversation(conversation);
780 }
781 return messages;
782 }
783
784 public List<Account> getAccounts() {
785 return this.accounts;
786 }
787
788 public Conversation find(List<Conversation> haystack, Contact contact) {
789 for (Conversation conversation : haystack) {
790 if (conversation.getContact() == contact) {
791 return conversation;
792 }
793 }
794 return null;
795 }
796
797 public Conversation find(List<Conversation> haystack, Account account, String jid) {
798 for (Conversation conversation : haystack) {
799 if ((conversation.getAccount().equals(account))
800 && (conversation.getContactJid().split("/")[0].equals(jid))) {
801 return conversation;
802 }
803 }
804 return null;
805 }
806
807
808 public Conversation findOrCreateConversation(Account account, String jid,
809 boolean muc) {
810 Conversation conversation = find(account, jid);
811 if (conversation != null) {
812 return conversation;
813 }
814 conversation = databaseBackend.findConversation(account,jid);
815 if (conversation != null) {
816 conversation.setStatus(Conversation.STATUS_AVAILABLE);
817 conversation.setAccount(account);
818 if (muc) {
819 conversation.setMode(Conversation.MODE_MULTI);
820 } else {
821 conversation.setMode(Conversation.MODE_SINGLE);
822 }
823 conversation.setMessages(databaseBackend.getMessages(conversation,
824 50));
825 this.databaseBackend.updateConversation(conversation);
826 } else {
827 String conversationName;
828 Contact contact = account.getRoster().getContact(jid);
829 if (contact != null) {
830 conversationName = contact.getDisplayName();
831 } else {
832 conversationName = jid.split("@")[0];
833 }
834 if (muc) {
835 conversation = new Conversation(conversationName, account, jid,
836 Conversation.MODE_MULTI);
837 } else {
838 conversation = new Conversation(conversationName, account, jid,
839 Conversation.MODE_SINGLE);
840 }
841 this.databaseBackend.createConversation(conversation);
842 }
843 this.conversations.add(conversation);
844 updateConversationUi();
845 return conversation;
846 }
847
848 public void archiveConversation(Conversation conversation) {
849 if (conversation.getMode() == Conversation.MODE_MULTI) {
850 Bookmark bookmark = conversation.getBookmark();
851 if (bookmark!=null && bookmark.autojoin()) {
852 bookmark.setAutojoin(false);
853 pushBookmarks(bookmark.getAccount());
854 }
855 leaveMuc(conversation);
856 } else {
857 conversation.endOtrIfNeeded();
858 }
859 this.databaseBackend.updateConversation(conversation);
860 this.conversations.remove(conversation);
861 updateConversationUi();
862 }
863
864 public void clearConversationHistory(Conversation conversation) {
865 this.databaseBackend.deleteMessagesInConversation(conversation);
866 this.fileBackend.removeFiles(conversation);
867 conversation.getMessages().clear();
868 updateConversationUi();
869 }
870
871 public int getConversationCount() {
872 return this.databaseBackend.getConversationCount();
873 }
874
875 public void createAccount(Account account) {
876 databaseBackend.createAccount(account);
877 this.accounts.add(account);
878 this.reconnectAccount(account, false);
879 updateAccountUi();
880 }
881
882 public void updateAccount(Account account) {
883 this.statusListener.onStatusChanged(account);
884 databaseBackend.updateAccount(account);
885 reconnectAccount(account, false);
886 updateAccountUi();
887 UIHelper.showErrorNotification(getApplicationContext(), getAccounts());
888 }
889
890 public void deleteAccount(Account account) {
891 for(Conversation conversation : conversations) {
892 if (conversation.getAccount() == account) {
893 if (conversation.getMode() == Conversation.MODE_MULTI) {
894 leaveMuc(conversation);
895 } else if (conversation.getMode() == Conversation.MODE_SINGLE) {
896 conversation.endOtrIfNeeded();
897 }
898 conversations.remove(conversation);
899 }
900 }
901 if (account.getXmppConnection() != null) {
902 this.disconnect(account, true);
903 }
904 databaseBackend.deleteAccount(account);
905 this.accounts.remove(account);
906 updateAccountUi();
907 UIHelper.showErrorNotification(getApplicationContext(), getAccounts());
908 }
909
910 public void setOnConversationListChangedListener(
911 OnConversationUpdate listener) {
912 this.mOnConversationUpdate = listener;
913 this.convChangedListenerCount++;
914 }
915
916 public void removeOnConversationListChangedListener() {
917 this.convChangedListenerCount--;
918 if (this.convChangedListenerCount == 0) {
919 this.mOnConversationUpdate = null;
920 }
921 }
922
923 public void setOnAccountListChangedListener(OnAccountUpdate listener) {
924 this.mOnAccountUpdate = listener;
925 }
926
927 public void removeOnAccountListChangedListener() {
928 this.mOnAccountUpdate = null;
929 }
930
931 public void setOnRosterUpdateListener(OnRosterUpdate listener) {
932 this.mOnRosterUpdate = listener;
933 }
934
935 public void removeOnRosterUpdateListener() {
936 this.mOnRosterUpdate = null;
937 }
938
939 public void connectMultiModeConversations(Account account) {
940 List<Conversation> conversations = getConversations();
941 for (int i = 0; i < conversations.size(); i++) {
942 Conversation conversation = conversations.get(i);
943 if ((conversation.getMode() == Conversation.MODE_MULTI)
944 && (conversation.getAccount() == account)) {
945 joinMuc(conversation);
946 }
947 }
948 }
949
950 public void joinMuc(Conversation conversation) {
951 Account account = conversation.getAccount();
952 account.pendingConferenceJoins.remove(conversation);
953 account.pendingConferenceLeaves.remove(conversation);
954 if (account.getStatus() == Account.STATUS_ONLINE) {
955 Log.d(LOGTAG,"joining conversation "+conversation.getContactJid());
956 String nick = conversation.getMucOptions().getProposedNick();
957 conversation.getMucOptions().setJoinNick(nick);
958 PresencePacket packet = new PresencePacket();
959 String joinJid = conversation.getMucOptions().getJoinJid();
960 packet.setAttribute("to",conversation.getMucOptions().getJoinJid());
961 Element x = new Element("x");
962 x.setAttribute("xmlns", "http://jabber.org/protocol/muc");
963 String sig = account.getPgpSignature();
964 if (sig != null) {
965 packet.addChild("status").setContent("online");
966 packet.addChild("x", "jabber:x:signed").setContent(sig);
967 }
968 if (conversation.getMessages().size() != 0) {
969 final SimpleDateFormat mDateFormat = new SimpleDateFormat(
970 "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US);
971 mDateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
972 Date date = new Date(
973 conversation.getLatestMessage().getTimeSent() + 1000);
974 x.addChild("history").setAttribute("since",
975 mDateFormat.format(date));
976 }
977 packet.addChild(x);
978 sendPresencePacket(account, packet);
979 if (!joinJid.equals(conversation.getContactJid())) {
980 conversation.setContactJid(joinJid);
981 databaseBackend.updateConversation(conversation);
982 }
983 } else {
984 account.pendingConferenceJoins.add(conversation);
985 }
986 }
987
988 private OnRenameListener renameListener = null;
989 private IqGenerator mIqGenerator = new IqGenerator();
990
991 public void setOnRenameListener(OnRenameListener listener) {
992 this.renameListener = listener;
993 }
994
995 public void renameInMuc(final Conversation conversation, final String nick) {
996 final MucOptions options = conversation.getMucOptions();
997 options.setJoinNick(nick);
998 if (options.online()) {
999 Account account = conversation.getAccount();
1000 options.setOnRenameListener(new OnRenameListener() {
1001
1002 @Override
1003 public void onRename(boolean success) {
1004 if (renameListener != null) {
1005 renameListener.onRename(success);
1006 }
1007 if (success) {
1008 conversation.setContactJid(conversation.getMucOptions().getJoinJid());
1009 databaseBackend.updateConversation(conversation);
1010 Bookmark bookmark = conversation.getBookmark();
1011 if (bookmark!=null) {
1012 bookmark.setNick(nick);
1013 pushBookmarks(bookmark.getAccount());
1014 }
1015 }
1016 }
1017 });
1018 options.flagAboutToRename();
1019 PresencePacket packet = new PresencePacket();
1020 packet.setAttribute("to",options.getJoinJid());
1021 packet.setAttribute("from", conversation.getAccount().getFullJid());
1022
1023 String sig = account.getPgpSignature();
1024 if (sig != null) {
1025 packet.addChild("status").setContent("online");
1026 packet.addChild("x", "jabber:x:signed").setContent(sig);
1027 }
1028 sendPresencePacket(account,packet);
1029 } else {
1030 conversation.setContactJid(options.getJoinJid());
1031 databaseBackend.updateConversation(conversation);
1032 if (conversation.getAccount().getStatus() == Account.STATUS_ONLINE) {
1033 Bookmark bookmark = conversation.getBookmark();
1034 if (bookmark!=null) {
1035 bookmark.setNick(nick);
1036 pushBookmarks(bookmark.getAccount());
1037 }
1038 joinMuc(conversation);
1039 }
1040 }
1041 }
1042
1043 public void leaveMuc(Conversation conversation) {
1044 Account account = conversation.getAccount();
1045 account.pendingConferenceJoins.remove(conversation);
1046 account.pendingConferenceLeaves.remove(conversation);
1047 if (account.getStatus() == Account.STATUS_ONLINE) {
1048 PresencePacket packet = new PresencePacket();
1049 packet.setAttribute("to", conversation.getMucOptions().getJoinJid());
1050 packet.setAttribute("from", conversation.getAccount().getFullJid());
1051 packet.setAttribute("type", "unavailable");
1052 sendPresencePacket(conversation.getAccount(),packet);
1053 conversation.getMucOptions().setOffline();
1054 conversation.deregisterWithBookmark();
1055 Log.d(LOGTAG,conversation.getAccount().getJid()+" leaving muc "+conversation.getContactJid());
1056 } else {
1057 account.pendingConferenceLeaves.add(conversation);
1058 }
1059 }
1060
1061 public void disconnect(Account account, boolean force) {
1062 if ((account.getStatus() == Account.STATUS_ONLINE)
1063 || (account.getStatus() == Account.STATUS_DISABLED)) {
1064 if (!force) {
1065 List<Conversation> conversations = getConversations();
1066 for (int i = 0; i < conversations.size(); i++) {
1067 Conversation conversation = conversations.get(i);
1068 if (conversation.getAccount() == account) {
1069 if (conversation.getMode() == Conversation.MODE_MULTI) {
1070 leaveMuc(conversation);
1071 } else {
1072 conversation.endOtrIfNeeded();
1073 }
1074 }
1075 }
1076 }
1077 account.getXmppConnection().disconnect(force);
1078 }
1079 }
1080
1081 @Override
1082 public IBinder onBind(Intent intent) {
1083 return mBinder;
1084 }
1085
1086 public void updateMessage(Message message) {
1087 databaseBackend.updateMessage(message);
1088 }
1089
1090 protected void syncDirtyContacts(Account account) {
1091 for (Contact contact : account.getRoster().getContacts()) {
1092 if (contact.getOption(Contact.Options.DIRTY_PUSH)) {
1093 pushContactToServer(contact);
1094 }
1095 if (contact.getOption(Contact.Options.DIRTY_DELETE)) {
1096 Log.d(LOGTAG, "dirty delete");
1097 deleteContactOnServer(contact);
1098 }
1099 }
1100 }
1101
1102 public void createContact(Contact contact) {
1103 SharedPreferences sharedPref = getPreferences();
1104 boolean autoGrant = sharedPref.getBoolean("grant_new_contacts", true);
1105 if (autoGrant) {
1106 contact.setOption(Contact.Options.PREEMPTIVE_GRANT);
1107 contact.setOption(Contact.Options.ASKING);
1108 }
1109 pushContactToServer(contact);
1110 }
1111
1112 public void onOtrSessionEstablished(Conversation conversation) {
1113 Account account = conversation.getAccount();
1114 List<Message> messages = conversation.getMessages();
1115 Session otrSession = conversation.getOtrSession();
1116 Log.d(LOGTAG, account.getJid() + " otr session established with "
1117 + conversation.getContactJid() + "/"
1118 + otrSession.getSessionID().getUserID());
1119 for (int i = 0; i < messages.size(); ++i) {
1120 Message msg = messages.get(i);
1121 if ((msg.getStatus() == Message.STATUS_UNSEND || msg.getStatus() == Message.STATUS_WAITING)
1122 && (msg.getEncryption() == Message.ENCRYPTION_OTR)) {
1123 msg.setPresence(otrSession.getSessionID().getUserID());
1124 if (msg.getType() == Message.TYPE_TEXT) {
1125 MessagePacket outPacket = mMessageGenerator
1126 .generateOtrChat(msg, true);
1127 if (outPacket != null) {
1128 msg.setStatus(Message.STATUS_SEND);
1129 databaseBackend.updateMessage(msg);
1130 sendMessagePacket(account,outPacket);
1131 }
1132 } else if (msg.getType() == Message.TYPE_IMAGE) {
1133 mJingleConnectionManager.createNewConnection(msg);
1134 }
1135 }
1136 }
1137 notifyUi(conversation, false);
1138 }
1139
1140 public boolean renewSymmetricKey(Conversation conversation) {
1141 Account account = conversation.getAccount();
1142 byte[] symmetricKey = new byte[32];
1143 this.mRandom.nextBytes(symmetricKey);
1144 Session otrSession = conversation.getOtrSession();
1145 if (otrSession != null) {
1146 MessagePacket packet = new MessagePacket();
1147 packet.setType(MessagePacket.TYPE_CHAT);
1148 packet.setFrom(account.getFullJid());
1149 packet.addChild("private", "urn:xmpp:carbons:2");
1150 packet.addChild("no-copy", "urn:xmpp:hints");
1151 packet.setTo(otrSession.getSessionID().getAccountID() + "/"
1152 + otrSession.getSessionID().getUserID());
1153 try {
1154 packet.setBody(otrSession
1155 .transformSending(CryptoHelper.FILETRANSFER
1156 + CryptoHelper.bytesToHex(symmetricKey)));
1157 sendMessagePacket(account,packet);
1158 conversation.setSymmetricKey(symmetricKey);
1159 return true;
1160 } catch (OtrException e) {
1161 return false;
1162 }
1163 }
1164 return false;
1165 }
1166
1167 public void pushContactToServer(Contact contact) {
1168 contact.resetOption(Contact.Options.DIRTY_DELETE);
1169 contact.setOption(Contact.Options.DIRTY_PUSH);
1170 Account account = contact.getAccount();
1171 if (account.getStatus() == Account.STATUS_ONLINE) {
1172 boolean ask = contact.getOption(Contact.Options.ASKING);
1173 boolean sendUpdates = contact.getOption(Contact.Options.PENDING_SUBSCRIPTION_REQUEST)
1174 && contact.getOption(Contact.Options.PREEMPTIVE_GRANT);
1175 IqPacket iq = new IqPacket(IqPacket.TYPE_SET);
1176 iq.query("jabber:iq:roster").addChild(contact.asElement());
1177 account.getXmppConnection().sendIqPacket(iq, null);
1178 if (sendUpdates) {
1179 sendPresencePacket(account, mPresenceGenerator.sendPresenceUpdatesTo(contact));
1180 }
1181 if (ask) {
1182 sendPresencePacket(account, mPresenceGenerator.requestPresenceUpdatesFrom(contact));
1183 }
1184 }
1185 }
1186
1187 public void deleteContactOnServer(Contact contact) {
1188 contact.resetOption(Contact.Options.PREEMPTIVE_GRANT);
1189 contact.resetOption(Contact.Options.DIRTY_PUSH);
1190 contact.setOption(Contact.Options.DIRTY_DELETE);
1191 Account account = contact.getAccount();
1192 if (account.getStatus() == Account.STATUS_ONLINE) {
1193 IqPacket iq = new IqPacket(IqPacket.TYPE_SET);
1194 Element item = iq.query("jabber:iq:roster").addChild("item");
1195 item.setAttribute("jid", contact.getJid());
1196 item.setAttribute("subscription", "remove");
1197 account.getXmppConnection().sendIqPacket(iq, null);
1198 }
1199 }
1200
1201 public void updateConversation(Conversation conversation) {
1202 this.databaseBackend.updateConversation(conversation);
1203 }
1204
1205 public void reconnectAccount(final Account account, final boolean force) {
1206 new Thread(new Runnable() {
1207
1208 @Override
1209 public void run() {
1210 if (account.getXmppConnection() != null) {
1211 disconnect(account, force);
1212 }
1213 if (!account.isOptionSet(Account.OPTION_DISABLED)) {
1214 if (account.getXmppConnection() == null) {
1215 account.setXmppConnection(createConnection(account));
1216 }
1217 Thread thread = new Thread(account.getXmppConnection());
1218 thread.start();
1219 scheduleWakeupCall((int) (CONNECT_TIMEOUT * 1.2), false);
1220 }
1221 }
1222 }).start();
1223 }
1224
1225 public void invite(Conversation conversation, String contact) {
1226 MessagePacket packet = mMessageGenerator.invite(conversation, contact);
1227 sendMessagePacket(conversation.getAccount(),packet);
1228 }
1229
1230 public boolean markMessage(Account account, String recipient, String uuid,
1231 int status) {
1232 for (Conversation conversation : getConversations()) {
1233 if (conversation.getContactJid().equals(recipient)
1234 && conversation.getAccount().equals(account)) {
1235 return markMessage(conversation, uuid, status);
1236 }
1237 }
1238 return false;
1239 }
1240
1241 public boolean markMessage(Conversation conversation, String uuid,
1242 int status) {
1243 for (Message message : conversation.getMessages()) {
1244 if (message.getUuid().equals(uuid)) {
1245 markMessage(message, status);
1246 return true;
1247 }
1248 }
1249 return false;
1250 }
1251
1252 public void markMessage(Message message, int status) {
1253 message.setStatus(status);
1254 databaseBackend.updateMessage(message);
1255 updateConversationUi();
1256 }
1257
1258 public SharedPreferences getPreferences() {
1259 return PreferenceManager
1260 .getDefaultSharedPreferences(getApplicationContext());
1261 }
1262
1263 public boolean confirmMessages() {
1264 return getPreferences().getBoolean("confirm_messages", true);
1265 }
1266
1267 public void notifyUi(Conversation conversation, boolean notify) {
1268 if (mOnConversationUpdate != null) {
1269 mOnConversationUpdate.onConversationUpdate();
1270 } else {
1271 UIHelper.updateNotification(getApplicationContext(),
1272 getConversations(), conversation, notify);
1273 }
1274 }
1275
1276 public void updateConversationUi() {
1277 if (mOnConversationUpdate != null) {
1278 mOnConversationUpdate.onConversationUpdate();
1279 }
1280 }
1281
1282 public void updateAccountUi() {
1283 if (mOnAccountUpdate != null) {
1284 mOnAccountUpdate.onAccountUpdate();
1285 }
1286 }
1287
1288 public void updateRosterUi() {
1289 if (mOnRosterUpdate != null) {
1290 mOnRosterUpdate.onRosterUpdate();
1291 }
1292 }
1293
1294 public Account findAccountByJid(String accountJid) {
1295 for (Account account : this.accounts) {
1296 if (account.getJid().equals(accountJid)) {
1297 return account;
1298 }
1299 }
1300 return null;
1301 }
1302
1303 public Conversation findConversationByUuid(String uuid) {
1304 for (Conversation conversation : getConversations()) {
1305 if (conversation.getUuid().equals(uuid)) {
1306 return conversation;
1307 }
1308 }
1309 return null;
1310 }
1311
1312 public void markRead(Conversation conversation) {
1313 conversation.markRead();
1314 String id = conversation.popLatestMarkableMessageId();
1315 if (confirmMessages() && id != null) {
1316 Account account = conversation.getAccount();
1317 String to = conversation.getContactJid();
1318 this.sendMessagePacket(conversation.getAccount(), mMessageGenerator.confirm(account, to, id));
1319 }
1320 }
1321
1322 public void failWaitingOtrMessages(Conversation conversation) {
1323 for (Message message : conversation.getMessages()) {
1324 if (message.getEncryption() == Message.ENCRYPTION_OTR
1325 && message.getStatus() == Message.STATUS_WAITING) {
1326 markMessage(message, Message.STATUS_SEND_FAILED);
1327 }
1328 }
1329 }
1330
1331 public SecureRandom getRNG() {
1332 return this.mRandom;
1333 }
1334
1335 public MemorizingTrustManager getMemorizingTrustManager() {
1336 return this.mMemorizingTrustManager;
1337 }
1338
1339 public PowerManager getPowerManager() {
1340 return this.pm;
1341 }
1342
1343 public void replyWithNotAcceptable(Account account, MessagePacket packet) {
1344 if (account.getStatus() == Account.STATUS_ONLINE) {
1345 MessagePacket error = this.mMessageGenerator
1346 .generateNotAcceptable(packet);
1347 sendMessagePacket(account,error);
1348 }
1349 }
1350
1351 public void syncRosterToDisk(final Account account) {
1352 new Thread(new Runnable() {
1353
1354 @Override
1355 public void run() {
1356 databaseBackend.writeRoster(account.getRoster());
1357 }
1358 }).start();
1359
1360 }
1361
1362 public List<String> getKnownHosts() {
1363 List<String> hosts = new ArrayList<String>();
1364 for (Account account : getAccounts()) {
1365 if (!hosts.contains(account.getServer())) {
1366 hosts.add(account.getServer());
1367 }
1368 for (Contact contact : account.getRoster().getContacts()) {
1369 if (contact.showInRoster()) {
1370 String server = contact.getServer();
1371 if (server != null && !hosts.contains(server)) {
1372 hosts.add(server);
1373 }
1374 }
1375 }
1376 }
1377 return hosts;
1378 }
1379
1380 public List<String> getKnownConferenceHosts() {
1381 ArrayList<String> mucServers = new ArrayList<String>();
1382 for (Account account : accounts) {
1383 if (account.getXmppConnection() != null) {
1384 String server = account.getXmppConnection().getMucServer();
1385 if (server != null) {
1386 mucServers.add(server);
1387 }
1388 }
1389 }
1390 return mucServers;
1391 }
1392
1393 public void sendMessagePacket(Account account, MessagePacket packet) {
1394 account.getXmppConnection().sendMessagePacket(packet);
1395 }
1396
1397 public void sendPresencePacket(Account account, PresencePacket packet) {
1398 account.getXmppConnection().sendPresencePacket(packet);
1399 }
1400
1401 public void sendIqPacket(Account account, IqPacket packet, OnIqPacketReceived callback) {
1402 account.getXmppConnection().sendIqPacket(packet, callback);
1403 }
1404
1405 public MessageGenerator getMessageGenerator() {
1406 return this.mMessageGenerator;
1407 }
1408
1409 public PresenceGenerator getPresenceGenerator() {
1410 return this.mPresenceGenerator;
1411 }
1412
1413 public IqGenerator getIqGenerator() {
1414 return this.mIqGenerator ;
1415 }
1416
1417 public JingleConnectionManager getJingleConnectionManager() {
1418 return this.mJingleConnectionManager;
1419 }
1420
1421 public interface OnConversationUpdate {
1422 public void onConversationUpdate();
1423 }
1424
1425 public interface OnAccountUpdate {
1426 public void onAccountUpdate();
1427 }
1428
1429 public interface OnRosterUpdate {
1430 public void onRosterUpdate();
1431 }
1432}