1package eu.siacs.conversations.services;
2
3import android.annotation.SuppressLint;
4import android.app.AlarmManager;
5import android.app.PendingIntent;
6import android.app.Service;
7import android.content.Context;
8import android.content.Intent;
9import android.content.SharedPreferences;
10import android.database.ContentObserver;
11import android.graphics.Bitmap;
12import android.net.ConnectivityManager;
13import android.net.NetworkInfo;
14import android.net.Uri;
15import android.os.Binder;
16import android.os.Bundle;
17import android.os.FileObserver;
18import android.os.IBinder;
19import android.os.PowerManager;
20import android.os.PowerManager.WakeLock;
21import android.os.SystemClock;
22import android.preference.PreferenceManager;
23import android.provider.ContactsContract;
24import android.util.Log;
25import android.util.LruCache;
26
27import net.java.otr4j.OtrException;
28import net.java.otr4j.session.Session;
29import net.java.otr4j.session.SessionID;
30import net.java.otr4j.session.SessionStatus;
31
32import org.openintents.openpgp.util.OpenPgpApi;
33import org.openintents.openpgp.util.OpenPgpServiceConnection;
34
35import java.math.BigInteger;
36import java.security.SecureRandom;
37import java.util.ArrayList;
38import java.util.Collections;
39import java.util.Comparator;
40import java.util.Hashtable;
41import java.util.List;
42import java.util.Locale;
43import java.util.concurrent.CopyOnWriteArrayList;
44
45import de.duenndns.ssl.MemorizingTrustManager;
46import eu.siacs.conversations.Config;
47import eu.siacs.conversations.R;
48import eu.siacs.conversations.crypto.PgpEngine;
49import eu.siacs.conversations.entities.Account;
50import eu.siacs.conversations.entities.Bookmark;
51import eu.siacs.conversations.entities.Contact;
52import eu.siacs.conversations.entities.Conversation;
53import eu.siacs.conversations.entities.Downloadable;
54import eu.siacs.conversations.entities.DownloadableFile;
55import eu.siacs.conversations.entities.DownloadablePlaceholder;
56import eu.siacs.conversations.entities.Message;
57import eu.siacs.conversations.entities.MucOptions;
58import eu.siacs.conversations.entities.MucOptions.OnRenameListener;
59import eu.siacs.conversations.entities.Presences;
60import eu.siacs.conversations.generator.IqGenerator;
61import eu.siacs.conversations.generator.MessageGenerator;
62import eu.siacs.conversations.generator.PresenceGenerator;
63import eu.siacs.conversations.http.HttpConnectionManager;
64import eu.siacs.conversations.parser.IqParser;
65import eu.siacs.conversations.parser.MessageParser;
66import eu.siacs.conversations.parser.PresenceParser;
67import eu.siacs.conversations.persistance.DatabaseBackend;
68import eu.siacs.conversations.persistance.FileBackend;
69import eu.siacs.conversations.ui.UiCallback;
70import eu.siacs.conversations.utils.CryptoHelper;
71import eu.siacs.conversations.utils.ExceptionHelper;
72import eu.siacs.conversations.utils.OnPhoneContactsLoadedListener;
73import eu.siacs.conversations.utils.PRNGFixes;
74import eu.siacs.conversations.utils.PhoneHelper;
75import eu.siacs.conversations.xml.Element;
76import eu.siacs.conversations.xmpp.OnAdvancedStreamFeaturesLoaded;
77import eu.siacs.conversations.xmpp.OnBindListener;
78import eu.siacs.conversations.xmpp.OnContactStatusChanged;
79import eu.siacs.conversations.xmpp.OnIqPacketReceived;
80import eu.siacs.conversations.xmpp.OnMessageAcknowledged;
81import eu.siacs.conversations.xmpp.OnStatusChanged;
82import eu.siacs.conversations.xmpp.XmppConnection;
83import eu.siacs.conversations.xmpp.forms.Data;
84import eu.siacs.conversations.xmpp.forms.Field;
85import eu.siacs.conversations.xmpp.jid.InvalidJidException;
86import eu.siacs.conversations.xmpp.jid.Jid;
87import eu.siacs.conversations.xmpp.jingle.JingleConnectionManager;
88import eu.siacs.conversations.xmpp.jingle.OnJinglePacketReceived;
89import eu.siacs.conversations.xmpp.jingle.stanzas.JinglePacket;
90import eu.siacs.conversations.xmpp.pep.Avatar;
91import eu.siacs.conversations.xmpp.stanzas.IqPacket;
92import eu.siacs.conversations.xmpp.stanzas.MessagePacket;
93import eu.siacs.conversations.xmpp.stanzas.PresencePacket;
94
95public class XmppConnectionService extends Service {
96
97 public static String ACTION_CLEAR_NOTIFICATION = "clear_notification";
98 private static String ACTION_MERGE_PHONE_CONTACTS = "merge_phone_contacts";
99 public static String ACTION_DISABLE_FOREGROUND = "disable_foreground";
100 private ContentObserver contactObserver = new ContentObserver(null) {
101 @Override
102 public void onChange(boolean selfChange) {
103 super.onChange(selfChange);
104 Intent intent = new Intent(getApplicationContext(),
105 XmppConnectionService.class);
106 intent.setAction(ACTION_MERGE_PHONE_CONTACTS);
107 startService(intent);
108 }
109 };
110 private final IBinder mBinder = new XmppConnectionBinder();
111 public DatabaseBackend databaseBackend;
112 public OnContactStatusChanged onContactStatusChanged = new OnContactStatusChanged() {
113
114 @Override
115 public void onContactStatusChanged(Contact contact, boolean online) {
116 Conversation conversation = find(getConversations(), contact);
117 if (conversation != null) {
118 if (online && contact.getPresences().size() > 1) {
119 conversation.endOtrIfNeeded();
120 } else {
121 conversation.resetOtrSession();
122 }
123 if (online && (contact.getPresences().size() == 1)) {
124 sendUnsendMessages(conversation);
125 }
126 }
127 }
128 };
129 private FileBackend fileBackend = new FileBackend(this);
130 private MemorizingTrustManager mMemorizingTrustManager;
131 private NotificationService mNotificationService = new NotificationService(
132 this);
133 private MessageParser mMessageParser = new MessageParser(this);
134 private PresenceParser mPresenceParser = new PresenceParser(this);
135 private IqParser mIqParser = new IqParser(this);
136 private MessageGenerator mMessageGenerator = new MessageGenerator(this);
137 private PresenceGenerator mPresenceGenerator = new PresenceGenerator(this);
138 private List<Account> accounts;
139 private final CopyOnWriteArrayList<Conversation> conversations = new CopyOnWriteArrayList<Conversation>();
140 private JingleConnectionManager mJingleConnectionManager = new JingleConnectionManager(
141 this);
142 private HttpConnectionManager mHttpConnectionManager = new HttpConnectionManager(
143 this);
144 private AvatarService mAvatarService = new AvatarService(this);
145 private MessageArchiveService mMessageArchiveService = new MessageArchiveService(this);
146 private OnConversationUpdate mOnConversationUpdate = null;
147 private Integer convChangedListenerCount = 0;
148 private OnAccountUpdate mOnAccountUpdate = null;
149 private OnStatusChanged statusListener = new OnStatusChanged() {
150
151 @Override
152 public void onStatusChanged(Account account) {
153 XmppConnection connection = account.getXmppConnection();
154 if (mOnAccountUpdate != null) {
155 mOnAccountUpdate.onAccountUpdate();
156 }
157 if (account.getStatus() == Account.State.ONLINE) {
158 for (Conversation conversation : account.pendingConferenceLeaves) {
159 leaveMuc(conversation);
160 }
161 for (Conversation conversation : account.pendingConferenceJoins) {
162 joinMuc(conversation);
163 }
164 mJingleConnectionManager.cancelInTransmission();
165 List<Conversation> conversations = getConversations();
166 for (Conversation conversation : conversations) {
167 if (conversation.getAccount() == account) {
168 conversation.startOtrIfNeeded();
169 sendUnsendMessages(conversation);
170 }
171 }
172 if (connection != null && connection.getFeatures().csi()) {
173 if (checkListeners()) {
174 Log.d(Config.LOGTAG, account.getJid().toBareJid()
175 + " sending csi//inactive");
176 connection.sendInactive();
177 } else {
178 Log.d(Config.LOGTAG, account.getJid().toBareJid()
179 + " sending csi//active");
180 connection.sendActive();
181 }
182 }
183 syncDirtyContacts(account);
184 scheduleWakeupCall(Config.PING_MAX_INTERVAL, true);
185 } else if (account.getStatus() == Account.State.OFFLINE) {
186 resetSendingToWaiting(account);
187 if (!account.isOptionSet(Account.OPTION_DISABLED)) {
188 int timeToReconnect = mRandom.nextInt(50) + 10;
189 scheduleWakeupCall(timeToReconnect, false);
190 }
191 } else if (account.getStatus() == Account.State.REGISTRATION_SUCCESSFUL) {
192 databaseBackend.updateAccount(account);
193 reconnectAccount(account, true);
194 } else if ((account.getStatus() != Account.State.CONNECTING)
195 && (account.getStatus() != Account.State.NO_INTERNET)) {
196 if (connection != null) {
197 int next = connection.getTimeToNextAttempt();
198 Log.d(Config.LOGTAG, account.getJid().toBareJid()
199 + ": error connecting account. try again in "
200 + next + "s for the "
201 + (connection.getAttempt() + 1) + " time");
202 scheduleWakeupCall((int) (next * 1.2), false);
203 }
204 }
205 getNotificationService().updateErrorNotification();
206 }
207 };
208
209 private int accountChangedListenerCount = 0;
210 private OnRosterUpdate mOnRosterUpdate = null;
211 private int rosterChangedListenerCount = 0;
212 private OnMucRosterUpdate mOnMucRosterUpdate = null;
213 private int mucRosterChangedListenerCount = 0;
214 private SecureRandom mRandom;
215 private FileObserver fileObserver = new FileObserver(
216 FileBackend.getConversationsImageDirectory()) {
217
218 @Override
219 public void onEvent(int event, String path) {
220 if (event == FileObserver.DELETE) {
221 markFileDeleted(path.split("\\.")[0]);
222 }
223 }
224 };
225 private OnJinglePacketReceived jingleListener = new OnJinglePacketReceived() {
226
227 @Override
228 public void onJinglePacketReceived(Account account, JinglePacket packet) {
229 mJingleConnectionManager.deliverPacket(account, packet);
230 }
231 };
232
233 private OpenPgpServiceConnection pgpServiceConnection;
234 private PgpEngine mPgpEngine = null;
235 private Intent pingIntent;
236 private PendingIntent pendingPingIntent = null;
237 private WakeLock wakeLock;
238 private PowerManager pm;
239 private OnBindListener mOnBindListener = new OnBindListener() {
240
241 @Override
242 public void onBind(final Account account) {
243 account.getRoster().clearPresences();
244 account.clearPresences(); // self presences
245 account.pendingConferenceJoins.clear();
246 account.pendingConferenceLeaves.clear();
247 fetchRosterFromServer(account);
248 fetchBookmarks(account);
249 sendPresencePacket(account,
250 mPresenceGenerator.sendPresence(account));
251 connectMultiModeConversations(account);
252 updateConversationUi();
253 }
254 };
255
256 private OnMessageAcknowledged mOnMessageAcknowledgedListener = new OnMessageAcknowledged() {
257
258 @Override
259 public void onMessageAcknowledged(Account account, String uuid) {
260 for (Conversation conversation : getConversations()) {
261 if (conversation.getAccount() == account) {
262 for (Message message : conversation.getMessages()) {
263 if ((message.getStatus() == Message.STATUS_UNSEND || message
264 .getStatus() == Message.STATUS_WAITING)
265 && message.getUuid().equals(uuid)) {
266 markMessage(message, Message.STATUS_SEND);
267 return;
268 }
269 }
270 }
271 }
272 }
273 };
274 private LruCache<String, Bitmap> mBitmapCache;
275 private IqGenerator mIqGenerator = new IqGenerator(this);
276
277 public PgpEngine getPgpEngine() {
278 if (pgpServiceConnection.isBound()) {
279 if (this.mPgpEngine == null) {
280 this.mPgpEngine = new PgpEngine(new OpenPgpApi(
281 getApplicationContext(),
282 pgpServiceConnection.getService()), this);
283 }
284 return mPgpEngine;
285 } else {
286 return null;
287 }
288
289 }
290
291 public FileBackend getFileBackend() {
292 return this.fileBackend;
293 }
294
295 public AvatarService getAvatarService() {
296 return this.mAvatarService;
297 }
298
299 public void attachFileToConversation(Conversation conversation, final Uri uri, final UiCallback<Message> callback) {
300 final Message message;
301 if (conversation.getNextEncryption(forceEncryption()) == Message.ENCRYPTION_PGP) {
302 message = new Message(conversation, "",
303 Message.ENCRYPTION_DECRYPTED);
304 } else {
305 message = new Message(conversation, "",
306 conversation.getNextEncryption(forceEncryption()));
307 }
308 message.setCounterpart(conversation.getNextCounterpart());
309 message.setType(Message.TYPE_FILE);
310 message.setStatus(Message.STATUS_OFFERED);
311 String path = getFileBackend().getOriginalPath(uri);
312 if (path!=null) {
313 message.setRelativeFilePath(path);
314 getFileBackend().updateFileParams(message);
315 if (message.getEncryption() == Message.ENCRYPTION_DECRYPTED) {
316 getPgpEngine().encrypt(message, callback);
317 } else {
318 callback.success(message);
319 }
320 } else {
321 new Thread(new Runnable() {
322 @Override
323 public void run() {
324 try {
325 getFileBackend().copyFileToPrivateStorage(message, uri);
326 getFileBackend().updateFileParams(message);
327 if (message.getEncryption() == Message.ENCRYPTION_DECRYPTED) {
328 getPgpEngine().encrypt(message, callback);
329 } else {
330 callback.success(message);
331 }
332 } catch (FileBackend.FileCopyException e) {
333 callback.error(e.getResId(),message);
334 }
335 }
336 }).start();
337
338 }
339 }
340
341 public void attachImageToConversation(final Conversation conversation,
342 final Uri uri, final UiCallback<Message> callback) {
343 final Message message;
344 if (conversation.getNextEncryption(forceEncryption()) == Message.ENCRYPTION_PGP) {
345 message = new Message(conversation, "",
346 Message.ENCRYPTION_DECRYPTED);
347 } else {
348 message = new Message(conversation, "",
349 conversation.getNextEncryption(forceEncryption()));
350 }
351 message.setCounterpart(conversation.getNextCounterpart());
352 message.setType(Message.TYPE_IMAGE);
353 message.setStatus(Message.STATUS_OFFERED);
354 new Thread(new Runnable() {
355
356 @Override
357 public void run() {
358 try {
359 DownloadableFile file = getFileBackend().copyImageToPrivateStorage(message, uri);
360 if (conversation.getNextEncryption(forceEncryption()) == Message.ENCRYPTION_PGP) {
361 getPgpEngine().encrypt(message, callback);
362 } else {
363 callback.success(message);
364 }
365 } catch (FileBackend.FileCopyException e) {
366 callback.error(e.getResId(), message);
367 }
368 }
369 }).start();
370 }
371
372 public Conversation find(Bookmark bookmark) {
373 return find(bookmark.getAccount(), bookmark.getJid());
374 }
375
376 public Conversation find(final Account account, final Jid jid) {
377 return find(getConversations(), account, jid);
378 }
379
380 @Override
381 public int onStartCommand(Intent intent, int flags, int startId) {
382 if (intent != null && intent.getAction() != null) {
383 if (intent.getAction().equals(ACTION_MERGE_PHONE_CONTACTS)) {
384 mergePhoneContactsWithRoster();
385 return START_STICKY;
386 } else if (intent.getAction().equals(Intent.ACTION_SHUTDOWN)) {
387 logoutAndSave();
388 return START_NOT_STICKY;
389 } else if (intent.getAction().equals(ACTION_CLEAR_NOTIFICATION)) {
390 mNotificationService.clear();
391 } else if (intent.getAction().equals(ACTION_DISABLE_FOREGROUND)) {
392 getPreferences().edit().putBoolean("keep_foreground_service",false).commit();
393 toggleForegroundService();
394 }
395 }
396 this.wakeLock.acquire();
397
398 for (Account account : accounts) {
399 if (!account.isOptionSet(Account.OPTION_DISABLED)) {
400 if (!hasInternetConnection()) {
401 account.setStatus(Account.State.NO_INTERNET);
402 if (statusListener != null) {
403 statusListener.onStatusChanged(account);
404 }
405 } else {
406 if (account.getStatus() == Account.State.NO_INTERNET) {
407 account.setStatus(Account.State.OFFLINE);
408 if (statusListener != null) {
409 statusListener.onStatusChanged(account);
410 }
411 }
412 if (account.getStatus() == Account.State.ONLINE) {
413 long lastReceived = account.getXmppConnection()
414 .getLastPacketReceived();
415 long lastSent = account.getXmppConnection()
416 .getLastPingSent();
417 if (lastSent - lastReceived >= Config.PING_TIMEOUT * 1000) {
418 Log.d(Config.LOGTAG, account.getJid()
419 + ": ping timeout");
420 this.reconnectAccount(account, true);
421 } else if (SystemClock.elapsedRealtime() - lastReceived >= Config.PING_MIN_INTERVAL * 1000) {
422 account.getXmppConnection().sendPing();
423 this.scheduleWakeupCall(2, false);
424 }
425 } else if (account.getStatus() == Account.State.OFFLINE) {
426 if (account.getXmppConnection() == null) {
427 account.setXmppConnection(this
428 .createConnection(account));
429 }
430 new Thread(account.getXmppConnection()).start();
431 } else if ((account.getStatus() == Account.State.CONNECTING)
432 && ((SystemClock.elapsedRealtime() - account
433 .getXmppConnection().getLastConnect()) / 1000 >= Config.CONNECT_TIMEOUT)) {
434 Log.d(Config.LOGTAG, account.getJid()
435 + ": time out during connect reconnecting");
436 reconnectAccount(account, true);
437 } else {
438 if (account.getXmppConnection().getTimeToNextAttempt() <= 0) {
439 reconnectAccount(account, true);
440 }
441 }
442 // in any case. reschedule wakup call
443 this.scheduleWakeupCall(Config.PING_MAX_INTERVAL, true);
444 }
445 if (mOnAccountUpdate != null) {
446 mOnAccountUpdate.onAccountUpdate();
447 }
448 }
449 }
450 /*PowerManager pm = (PowerManager) this.getSystemService(Context.POWER_SERVICE);
451 if (!pm.isScreenOn()) {
452 removeStaleListeners();
453 }*/
454 if (wakeLock.isHeld()) {
455 try {
456 wakeLock.release();
457 } catch (final RuntimeException ignored) {
458 }
459 }
460 return START_STICKY;
461 }
462
463 public boolean hasInternetConnection() {
464 ConnectivityManager cm = (ConnectivityManager) getApplicationContext()
465 .getSystemService(Context.CONNECTIVITY_SERVICE);
466 NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
467 return activeNetwork != null && activeNetwork.isConnected();
468 }
469
470 @SuppressLint("TrulyRandom")
471 @Override
472 public void onCreate() {
473 ExceptionHelper.init(getApplicationContext());
474 PRNGFixes.apply();
475 this.mRandom = new SecureRandom();
476 this.mMemorizingTrustManager = new MemorizingTrustManager(
477 getApplicationContext());
478
479 int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
480 int cacheSize = maxMemory / 8;
481 this.mBitmapCache = new LruCache<String, Bitmap>(cacheSize) {
482 @Override
483 protected int sizeOf(String key, Bitmap bitmap) {
484 return bitmap.getByteCount() / 1024;
485 }
486 };
487
488 this.databaseBackend = DatabaseBackend.getInstance(getApplicationContext());
489 this.accounts = databaseBackend.getAccounts();
490
491 for (Account account : this.accounts) {
492 account.initOtrEngine(this);
493 this.databaseBackend.readRoster(account.getRoster());
494 }
495 initConversations();
496 this.mergePhoneContactsWithRoster();
497
498 getContentResolver().registerContentObserver(ContactsContract.Contacts.CONTENT_URI, true, contactObserver);
499 this.fileObserver.startWatching();
500 this.pgpServiceConnection = new OpenPgpServiceConnection(getApplicationContext(), "org.sufficientlysecure.keychain");
501 this.pgpServiceConnection.bindToService();
502
503 this.pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
504 this.wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,"XmppConnectionService");
505 toggleForegroundService();
506 }
507
508 public void toggleForegroundService() {
509 if (getPreferences().getBoolean("keep_foreground_service",false)) {
510 startForeground(NotificationService.FOREGROUND_NOTIFICATION_ID, this.mNotificationService.createForegroundNotification());
511 } else {
512 stopForeground(true);
513 }
514 }
515
516 @Override
517 public void onTaskRemoved(Intent rootIntent) {
518 super.onTaskRemoved(rootIntent);
519 if (!getPreferences().getBoolean("keep_foreground_service",false)) {
520 this.logoutAndSave();
521 }
522 }
523
524 private void logoutAndSave() {
525 for (Account account : accounts) {
526 databaseBackend.writeRoster(account.getRoster());
527 if (account.getXmppConnection() != null) {
528 disconnect(account, false);
529 }
530 }
531 Context context = getApplicationContext();
532 AlarmManager alarmManager = (AlarmManager) context
533 .getSystemService(Context.ALARM_SERVICE);
534 Intent intent = new Intent(context, EventReceiver.class);
535 alarmManager.cancel(PendingIntent.getBroadcast(context, 0, intent, 0));
536 Log.d(Config.LOGTAG, "good bye");
537 stopSelf();
538 }
539
540 protected void scheduleWakeupCall(int seconds, boolean ping) {
541 long timeToWake = SystemClock.elapsedRealtime() + seconds * 1000;
542 Context context = getApplicationContext();
543 AlarmManager alarmManager = (AlarmManager) context
544 .getSystemService(Context.ALARM_SERVICE);
545
546 if (ping) {
547 if (this.pingIntent == null) {
548 this.pingIntent = new Intent(context, EventReceiver.class);
549 this.pingIntent.setAction("ping");
550 this.pingIntent.putExtra("time", timeToWake);
551 this.pendingPingIntent = PendingIntent.getBroadcast(context, 0,
552 this.pingIntent, 0);
553 alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
554 timeToWake, pendingPingIntent);
555 } else {
556 long scheduledTime = this.pingIntent.getLongExtra("time", 0);
557 if (scheduledTime < SystemClock.elapsedRealtime()
558 || (scheduledTime > timeToWake)) {
559 this.pingIntent.putExtra("time", timeToWake);
560 alarmManager.cancel(this.pendingPingIntent);
561 this.pendingPingIntent = PendingIntent.getBroadcast(
562 context, 0, this.pingIntent, 0);
563 alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
564 timeToWake, pendingPingIntent);
565 }
566 }
567 } else {
568 Intent intent = new Intent(context, EventReceiver.class);
569 intent.setAction("ping_check");
570 PendingIntent alarmIntent = PendingIntent.getBroadcast(context, 0,
571 intent, 0);
572 alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, timeToWake,
573 alarmIntent);
574 }
575
576 }
577
578 public XmppConnection createConnection(Account account) {
579 SharedPreferences sharedPref = getPreferences();
580 account.setResource(sharedPref.getString("resource", "mobile")
581 .toLowerCase(Locale.getDefault()));
582 XmppConnection connection = new XmppConnection(account, this);
583 connection.setOnMessagePacketReceivedListener(this.mMessageParser);
584 connection.setOnStatusChangedListener(this.statusListener);
585 connection.setOnPresencePacketReceivedListener(this.mPresenceParser);
586 connection.setOnUnregisteredIqPacketReceivedListener(this.mIqParser);
587 connection.setOnJinglePacketReceivedListener(this.jingleListener);
588 connection.setOnBindListener(this.mOnBindListener);
589 connection.setOnMessageAcknowledgeListener(this.mOnMessageAcknowledgedListener);
590 connection.addOnAdvancedStreamFeaturesAvailableListener(this.mMessageArchiveService);
591 return connection;
592 }
593
594 public void sendMessage(Message message) {
595 Account account = message.getConversation().getAccount();
596 account.deactivateGracePeriod();
597 Conversation conv = message.getConversation();
598 MessagePacket packet = null;
599 boolean saveInDb = true;
600 boolean send = false;
601 if (account.getStatus() == Account.State.ONLINE
602 && account.getXmppConnection() != null) {
603 if (message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE) {
604 if (message.getCounterpart() != null) {
605 if (message.getEncryption() == Message.ENCRYPTION_OTR) {
606 if (!conv.hasValidOtrSession()) {
607 conv.startOtrSession(message.getCounterpart().getResourcepart(),true);
608 message.setStatus(Message.STATUS_WAITING);
609 } else if (conv.hasValidOtrSession()
610 && conv.getOtrSession().getSessionStatus() == SessionStatus.ENCRYPTED) {
611 mJingleConnectionManager
612 .createNewConnection(message);
613 }
614 } else {
615 mJingleConnectionManager.createNewConnection(message);
616 }
617 } else {
618 if (message.getEncryption() == Message.ENCRYPTION_OTR) {
619 conv.startOtrIfNeeded();
620 }
621 message.setStatus(Message.STATUS_WAITING);
622 }
623 } else {
624 if (message.getEncryption() == Message.ENCRYPTION_OTR) {
625 if (!conv.hasValidOtrSession() && (message.getCounterpart() != null)) {
626 conv.startOtrSession(message.getCounterpart().getResourcepart(), true);
627 message.setStatus(Message.STATUS_WAITING);
628 } else if (conv.hasValidOtrSession()) {
629 if (conv.getOtrSession().getSessionStatus() == SessionStatus.ENCRYPTED) {
630 packet = mMessageGenerator.generateOtrChat(message);
631 send = true;
632 } else {
633 message.setStatus(Message.STATUS_WAITING);
634 conv.startOtrIfNeeded();
635 }
636 } else {
637 message.setStatus(Message.STATUS_WAITING);
638 }
639 } else if (message.getEncryption() == Message.ENCRYPTION_DECRYPTED) {
640 message.getConversation().endOtrIfNeeded();
641 failWaitingOtrMessages(message.getConversation());
642 packet = mMessageGenerator.generatePgpChat(message);
643 send = true;
644 } else {
645 message.getConversation().endOtrIfNeeded();
646 failWaitingOtrMessages(message.getConversation());
647 packet = mMessageGenerator.generateChat(message);
648 send = true;
649 }
650 }
651 if (!account.getXmppConnection().getFeatures().sm()
652 && conv.getMode() != Conversation.MODE_MULTI) {
653 message.setStatus(Message.STATUS_SEND);
654 }
655 } else {
656 message.setStatus(Message.STATUS_WAITING);
657 if (message.getType() == Message.TYPE_TEXT) {
658 if (message.getEncryption() == Message.ENCRYPTION_DECRYPTED) {
659 String pgpBody = message.getEncryptedBody();
660 String decryptedBody = message.getBody();
661 message.setBody(pgpBody);
662 message.setEncryption(Message.ENCRYPTION_PGP);
663 databaseBackend.createMessage(message);
664 saveInDb = false;
665 message.setBody(decryptedBody);
666 message.setEncryption(Message.ENCRYPTION_DECRYPTED);
667 } else if (message.getEncryption() == Message.ENCRYPTION_OTR) {
668 if (!conv.hasValidOtrSession()
669 && message.getCounterpart() != null) {
670 conv.startOtrSession(message.getCounterpart().getResourcepart(), false);
671 }
672 }
673 }
674
675 }
676 conv.add(message);
677 if (saveInDb) {
678 if (message.getEncryption() == Message.ENCRYPTION_NONE
679 || saveEncryptedMessages()) {
680 databaseBackend.createMessage(message);
681 }
682 }
683 if ((send) && (packet != null)) {
684 sendMessagePacket(account, packet);
685 }
686 updateConversationUi();
687 }
688
689 private void sendUnsendMessages(Conversation conversation) {
690 for (int i = 0; i < conversation.getMessages().size(); ++i) {
691 int status = conversation.getMessages().get(i).getStatus();
692 if (status == Message.STATUS_WAITING) {
693 resendMessage(conversation.getMessages().get(i));
694 }
695 }
696 }
697
698 private void resendMessage(Message message) {
699 Account account = message.getConversation().getAccount();
700 MessagePacket packet = null;
701 if (message.getEncryption() == Message.ENCRYPTION_OTR) {
702 Presences presences = message.getConversation().getContact()
703 .getPresences();
704 if (!message.getConversation().hasValidOtrSession()) {
705 if ((message.getCounterpart() != null)
706 && (presences.has(message.getCounterpart().getResourcepart()))) {
707 message.getConversation().startOtrSession(message.getCounterpart().getResourcepart(), true);
708 } else {
709 if (presences.size() == 1) {
710 String presence = presences.asStringArray()[0];
711 message.getConversation().startOtrSession(presence, true);
712 }
713 }
714 } else {
715 if (message.getConversation().getOtrSession()
716 .getSessionStatus() == SessionStatus.ENCRYPTED) {
717 try {
718 message.setCounterpart(Jid.fromSessionID(message.getConversation().getOtrSession().getSessionID()));
719 if (message.getType() == Message.TYPE_TEXT) {
720 packet = mMessageGenerator.generateOtrChat(message,
721 true);
722 } else if (message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE) {
723 mJingleConnectionManager.createNewConnection(message);
724 }
725 } catch (InvalidJidException e) {
726
727 }
728 }
729 }
730 } else if (message.getType() == Message.TYPE_TEXT) {
731 if (message.getEncryption() == Message.ENCRYPTION_NONE) {
732 packet = mMessageGenerator.generateChat(message, true);
733 } else if ((message.getEncryption() == Message.ENCRYPTION_DECRYPTED)
734 || (message.getEncryption() == Message.ENCRYPTION_PGP)) {
735 packet = mMessageGenerator.generatePgpChat(message, true);
736 }
737 } else if (message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE) {
738 Contact contact = message.getConversation().getContact();
739 Presences presences = contact.getPresences();
740 if ((message.getCounterpart() != null)
741 && (presences.has(message.getCounterpart().getResourcepart()))) {
742 markMessage(message, Message.STATUS_OFFERED);
743 mJingleConnectionManager.createNewConnection(message);
744 } else {
745 if (presences.size() == 1) {
746 String presence = presences.asStringArray()[0];
747 try {
748 message.setCounterpart(Jid.fromParts(contact.getJid().getLocalpart(), contact.getJid().getDomainpart(), presence));
749 } catch (InvalidJidException e) {
750 return;
751 }
752 markMessage(message, Message.STATUS_OFFERED);
753 mJingleConnectionManager.createNewConnection(message);
754 }
755 }
756 }
757 if (packet != null) {
758 if (!account.getXmppConnection().getFeatures().sm()
759 && message.getConversation().getMode() != Conversation.MODE_MULTI) {
760 markMessage(message, Message.STATUS_SEND);
761 } else {
762 markMessage(message, Message.STATUS_UNSEND);
763 }
764 sendMessagePacket(account, packet);
765 }
766 }
767
768 public void fetchRosterFromServer(Account account) {
769 IqPacket iqPacket = new IqPacket(IqPacket.TYPE_GET);
770 if (!"".equals(account.getRosterVersion())) {
771 Log.d(Config.LOGTAG, account.getJid().toBareJid()
772 + ": fetching roster version " + account.getRosterVersion());
773 } else {
774 Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": fetching roster");
775 }
776 iqPacket.query("jabber:iq:roster").setAttribute("ver",
777 account.getRosterVersion());
778 account.getXmppConnection().sendIqPacket(iqPacket,
779 new OnIqPacketReceived() {
780
781 @Override
782 public void onIqPacketReceived(final Account account,
783 IqPacket packet) {
784 Element query = packet.findChild("query");
785 if (query != null) {
786 account.getRoster().markAllAsNotInRoster();
787 mIqParser.rosterItems(account, query);
788 }
789 }
790 });
791 }
792
793 public void fetchBookmarks(Account account) {
794 IqPacket iqPacket = new IqPacket(IqPacket.TYPE_GET);
795 Element query = iqPacket.query("jabber:iq:private");
796 query.addChild("storage", "storage:bookmarks");
797 OnIqPacketReceived callback = new OnIqPacketReceived() {
798
799 @Override
800 public void onIqPacketReceived(Account account, IqPacket packet) {
801 Element query = packet.query();
802 List<Bookmark> bookmarks = new CopyOnWriteArrayList<>();
803 Element storage = query.findChild("storage",
804 "storage:bookmarks");
805 if (storage != null) {
806 for (Element item : storage.getChildren()) {
807 if (item.getName().equals("conference")) {
808 Bookmark bookmark = Bookmark.parse(item, account);
809 bookmarks.add(bookmark);
810 Conversation conversation = find(bookmark);
811 if (conversation != null) {
812 conversation.setBookmark(bookmark);
813 } else if (bookmark.autojoin() && bookmark.getJid() != null) {
814 conversation = findOrCreateConversation(
815 account, bookmark.getJid(), true);
816 conversation.setBookmark(bookmark);
817 joinMuc(conversation);
818 }
819 }
820 }
821 }
822 account.setBookmarks(bookmarks);
823 }
824 };
825 sendIqPacket(account, iqPacket, callback);
826
827 }
828
829 public void pushBookmarks(Account account) {
830 IqPacket iqPacket = new IqPacket(IqPacket.TYPE_SET);
831 Element query = iqPacket.query("jabber:iq:private");
832 Element storage = query.addChild("storage", "storage:bookmarks");
833 for (Bookmark bookmark : account.getBookmarks()) {
834 storage.addChild(bookmark);
835 }
836 sendIqPacket(account, iqPacket, null);
837 }
838
839 private void mergePhoneContactsWithRoster() {
840 PhoneHelper.loadPhoneContacts(getApplicationContext(),
841 new OnPhoneContactsLoadedListener() {
842 @Override
843 public void onPhoneContactsLoaded(List<Bundle> phoneContacts) {
844 for (Account account : accounts) {
845 account.getRoster().clearSystemAccounts();
846 }
847 for (Bundle phoneContact : phoneContacts) {
848 for (Account account : accounts) {
849 Jid jid;
850 try {
851 jid = Jid.fromString(phoneContact.getString("jid"));
852 } catch (final InvalidJidException e) {
853 // TODO: Warn if contact import fails here?
854 break;
855 }
856 final Contact contact = account.getRoster()
857 .getContact(jid);
858 String systemAccount = phoneContact
859 .getInt("phoneid")
860 + "#"
861 + phoneContact.getString("lookup");
862 contact.setSystemAccount(systemAccount);
863 contact.setPhotoUri(phoneContact
864 .getString("photouri"));
865 contact.setSystemName(phoneContact
866 .getString("displayname"));
867 getAvatarService().clear(contact);
868 }
869 }
870 }
871 });
872 }
873
874 private void initConversations() {
875 synchronized (this.conversations) {
876 Hashtable<String, Account> accountLookupTable = new Hashtable<>();
877 for (Account account : this.accounts) {
878 accountLookupTable.put(account.getUuid(), account);
879 }
880 this.conversations.addAll(databaseBackend.getConversations(Conversation.STATUS_AVAILABLE));
881 for (Conversation conv : this.conversations) {
882 Account account = accountLookupTable.get(conv.getAccountUuid());
883 conv.setAccount(account);
884 conv.addAll(0, databaseBackend.getMessages(conv, 50));
885 checkDeletedFiles(conv);
886 }
887 }
888 }
889
890 public List<Conversation> getConversations() {
891 return this.conversations;
892 }
893
894 private void checkDeletedFiles(Conversation conversation) {
895 for (Message message : conversation.getMessages()) {
896 if ((message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE)
897 && message.getEncryption() != Message.ENCRYPTION_PGP) {
898 if (!getFileBackend().isFileAvailable(message)) {
899 message.setDownloadable(new DownloadablePlaceholder(Downloadable.STATUS_DELETED));
900 }
901 }
902 }
903 }
904
905 private void markFileDeleted(String uuid) {
906 for (Conversation conversation : getConversations()) {
907 for (Message message : conversation.getMessages()) {
908 if (message.getType() == Message.TYPE_IMAGE
909 && message.getEncryption() != Message.ENCRYPTION_PGP
910 && message.getUuid().equals(uuid)) {
911 if (!getFileBackend().isFileAvailable(message)) {
912 message.setDownloadable(new DownloadablePlaceholder(Downloadable.STATUS_DELETED));
913 updateConversationUi();
914 }
915 return;
916 }
917 }
918 }
919 }
920
921 public void populateWithOrderedConversations(final List<Conversation> list) {
922 populateWithOrderedConversations(list, true);
923 }
924
925 public void populateWithOrderedConversations(final List<Conversation> list, boolean includeConferences) {
926 list.clear();
927 if (includeConferences) {
928 list.addAll(getConversations());
929 } else {
930 for (Conversation conversation : getConversations()) {
931 if (conversation.getMode() == Conversation.MODE_SINGLE) {
932 list.add(conversation);
933 }
934 }
935 }
936 Collections.sort(list, new Comparator<Conversation>() {
937 @Override
938 public int compare(Conversation lhs, Conversation rhs) {
939 Message left = lhs.getLatestMessage();
940 Message right = rhs.getLatestMessage();
941 if (left.getTimeSent() > right.getTimeSent()) {
942 return -1;
943 } else if (left.getTimeSent() < right.getTimeSent()) {
944 return 1;
945 } else {
946 return 0;
947 }
948 }
949 });
950 }
951
952 public int loadMoreMessages(Conversation conversation, long timestamp) {
953 List<Message> messages = databaseBackend.getMessages(conversation, 50,
954 timestamp);
955 for (Message message : messages) {
956 message.setConversation(conversation);
957 }
958 conversation.addAll(0, messages);
959 return messages.size();
960 }
961
962 public List<Account> getAccounts() {
963 return this.accounts;
964 }
965
966 public Conversation find(List<Conversation> haystack, Contact contact) {
967 for (Conversation conversation : haystack) {
968 if (conversation.getContact() == contact) {
969 return conversation;
970 }
971 }
972 return null;
973 }
974
975 public Conversation find(final List<Conversation> haystack,
976 final Account account,
977 final Jid jid) {
978 if (jid == null ) {
979 return null;
980 }
981 for (Conversation conversation : haystack) {
982 if ((account == null || conversation.getAccount() == account)
983 && (conversation.getContactJid().toBareJid().equals(jid.toBareJid()))) {
984 return conversation;
985 }
986 }
987 return null;
988 }
989
990 public Conversation findOrCreateConversation(final Account account, final Jid jid,
991 final boolean muc) {
992 synchronized (this.conversations) {
993 Conversation conversation = find(account, jid);
994 if (conversation != null) {
995 return conversation;
996 }
997 conversation = databaseBackend.findConversation(account, jid);
998 if (conversation != null) {
999 conversation.setStatus(Conversation.STATUS_AVAILABLE);
1000 conversation.setAccount(account);
1001 if (muc) {
1002 conversation.setMode(Conversation.MODE_MULTI);
1003 } else {
1004 conversation.setMode(Conversation.MODE_SINGLE);
1005 }
1006 conversation.addAll(0, databaseBackend.getMessages(conversation, 50));
1007 this.databaseBackend.updateConversation(conversation);
1008 } else {
1009 String conversationName;
1010 Contact contact = account.getRoster().getContact(jid);
1011 if (contact != null) {
1012 conversationName = contact.getDisplayName();
1013 } else {
1014 conversationName = jid.getLocalpart();
1015 }
1016 if (muc) {
1017 conversation = new Conversation(conversationName, account, jid,
1018 Conversation.MODE_MULTI);
1019 } else {
1020 conversation = new Conversation(conversationName, account, jid,
1021 Conversation.MODE_SINGLE);
1022 }
1023 this.databaseBackend.createConversation(conversation);
1024 }
1025 this.mMessageArchiveService.query(conversation);
1026 this.conversations.add(conversation);
1027 updateConversationUi();
1028 return conversation;
1029 }
1030 }
1031
1032 public void archiveConversation(Conversation conversation) {
1033 synchronized (this.conversations) {
1034 if (conversation.getMode() == Conversation.MODE_MULTI) {
1035 if (conversation.getAccount().getStatus() == Account.State.ONLINE) {
1036 Bookmark bookmark = conversation.getBookmark();
1037 if (bookmark != null && bookmark.autojoin()) {
1038 bookmark.setAutojoin(false);
1039 pushBookmarks(bookmark.getAccount());
1040 }
1041 }
1042 leaveMuc(conversation);
1043 } else {
1044 conversation.endOtrIfNeeded();
1045 }
1046 this.databaseBackend.updateConversation(conversation);
1047 this.conversations.remove(conversation);
1048 updateConversationUi();
1049 }
1050 }
1051
1052 public void clearConversationHistory(Conversation conversation) {
1053 this.databaseBackend.deleteMessagesInConversation(conversation);
1054 conversation.getMessages().clear();
1055 updateConversationUi();
1056 }
1057
1058 public void createAccount(Account account) {
1059 account.initOtrEngine(this);
1060 databaseBackend.createAccount(account);
1061 this.accounts.add(account);
1062 this.reconnectAccount(account, false);
1063 updateAccountUi();
1064 }
1065
1066 public void updateAccount(Account account) {
1067 this.statusListener.onStatusChanged(account);
1068 databaseBackend.updateAccount(account);
1069 reconnectAccount(account, false);
1070 updateAccountUi();
1071 getNotificationService().updateErrorNotification();
1072 }
1073
1074 public void deleteAccount(Account account) {
1075 synchronized (this.conversations) {
1076 for (Conversation conversation : conversations) {
1077 if (conversation.getAccount() == account) {
1078 if (conversation.getMode() == Conversation.MODE_MULTI) {
1079 leaveMuc(conversation);
1080 } else if (conversation.getMode() == Conversation.MODE_SINGLE) {
1081 conversation.endOtrIfNeeded();
1082 }
1083 conversations.remove(conversation);
1084 }
1085 }
1086 if (account.getXmppConnection() != null) {
1087 this.disconnect(account, true);
1088 }
1089 databaseBackend.deleteAccount(account);
1090 this.accounts.remove(account);
1091 updateAccountUi();
1092 getNotificationService().updateErrorNotification();
1093 }
1094 }
1095
1096 public void setOnConversationListChangedListener(OnConversationUpdate listener) {
1097 synchronized (this) {
1098 if (checkListeners()) {
1099 switchToForeground();
1100 }
1101 this.mOnConversationUpdate = listener;
1102 this.mNotificationService.setIsInForeground(true);
1103 if (this.convChangedListenerCount < 2) {
1104 this.convChangedListenerCount++;
1105 }
1106 }
1107 }
1108
1109 public void removeOnConversationListChangedListener() {
1110 synchronized (this) {
1111 this.convChangedListenerCount--;
1112 if (this.convChangedListenerCount <= 0) {
1113 this.convChangedListenerCount = 0;
1114 this.mOnConversationUpdate = null;
1115 this.mNotificationService.setIsInForeground(false);
1116 if (checkListeners()) {
1117 switchToBackground();
1118 }
1119 }
1120 }
1121 }
1122
1123 public void setOnAccountListChangedListener(OnAccountUpdate listener) {
1124 synchronized (this) {
1125 if (checkListeners()) {
1126 switchToForeground();
1127 }
1128 this.mOnAccountUpdate = listener;
1129 if (this.accountChangedListenerCount < 2) {
1130 this.accountChangedListenerCount++;
1131 }
1132 }
1133 }
1134
1135 public void removeOnAccountListChangedListener() {
1136 synchronized (this) {
1137 this.accountChangedListenerCount--;
1138 if (this.accountChangedListenerCount <= 0) {
1139 this.mOnAccountUpdate = null;
1140 this.accountChangedListenerCount = 0;
1141 if (checkListeners()) {
1142 switchToBackground();
1143 }
1144 }
1145 }
1146 }
1147
1148 public void setOnRosterUpdateListener(OnRosterUpdate listener) {
1149 synchronized (this) {
1150 if (checkListeners()) {
1151 switchToForeground();
1152 }
1153 this.mOnRosterUpdate = listener;
1154 if (this.rosterChangedListenerCount < 2) {
1155 this.rosterChangedListenerCount++;
1156 }
1157 }
1158 }
1159
1160 public void removeOnRosterUpdateListener() {
1161 synchronized (this) {
1162 this.rosterChangedListenerCount--;
1163 if (this.rosterChangedListenerCount <= 0) {
1164 this.rosterChangedListenerCount = 0;
1165 this.mOnRosterUpdate = null;
1166 if (checkListeners()) {
1167 switchToBackground();
1168 }
1169 }
1170 }
1171 }
1172
1173 public void setOnMucRosterUpdateListener(OnMucRosterUpdate listener) {
1174 synchronized (this) {
1175 if (checkListeners()) {
1176 switchToForeground();
1177 }
1178 this.mOnMucRosterUpdate = listener;
1179 if (this.mucRosterChangedListenerCount < 2) {
1180 this.mucRosterChangedListenerCount++;
1181 }
1182 }
1183 }
1184
1185 public void removeOnMucRosterUpdateListener() {
1186 synchronized (this) {
1187 this.mucRosterChangedListenerCount--;
1188 if (this.mucRosterChangedListenerCount <= 0) {
1189 this.mucRosterChangedListenerCount = 0;
1190 this.mOnMucRosterUpdate = null;
1191 if (checkListeners()) {
1192 switchToBackground();
1193 }
1194 }
1195 }
1196 }
1197
1198 private boolean checkListeners() {
1199 return (this.mOnAccountUpdate == null
1200 && this.mOnConversationUpdate == null && this.mOnRosterUpdate == null);
1201 }
1202
1203 private void switchToForeground() {
1204 for (Account account : getAccounts()) {
1205 if (account.getStatus() == Account.State.ONLINE) {
1206 XmppConnection connection = account.getXmppConnection();
1207 if (connection != null && connection.getFeatures().csi()) {
1208 connection.sendActive();
1209 }
1210 }
1211 }
1212 Log.d(Config.LOGTAG, "app switched into foreground");
1213 }
1214
1215 private void switchToBackground() {
1216 for (Account account : getAccounts()) {
1217 if (account.getStatus() == Account.State.ONLINE) {
1218 XmppConnection connection = account.getXmppConnection();
1219 if (connection != null && connection.getFeatures().csi()) {
1220 connection.sendInactive();
1221 }
1222 }
1223 }
1224 this.mNotificationService.setIsInForeground(false);
1225 Log.d(Config.LOGTAG, "app switched into background");
1226 }
1227
1228 public void connectMultiModeConversations(Account account) {
1229 List<Conversation> conversations = getConversations();
1230 for (Conversation conversation : conversations) {
1231 if ((conversation.getMode() == Conversation.MODE_MULTI)
1232 && (conversation.getAccount() == account)) {
1233 joinMuc(conversation);
1234 }
1235 }
1236 }
1237
1238 public void joinMuc(Conversation conversation) {
1239 Account account = conversation.getAccount();
1240 account.pendingConferenceJoins.remove(conversation);
1241 account.pendingConferenceLeaves.remove(conversation);
1242 if (account.getStatus() == Account.State.ONLINE) {
1243 final String nick = conversation.getMucOptions().getProposedNick();
1244 final Jid joinJid = conversation.getMucOptions().createJoinJid(nick);
1245 if (joinJid == null) {
1246 return; //safety net
1247 }
1248 Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": joining conversation " + joinJid.toString());
1249 PresencePacket packet = new PresencePacket();
1250 packet.setFrom(conversation.getAccount().getJid());
1251 packet.setTo(joinJid);
1252 Element x = packet.addChild("x","http://jabber.org/protocol/muc");
1253 if (conversation.getMucOptions().getPassword() != null) {
1254 x.addChild("password").setContent(conversation.getMucOptions().getPassword());
1255 }
1256 x.addChild("history").setAttribute("since",PresenceGenerator.getTimestamp(conversation.getLastMessageReceived()));
1257 String sig = account.getPgpSignature();
1258 if (sig != null) {
1259 packet.addChild("status").setContent("online");
1260 packet.addChild("x", "jabber:x:signed").setContent(sig);
1261 }
1262 sendPresencePacket(account, packet);
1263 if (!joinJid.equals(conversation.getContactJid())) {
1264 conversation.setContactJid(joinJid);
1265 databaseBackend.updateConversation(conversation);
1266 }
1267 } else {
1268 account.pendingConferenceJoins.add(conversation);
1269 }
1270 }
1271
1272 public void providePasswordForMuc(Conversation conversation, String password) {
1273 if (conversation.getMode() == Conversation.MODE_MULTI) {
1274 conversation.getMucOptions().setPassword(password);
1275 if (conversation.getBookmark() != null) {
1276 conversation.getBookmark().setAutojoin(true);
1277 pushBookmarks(conversation.getAccount());
1278 }
1279 databaseBackend.updateConversation(conversation);
1280 joinMuc(conversation);
1281 }
1282 }
1283
1284 public void renameInMuc(final Conversation conversation, final String nick, final UiCallback<Conversation> callback) {
1285 final MucOptions options = conversation.getMucOptions();
1286 final Jid joinJid = options.createJoinJid(nick);
1287 if (options.online()) {
1288 Account account = conversation.getAccount();
1289 options.setOnRenameListener(new OnRenameListener() {
1290
1291 @Override
1292 public void onSuccess() {
1293 conversation.setContactJid(joinJid);
1294 databaseBackend.updateConversation(conversation);
1295 Bookmark bookmark = conversation.getBookmark();
1296 if (bookmark != null) {
1297 bookmark.setNick(nick);
1298 pushBookmarks(bookmark.getAccount());
1299 }
1300 callback.success(conversation);
1301 }
1302
1303 @Override
1304 public void onFailure() {
1305 callback.error(R.string.nick_in_use,conversation);
1306 }
1307 });
1308
1309 PresencePacket packet = new PresencePacket();
1310 packet.setTo(joinJid);
1311 packet.setFrom(conversation.getAccount().getJid());
1312
1313 String sig = account.getPgpSignature();
1314 if (sig != null) {
1315 packet.addChild("status").setContent("online");
1316 packet.addChild("x", "jabber:x:signed").setContent(sig);
1317 }
1318 sendPresencePacket(account, packet);
1319 } else {
1320 conversation.setContactJid(joinJid);
1321 databaseBackend.updateConversation(conversation);
1322 if (conversation.getAccount().getStatus() == Account.State.ONLINE) {
1323 Bookmark bookmark = conversation.getBookmark();
1324 if (bookmark != null) {
1325 bookmark.setNick(nick);
1326 pushBookmarks(bookmark.getAccount());
1327 }
1328 joinMuc(conversation);
1329 }
1330 }
1331 }
1332
1333 public void leaveMuc(Conversation conversation) {
1334 Account account = conversation.getAccount();
1335 account.pendingConferenceJoins.remove(conversation);
1336 account.pendingConferenceLeaves.remove(conversation);
1337 if (account.getStatus() == Account.State.ONLINE) {
1338 PresencePacket packet = new PresencePacket();
1339 packet.setTo(conversation.getContactJid());
1340 packet.setFrom(conversation.getAccount().getJid());
1341 packet.setAttribute("type", "unavailable");
1342 sendPresencePacket(conversation.getAccount(), packet);
1343 conversation.getMucOptions().setOffline();
1344 conversation.deregisterWithBookmark();
1345 Log.d(Config.LOGTAG, conversation.getAccount().getJid().toBareJid()
1346 + ": leaving muc " + conversation.getContactJid());
1347 } else {
1348 account.pendingConferenceLeaves.add(conversation);
1349 }
1350 }
1351
1352 private String findConferenceServer(final Account account) {
1353 String server;
1354 if (account.getXmppConnection() != null) {
1355 server = account.getXmppConnection().getMucServer();
1356 if (server != null) {
1357 return server;
1358 }
1359 }
1360 for(Account other : getAccounts()) {
1361 if (other != account && other.getXmppConnection() != null) {
1362 server = other.getXmppConnection().getMucServer();
1363 if (server != null) {
1364 return server;
1365 }
1366 }
1367 }
1368 return null;
1369 }
1370
1371 public void createAdhocConference(final Account account, final List<Jid> jids, final UiCallback<Conversation> callback) {
1372 Log.d(Config.LOGTAG,account.getJid().toBareJid().toString()+": creating adhoc conference with "+ jids.toString());
1373 if (account.getStatus() == Account.State.ONLINE) {
1374 try {
1375 String server = findConferenceServer(account);
1376 if (server == null) {
1377 if (callback != null) {
1378 callback.error(R.string.no_conference_server_found,null);
1379 }
1380 return;
1381 }
1382 String name = new BigInteger(75,getRNG()).toString(32);
1383 Jid jid = Jid.fromParts(name,server,null);
1384 final Conversation conversation = findOrCreateConversation(account, jid, true);
1385 joinMuc(conversation);
1386 Bundle options = new Bundle();
1387 options.putString("muc#roomconfig_persistentroom", "1");
1388 options.putString("muc#roomconfig_membersonly", "1");
1389 options.putString("muc#roomconfig_publicroom", "0");
1390 options.putString("muc#roomconfig_whois", "anyone");
1391 pushConferenceConfiguration(conversation, options, new OnConferenceOptionsPushed() {
1392 @Override
1393 public void onPushSucceeded() {
1394 for(Jid invite : jids) {
1395 invite(conversation,invite);
1396 }
1397 if (callback != null) {
1398 callback.success(conversation);
1399 }
1400 }
1401
1402 @Override
1403 public void onPushFailed() {
1404 if (callback != null) {
1405 callback.error(R.string.conference_creation_failed, conversation);
1406 }
1407 }
1408 });
1409
1410 } catch (InvalidJidException e) {
1411 if (callback != null) {
1412 callback.error(R.string.conference_creation_failed, null);
1413 }
1414 }
1415 } else {
1416 if (callback != null) {
1417 callback.error(R.string.not_connected_try_again,null);
1418 }
1419 }
1420 }
1421
1422 public void pushConferenceConfiguration(final Conversation conversation,final Bundle options, final OnConferenceOptionsPushed callback) {
1423 IqPacket request = new IqPacket(IqPacket.TYPE_GET);
1424 request.setTo(conversation.getContactJid().toBareJid());
1425 request.query("http://jabber.org/protocol/muc#owner");
1426 sendIqPacket(conversation.getAccount(),request,new OnIqPacketReceived() {
1427 @Override
1428 public void onIqPacketReceived(Account account, IqPacket packet) {
1429 if (packet.getType() != IqPacket.TYPE_ERROR) {
1430 Data data = Data.parse(packet.query().findChild("x", "jabber:x:data"));
1431 for (Field field : data.getFields()) {
1432 if (options.containsKey(field.getName())) {
1433 field.setValue(options.getString(field.getName()));
1434 }
1435 }
1436 data.submit();
1437 IqPacket set = new IqPacket(IqPacket.TYPE_SET);
1438 set.setTo(conversation.getContactJid().toBareJid());
1439 set.query("http://jabber.org/protocol/muc#owner").addChild(data);
1440 sendIqPacket(account, set, new OnIqPacketReceived() {
1441 @Override
1442 public void onIqPacketReceived(Account account, IqPacket packet) {
1443 if (packet.getType() == IqPacket.TYPE_RESULT) {
1444 if (callback != null) {
1445 callback.onPushSucceeded();
1446 }
1447 } else {
1448 if (callback != null) {
1449 callback.onPushFailed();
1450 }
1451 }
1452 }
1453 });
1454 } else {
1455 if (callback != null) {
1456 callback.onPushFailed();
1457 }
1458 }
1459 }
1460 });
1461 }
1462
1463 public void disconnect(Account account, boolean force) {
1464 if ((account.getStatus() == Account.State.ONLINE)
1465 || (account.getStatus() == Account.State.DISABLED)) {
1466 if (!force) {
1467 List<Conversation> conversations = getConversations();
1468 for (Conversation conversation : conversations) {
1469 if (conversation.getAccount() == account) {
1470 if (conversation.getMode() == Conversation.MODE_MULTI) {
1471 leaveMuc(conversation);
1472 } else {
1473 if (conversation.endOtrIfNeeded()) {
1474 Log.d(Config.LOGTAG, account.getJid().toBareJid()
1475 + ": ended otr session with "
1476 + conversation.getContactJid());
1477 }
1478 }
1479 }
1480 }
1481 }
1482 account.getXmppConnection().disconnect(force);
1483 }
1484 }
1485
1486 @Override
1487 public IBinder onBind(Intent intent) {
1488 return mBinder;
1489 }
1490
1491 public void updateMessage(Message message) {
1492 databaseBackend.updateMessage(message);
1493 updateConversationUi();
1494 }
1495
1496 protected void syncDirtyContacts(Account account) {
1497 for (Contact contact : account.getRoster().getContacts()) {
1498 if (contact.getOption(Contact.Options.DIRTY_PUSH)) {
1499 pushContactToServer(contact);
1500 }
1501 if (contact.getOption(Contact.Options.DIRTY_DELETE)) {
1502 deleteContactOnServer(contact);
1503 }
1504 }
1505 }
1506
1507 public void createContact(Contact contact) {
1508 SharedPreferences sharedPref = getPreferences();
1509 boolean autoGrant = sharedPref.getBoolean("grant_new_contacts", true);
1510 if (autoGrant) {
1511 contact.setOption(Contact.Options.PREEMPTIVE_GRANT);
1512 contact.setOption(Contact.Options.ASKING);
1513 }
1514 pushContactToServer(contact);
1515 }
1516
1517 public void onOtrSessionEstablished(Conversation conversation) {
1518 Account account = conversation.getAccount();
1519 List<Message> messages = conversation.getMessages();
1520 Session otrSession = conversation.getOtrSession();
1521 Log.d(Config.LOGTAG,
1522 account.getJid().toBareJid() + " otr session established with "
1523 + conversation.getContactJid() + "/"
1524 + otrSession.getSessionID().getUserID());
1525 for (Message msg : messages) {
1526 if ((msg.getStatus() == Message.STATUS_UNSEND || msg.getStatus() == Message.STATUS_WAITING)
1527 && (msg.getEncryption() == Message.ENCRYPTION_OTR)) {
1528 SessionID id = otrSession.getSessionID();
1529 try {
1530 msg.setCounterpart(Jid.fromString(id.getAccountID() + "/" + id.getUserID()));
1531 } catch (InvalidJidException e) {
1532 break;
1533 }
1534 if (msg.getType() == Message.TYPE_TEXT) {
1535 MessagePacket outPacket = mMessageGenerator
1536 .generateOtrChat(msg, true);
1537 if (outPacket != null) {
1538 msg.setStatus(Message.STATUS_SEND);
1539 databaseBackend.updateMessage(msg);
1540 sendMessagePacket(account, outPacket);
1541 }
1542 } else if (msg.getType() == Message.TYPE_IMAGE || msg.getType() == Message.TYPE_FILE) {
1543 mJingleConnectionManager.createNewConnection(msg);
1544 }
1545 }
1546 }
1547 updateConversationUi();
1548 }
1549
1550 public boolean renewSymmetricKey(Conversation conversation) {
1551 Account account = conversation.getAccount();
1552 byte[] symmetricKey = new byte[32];
1553 this.mRandom.nextBytes(symmetricKey);
1554 Session otrSession = conversation.getOtrSession();
1555 if (otrSession != null) {
1556 MessagePacket packet = new MessagePacket();
1557 packet.setType(MessagePacket.TYPE_CHAT);
1558 packet.setFrom(account.getJid());
1559 packet.addChild("private", "urn:xmpp:carbons:2");
1560 packet.addChild("no-copy", "urn:xmpp:hints");
1561 packet.setAttribute("to", otrSession.getSessionID().getAccountID() + "/"
1562 + otrSession.getSessionID().getUserID());
1563 try {
1564 packet.setBody(otrSession
1565 .transformSending(CryptoHelper.FILETRANSFER
1566 + CryptoHelper.bytesToHex(symmetricKey)));
1567 sendMessagePacket(account, packet);
1568 conversation.setSymmetricKey(symmetricKey);
1569 return true;
1570 } catch (OtrException e) {
1571 return false;
1572 }
1573 }
1574 return false;
1575 }
1576
1577 public void pushContactToServer(Contact contact) {
1578 contact.resetOption(Contact.Options.DIRTY_DELETE);
1579 contact.setOption(Contact.Options.DIRTY_PUSH);
1580 Account account = contact.getAccount();
1581 if (account.getStatus() == Account.State.ONLINE) {
1582 boolean ask = contact.getOption(Contact.Options.ASKING);
1583 boolean sendUpdates = contact
1584 .getOption(Contact.Options.PENDING_SUBSCRIPTION_REQUEST)
1585 && contact.getOption(Contact.Options.PREEMPTIVE_GRANT);
1586 IqPacket iq = new IqPacket(IqPacket.TYPE_SET);
1587 iq.query("jabber:iq:roster").addChild(contact.asElement());
1588 account.getXmppConnection().sendIqPacket(iq, null);
1589 if (sendUpdates) {
1590 sendPresencePacket(account,
1591 mPresenceGenerator.sendPresenceUpdatesTo(contact));
1592 }
1593 if (ask) {
1594 sendPresencePacket(account,
1595 mPresenceGenerator.requestPresenceUpdatesFrom(contact));
1596 }
1597 }
1598 }
1599
1600 public void publishAvatar(Account account, Uri image,
1601 final UiCallback<Avatar> callback) {
1602 final Bitmap.CompressFormat format = Config.AVATAR_FORMAT;
1603 final int size = Config.AVATAR_SIZE;
1604 final Avatar avatar = getFileBackend()
1605 .getPepAvatar(image, size, format);
1606 if (avatar != null) {
1607 avatar.height = size;
1608 avatar.width = size;
1609 if (format.equals(Bitmap.CompressFormat.WEBP)) {
1610 avatar.type = "image/webp";
1611 } else if (format.equals(Bitmap.CompressFormat.JPEG)) {
1612 avatar.type = "image/jpeg";
1613 } else if (format.equals(Bitmap.CompressFormat.PNG)) {
1614 avatar.type = "image/png";
1615 }
1616 if (!getFileBackend().save(avatar)) {
1617 callback.error(R.string.error_saving_avatar, avatar);
1618 return;
1619 }
1620 IqPacket packet = this.mIqGenerator.publishAvatar(avatar);
1621 this.sendIqPacket(account, packet, new OnIqPacketReceived() {
1622
1623 @Override
1624 public void onIqPacketReceived(Account account, IqPacket result) {
1625 if (result.getType() == IqPacket.TYPE_RESULT) {
1626 IqPacket packet = XmppConnectionService.this.mIqGenerator
1627 .publishAvatarMetadata(avatar);
1628 sendIqPacket(account, packet, new OnIqPacketReceived() {
1629
1630 @Override
1631 public void onIqPacketReceived(Account account,
1632 IqPacket result) {
1633 if (result.getType() == IqPacket.TYPE_RESULT) {
1634 if (account.setAvatar(avatar.getFilename())) {
1635 databaseBackend.updateAccount(account);
1636 }
1637 callback.success(avatar);
1638 } else {
1639 callback.error(
1640 R.string.error_publish_avatar_server_reject,
1641 avatar);
1642 }
1643 }
1644 });
1645 } else {
1646 callback.error(
1647 R.string.error_publish_avatar_server_reject,
1648 avatar);
1649 }
1650 }
1651 });
1652 } else {
1653 callback.error(R.string.error_publish_avatar_converting, null);
1654 }
1655 }
1656
1657 public void fetchAvatar(Account account, Avatar avatar) {
1658 fetchAvatar(account, avatar, null);
1659 }
1660
1661 public void fetchAvatar(Account account, final Avatar avatar,
1662 final UiCallback<Avatar> callback) {
1663 IqPacket packet = this.mIqGenerator.retrieveAvatar(avatar);
1664 sendIqPacket(account, packet, new OnIqPacketReceived() {
1665
1666 @Override
1667 public void onIqPacketReceived(Account account, IqPacket result) {
1668 final String ERROR = account.getJid().toBareJid()
1669 + ": fetching avatar for " + avatar.owner + " failed ";
1670 if (result.getType() == IqPacket.TYPE_RESULT) {
1671 avatar.image = mIqParser.avatarData(result);
1672 if (avatar.image != null) {
1673 if (getFileBackend().save(avatar)) {
1674 if (account.getJid().toBareJid().equals(avatar.owner)) {
1675 if (account.setAvatar(avatar.getFilename())) {
1676 databaseBackend.updateAccount(account);
1677 }
1678 getAvatarService().clear(account);
1679 updateConversationUi();
1680 updateAccountUi();
1681 } else {
1682 Contact contact = account.getRoster()
1683 .getContact(avatar.owner);
1684 contact.setAvatar(avatar.getFilename());
1685 getAvatarService().clear(contact);
1686 updateConversationUi();
1687 updateRosterUi();
1688 }
1689 if (callback != null) {
1690 callback.success(avatar);
1691 }
1692 Log.d(Config.LOGTAG, account.getJid().toBareJid()
1693 + ": succesfully fetched avatar for "
1694 + avatar.owner);
1695 return;
1696 }
1697 } else {
1698
1699 Log.d(Config.LOGTAG, ERROR + "(parsing error)");
1700 }
1701 } else {
1702 Element error = result.findChild("error");
1703 if (error == null) {
1704 Log.d(Config.LOGTAG, ERROR + "(server error)");
1705 } else {
1706 Log.d(Config.LOGTAG, ERROR + error.toString());
1707 }
1708 }
1709 if (callback != null) {
1710 callback.error(0, null);
1711 }
1712
1713 }
1714 });
1715 }
1716
1717 public void checkForAvatar(Account account,
1718 final UiCallback<Avatar> callback) {
1719 IqPacket packet = this.mIqGenerator.retrieveAvatarMetaData(null);
1720 this.sendIqPacket(account, packet, new OnIqPacketReceived() {
1721
1722 @Override
1723 public void onIqPacketReceived(Account account, IqPacket packet) {
1724 if (packet.getType() == IqPacket.TYPE_RESULT) {
1725 Element pubsub = packet.findChild("pubsub",
1726 "http://jabber.org/protocol/pubsub");
1727 if (pubsub != null) {
1728 Element items = pubsub.findChild("items");
1729 if (items != null) {
1730 Avatar avatar = Avatar.parseMetadata(items);
1731 if (avatar != null) {
1732 avatar.owner = account.getJid().toBareJid();
1733 if (fileBackend.isAvatarCached(avatar)) {
1734 if (account.setAvatar(avatar.getFilename())) {
1735 databaseBackend.updateAccount(account);
1736 }
1737 getAvatarService().clear(account);
1738 callback.success(avatar);
1739 } else {
1740 fetchAvatar(account, avatar, callback);
1741 }
1742 return;
1743 }
1744 }
1745 }
1746 }
1747 callback.error(0, null);
1748 }
1749 });
1750 }
1751
1752 public void deleteContactOnServer(Contact contact) {
1753 contact.resetOption(Contact.Options.PREEMPTIVE_GRANT);
1754 contact.resetOption(Contact.Options.DIRTY_PUSH);
1755 contact.setOption(Contact.Options.DIRTY_DELETE);
1756 Account account = contact.getAccount();
1757 if (account.getStatus() == Account.State.ONLINE) {
1758 IqPacket iq = new IqPacket(IqPacket.TYPE_SET);
1759 Element item = iq.query("jabber:iq:roster").addChild("item");
1760 item.setAttribute("jid", contact.getJid().toString());
1761 item.setAttribute("subscription", "remove");
1762 account.getXmppConnection().sendIqPacket(iq, null);
1763 }
1764 }
1765
1766 public void updateConversation(Conversation conversation) {
1767 this.databaseBackend.updateConversation(conversation);
1768 }
1769
1770 public void reconnectAccount(final Account account, final boolean force) {
1771 new Thread(new Runnable() {
1772
1773 @Override
1774 public void run() {
1775 if (account.getXmppConnection() != null) {
1776 disconnect(account, force);
1777 }
1778 if (!account.isOptionSet(Account.OPTION_DISABLED)) {
1779 if (account.getXmppConnection() == null) {
1780 account.setXmppConnection(createConnection(account));
1781 }
1782 Thread thread = new Thread(account.getXmppConnection());
1783 thread.start();
1784 scheduleWakeupCall((int) (Config.CONNECT_TIMEOUT * 1.2),
1785 false);
1786 } else {
1787 account.getRoster().clearPresences();
1788 account.setXmppConnection(null);
1789 }
1790 }
1791 }).start();
1792 }
1793
1794 public void invite(Conversation conversation, Jid contact) {
1795 MessagePacket packet = mMessageGenerator.invite(conversation, contact);
1796 sendMessagePacket(conversation.getAccount(), packet);
1797 }
1798
1799 public void resetSendingToWaiting(Account account) {
1800 for (Conversation conversation : getConversations()) {
1801 if (conversation.getAccount() == account) {
1802 for (Message message : conversation.getMessages()) {
1803 if (message.getType() != Message.TYPE_IMAGE
1804 && message.getStatus() == Message.STATUS_UNSEND) {
1805 markMessage(message, Message.STATUS_WAITING);
1806 }
1807 }
1808 }
1809 }
1810 }
1811
1812 public boolean markMessage(final Account account, final Jid recipient, final String uuid,
1813 final int status) {
1814 if (uuid == null) {
1815 return false;
1816 } else {
1817 for (Conversation conversation : getConversations()) {
1818 if (conversation.getContactJid().equals(recipient)
1819 && conversation.getAccount().equals(account)) {
1820 return markMessage(conversation, uuid, status);
1821 }
1822 }
1823 return false;
1824 }
1825 }
1826
1827 public boolean markMessage(Conversation conversation, String uuid,
1828 int status) {
1829 if (uuid == null) {
1830 return false;
1831 } else {
1832 for (Message message : conversation.getMessages()) {
1833 if (uuid.equals(message.getUuid())
1834 || (message.getStatus() >= Message.STATUS_SEND && uuid
1835 .equals(message.getRemoteMsgId()))) {
1836 markMessage(message, status);
1837 return true;
1838 }
1839 }
1840 return false;
1841 }
1842 }
1843
1844 public void markMessage(Message message, int status) {
1845 if (status == Message.STATUS_SEND_FAILED
1846 && (message.getStatus() == Message.STATUS_SEND_RECEIVED || message
1847 .getStatus() == Message.STATUS_SEND_DISPLAYED)) {
1848 return;
1849 }
1850 message.setStatus(status);
1851 databaseBackend.updateMessage(message);
1852 updateConversationUi();
1853 }
1854
1855 public SharedPreferences getPreferences() {
1856 return PreferenceManager
1857 .getDefaultSharedPreferences(getApplicationContext());
1858 }
1859
1860 public boolean forceEncryption() {
1861 return getPreferences().getBoolean("force_encryption", false);
1862 }
1863
1864 public boolean confirmMessages() {
1865 return getPreferences().getBoolean("confirm_messages", true);
1866 }
1867
1868 public boolean saveEncryptedMessages() {
1869 return !getPreferences().getBoolean("dont_save_encrypted", false);
1870 }
1871
1872 public boolean indicateReceived() {
1873 return getPreferences().getBoolean("indicate_received", false);
1874 }
1875
1876 public void updateConversationUi() {
1877 if (mOnConversationUpdate != null) {
1878 mOnConversationUpdate.onConversationUpdate();
1879 }
1880 }
1881
1882 public void updateAccountUi() {
1883 if (mOnAccountUpdate != null) {
1884 mOnAccountUpdate.onAccountUpdate();
1885 }
1886 }
1887
1888 public void updateRosterUi() {
1889 if (mOnRosterUpdate != null) {
1890 mOnRosterUpdate.onRosterUpdate();
1891 }
1892 }
1893
1894 public void updateMucRosterUi() {
1895 if (mOnMucRosterUpdate != null) {
1896 mOnMucRosterUpdate.onMucRosterUpdate();
1897 }
1898 }
1899
1900 public Account findAccountByJid(final Jid accountJid) {
1901 for (Account account : this.accounts) {
1902 if (account.getJid().toBareJid().equals(accountJid.toBareJid())) {
1903 return account;
1904 }
1905 }
1906 return null;
1907 }
1908
1909 public Conversation findConversationByUuid(String uuid) {
1910 for (Conversation conversation : getConversations()) {
1911 if (conversation.getUuid().equals(uuid)) {
1912 return conversation;
1913 }
1914 }
1915 return null;
1916 }
1917
1918 public void markRead(Conversation conversation, boolean calledByUi) {
1919 mNotificationService.clear(conversation);
1920 final Message markable = conversation.getLatestMarkableMessage();
1921 conversation.markRead();
1922 if (confirmMessages() && markable != null && markable.getRemoteMsgId() != null && calledByUi) {
1923 Log.d(Config.LOGTAG, conversation.getAccount().getJid().toBareJid()+ ": sending read marker to " + markable.getCounterpart().toString());
1924 Account account = conversation.getAccount();
1925 final Jid to = markable.getCounterpart();
1926 MessagePacket packet = mMessageGenerator.confirm(account, to, markable.getRemoteMsgId());
1927 this.sendMessagePacket(conversation.getAccount(),packet);
1928 }
1929 if (!calledByUi) {
1930 updateConversationUi();
1931 }
1932 }
1933
1934 public void failWaitingOtrMessages(Conversation conversation) {
1935 for (Message message : conversation.getMessages()) {
1936 if (message.getEncryption() == Message.ENCRYPTION_OTR
1937 && message.getStatus() == Message.STATUS_WAITING) {
1938 markMessage(message, Message.STATUS_SEND_FAILED);
1939 }
1940 }
1941 }
1942
1943 public SecureRandom getRNG() {
1944 return this.mRandom;
1945 }
1946
1947 public MemorizingTrustManager getMemorizingTrustManager() {
1948 return this.mMemorizingTrustManager;
1949 }
1950
1951 public PowerManager getPowerManager() {
1952 return this.pm;
1953 }
1954
1955 public LruCache<String, Bitmap> getBitmapCache() {
1956 return this.mBitmapCache;
1957 }
1958
1959 public void syncRosterToDisk(final Account account) {
1960 new Thread(new Runnable() {
1961
1962 @Override
1963 public void run() {
1964 databaseBackend.writeRoster(account.getRoster());
1965 }
1966 }).start();
1967
1968 }
1969
1970 public List<String> getKnownHosts() {
1971 List<String> hosts = new ArrayList<>();
1972 for (Account account : getAccounts()) {
1973 if (!hosts.contains(account.getServer().toString())) {
1974 hosts.add(account.getServer().toString());
1975 }
1976 for (Contact contact : account.getRoster().getContacts()) {
1977 if (contact.showInRoster()) {
1978 final String server = contact.getServer().toString();
1979 if (server != null && !hosts.contains(server)) {
1980 hosts.add(server);
1981 }
1982 }
1983 }
1984 }
1985 return hosts;
1986 }
1987
1988 public List<String> getKnownConferenceHosts() {
1989 ArrayList<String> mucServers = new ArrayList<>();
1990 for (Account account : accounts) {
1991 if (account.getXmppConnection() != null) {
1992 String server = account.getXmppConnection().getMucServer();
1993 if (server != null && !mucServers.contains(server)) {
1994 mucServers.add(server);
1995 }
1996 }
1997 }
1998 return mucServers;
1999 }
2000
2001 public void sendMessagePacket(Account account, MessagePacket packet) {
2002 XmppConnection connection = account.getXmppConnection();
2003 if (connection != null) {
2004 connection.sendMessagePacket(packet);
2005 }
2006 }
2007
2008 public void sendPresencePacket(Account account, PresencePacket packet) {
2009 XmppConnection connection = account.getXmppConnection();
2010 if (connection != null) {
2011 connection.sendPresencePacket(packet);
2012 }
2013 }
2014
2015 public void sendIqPacket(Account account, IqPacket packet,
2016 OnIqPacketReceived callback) {
2017 XmppConnection connection = account.getXmppConnection();
2018 if (connection != null) {
2019 connection.sendIqPacket(packet, callback);
2020 }
2021 }
2022
2023 public MessageGenerator getMessageGenerator() {
2024 return this.mMessageGenerator;
2025 }
2026
2027 public PresenceGenerator getPresenceGenerator() {
2028 return this.mPresenceGenerator;
2029 }
2030
2031 public IqGenerator getIqGenerator() {
2032 return this.mIqGenerator;
2033 }
2034
2035 public JingleConnectionManager getJingleConnectionManager() {
2036 return this.mJingleConnectionManager;
2037 }
2038
2039 public MessageArchiveService getMessageArchiveService() {
2040 return this.mMessageArchiveService;
2041 }
2042
2043 public List<Contact> findContacts(Jid jid) {
2044 ArrayList<Contact> contacts = new ArrayList<>();
2045 for (Account account : getAccounts()) {
2046 if (!account.isOptionSet(Account.OPTION_DISABLED)) {
2047 Contact contact = account.getRoster().getContactFromRoster(jid);
2048 if (contact != null) {
2049 contacts.add(contact);
2050 }
2051 }
2052 }
2053 return contacts;
2054 }
2055
2056 public NotificationService getNotificationService() {
2057 return this.mNotificationService;
2058 }
2059
2060 public HttpConnectionManager getHttpConnectionManager() {
2061 return this.mHttpConnectionManager;
2062 }
2063
2064 public void resendFailedMessages(Message message) {
2065 List<Message> messages = new ArrayList<>();
2066 Message current = message;
2067 while (current.getStatus() == Message.STATUS_SEND_FAILED) {
2068 messages.add(current);
2069 if (current.mergeable(current.next())) {
2070 current = current.next();
2071 } else {
2072 break;
2073 }
2074 }
2075 for (Message msg : messages) {
2076 markMessage(msg, Message.STATUS_WAITING);
2077 this.resendMessage(msg);
2078 }
2079 }
2080
2081 public interface OnConversationUpdate {
2082 public void onConversationUpdate();
2083 }
2084
2085 public interface OnAccountUpdate {
2086 public void onAccountUpdate();
2087 }
2088
2089 public interface OnRosterUpdate {
2090 public void onRosterUpdate();
2091 }
2092
2093 public interface OnMucRosterUpdate {
2094 public void onMucRosterUpdate();
2095 }
2096
2097 private interface OnConferenceOptionsPushed {
2098 public void onPushSucceeded();
2099 public void onPushFailed();
2100 }
2101
2102 public class XmppConnectionBinder extends Binder {
2103 public XmppConnectionService getService() {
2104 return XmppConnectionService.this;
2105 }
2106 }
2107}