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