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