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.setNextEncryption(-1);
1009 conversation.addAll(0, databaseBackend.getMessages(conversation, Config.PAGE_SIZE));
1010 this.databaseBackend.updateConversation(conversation);
1011 } else {
1012 String conversationName;
1013 Contact contact = account.getRoster().getContact(jid);
1014 if (contact != null) {
1015 conversationName = contact.getDisplayName();
1016 } else {
1017 conversationName = jid.getLocalpart();
1018 }
1019 if (muc) {
1020 conversation = new Conversation(conversationName, account, jid,
1021 Conversation.MODE_MULTI);
1022 } else {
1023 conversation = new Conversation(conversationName, account, jid,
1024 Conversation.MODE_SINGLE);
1025 }
1026 this.databaseBackend.createConversation(conversation);
1027 }
1028 if (account.getXmppConnection() != null && account.getXmppConnection().getFeatures().mam()) {
1029 if (query == null) {
1030 this.mMessageArchiveService.query(conversation);
1031 } else {
1032 if (query.getConversation() == null) {
1033 this.mMessageArchiveService.query(conversation, query.getStart());
1034 }
1035 }
1036 }
1037 this.conversations.add(conversation);
1038 updateConversationUi();
1039 return conversation;
1040 }
1041 }
1042
1043 public void archiveConversation(Conversation conversation) {
1044 conversation.setStatus(Conversation.STATUS_ARCHIVED);
1045 conversation.setNextEncryption(-1);
1046 synchronized (this.conversations) {
1047 if (conversation.getMode() == Conversation.MODE_MULTI) {
1048 if (conversation.getAccount().getStatus() == Account.State.ONLINE) {
1049 Bookmark bookmark = conversation.getBookmark();
1050 if (bookmark != null && bookmark.autojoin()) {
1051 bookmark.setAutojoin(false);
1052 pushBookmarks(bookmark.getAccount());
1053 }
1054 }
1055 leaveMuc(conversation);
1056 } else {
1057 conversation.endOtrIfNeeded();
1058 }
1059 this.databaseBackend.updateConversation(conversation);
1060 this.conversations.remove(conversation);
1061 updateConversationUi();
1062 }
1063 }
1064
1065 public void createAccount(final Account account) {
1066 account.initOtrEngine(this);
1067 databaseBackend.createAccount(account);
1068 this.accounts.add(account);
1069 this.reconnectAccount(account, false);
1070 updateAccountUi();
1071 }
1072
1073 public void updateAccount(final Account account) {
1074 this.statusListener.onStatusChanged(account);
1075 databaseBackend.updateAccount(account);
1076 reconnectAccount(account, false);
1077 updateAccountUi();
1078 getNotificationService().updateErrorNotification();
1079 }
1080
1081 public void updateAccountPasswordOnServer(final Account account, final String newPassword, final OnAccountPasswordChanged callback) {
1082 final IqPacket iq = getIqGenerator().generateSetPassword(account, newPassword);
1083 sendIqPacket(account, iq, new OnIqPacketReceived() {
1084 @Override
1085 public void onIqPacketReceived(final Account account, final IqPacket packet) {
1086 if (packet.getType() == IqPacket.TYPE.RESULT) {
1087 account.setPassword(newPassword);
1088 databaseBackend.updateAccount(account);
1089 callback.onPasswordChangeSucceeded();
1090 } else {
1091 callback.onPasswordChangeFailed();
1092 }
1093 }
1094 });
1095 }
1096
1097 public interface OnAccountPasswordChanged {
1098 public void onPasswordChangeSucceeded();
1099 public void onPasswordChangeFailed();
1100 }
1101
1102 public void deleteAccount(final Account account) {
1103 synchronized (this.conversations) {
1104 for (final Conversation conversation : conversations) {
1105 if (conversation.getAccount() == account) {
1106 if (conversation.getMode() == Conversation.MODE_MULTI) {
1107 leaveMuc(conversation);
1108 } else if (conversation.getMode() == Conversation.MODE_SINGLE) {
1109 conversation.endOtrIfNeeded();
1110 }
1111 conversations.remove(conversation);
1112 }
1113 }
1114 if (account.getXmppConnection() != null) {
1115 this.disconnect(account, true);
1116 }
1117 databaseBackend.deleteAccount(account);
1118 this.accounts.remove(account);
1119 updateAccountUi();
1120 getNotificationService().updateErrorNotification();
1121 }
1122 }
1123
1124 public void setOnConversationListChangedListener(OnConversationUpdate listener) {
1125 synchronized (this) {
1126 if (checkListeners()) {
1127 switchToForeground();
1128 }
1129 this.mOnConversationUpdate = listener;
1130 this.mNotificationService.setIsInForeground(true);
1131 if (this.convChangedListenerCount < 2) {
1132 this.convChangedListenerCount++;
1133 }
1134 }
1135 }
1136
1137 public void removeOnConversationListChangedListener() {
1138 synchronized (this) {
1139 this.convChangedListenerCount--;
1140 if (this.convChangedListenerCount <= 0) {
1141 this.convChangedListenerCount = 0;
1142 this.mOnConversationUpdate = null;
1143 this.mNotificationService.setIsInForeground(false);
1144 if (checkListeners()) {
1145 switchToBackground();
1146 }
1147 }
1148 }
1149 }
1150
1151 public void setOnAccountListChangedListener(OnAccountUpdate listener) {
1152 synchronized (this) {
1153 if (checkListeners()) {
1154 switchToForeground();
1155 }
1156 this.mOnAccountUpdate = listener;
1157 if (this.accountChangedListenerCount < 2) {
1158 this.accountChangedListenerCount++;
1159 }
1160 }
1161 }
1162
1163 public void removeOnAccountListChangedListener() {
1164 synchronized (this) {
1165 this.accountChangedListenerCount--;
1166 if (this.accountChangedListenerCount <= 0) {
1167 this.mOnAccountUpdate = null;
1168 this.accountChangedListenerCount = 0;
1169 if (checkListeners()) {
1170 switchToBackground();
1171 }
1172 }
1173 }
1174 }
1175
1176 public void setOnRosterUpdateListener(final OnRosterUpdate listener) {
1177 synchronized (this) {
1178 if (checkListeners()) {
1179 switchToForeground();
1180 }
1181 this.mOnRosterUpdate = listener;
1182 if (this.rosterChangedListenerCount < 2) {
1183 this.rosterChangedListenerCount++;
1184 }
1185 }
1186 }
1187
1188 public void removeOnRosterUpdateListener() {
1189 synchronized (this) {
1190 this.rosterChangedListenerCount--;
1191 if (this.rosterChangedListenerCount <= 0) {
1192 this.rosterChangedListenerCount = 0;
1193 this.mOnRosterUpdate = null;
1194 if (checkListeners()) {
1195 switchToBackground();
1196 }
1197 }
1198 }
1199 }
1200
1201 public void setOnUpdateBlocklistListener(final OnUpdateBlocklist listener) {
1202 synchronized (this) {
1203 if (checkListeners()) {
1204 switchToForeground();
1205 }
1206 this.mOnUpdateBlocklist = listener;
1207 if (this.updateBlocklistListenerCount < 2) {
1208 this.updateBlocklistListenerCount++;
1209 }
1210 }
1211 }
1212
1213 public void removeOnUpdateBlocklistListener() {
1214 synchronized (this) {
1215 this.updateBlocklistListenerCount--;
1216 if (this.updateBlocklistListenerCount <= 0) {
1217 this.updateBlocklistListenerCount = 0;
1218 this.mOnUpdateBlocklist = null;
1219 if (checkListeners()) {
1220 switchToBackground();
1221 }
1222 }
1223 }
1224 }
1225
1226 public void setOnMucRosterUpdateListener(OnMucRosterUpdate listener) {
1227 synchronized (this) {
1228 if (checkListeners()) {
1229 switchToForeground();
1230 }
1231 this.mOnMucRosterUpdate = listener;
1232 if (this.mucRosterChangedListenerCount < 2) {
1233 this.mucRosterChangedListenerCount++;
1234 }
1235 }
1236 }
1237
1238 public void removeOnMucRosterUpdateListener() {
1239 synchronized (this) {
1240 this.mucRosterChangedListenerCount--;
1241 if (this.mucRosterChangedListenerCount <= 0) {
1242 this.mucRosterChangedListenerCount = 0;
1243 this.mOnMucRosterUpdate = null;
1244 if (checkListeners()) {
1245 switchToBackground();
1246 }
1247 }
1248 }
1249 }
1250
1251 private boolean checkListeners() {
1252 return (this.mOnAccountUpdate == null
1253 && this.mOnConversationUpdate == null
1254 && this.mOnRosterUpdate == null
1255 && this.mOnUpdateBlocklist == null);
1256 }
1257
1258 private void switchToForeground() {
1259 for (Account account : getAccounts()) {
1260 if (account.getStatus() == Account.State.ONLINE) {
1261 XmppConnection connection = account.getXmppConnection();
1262 if (connection != null && connection.getFeatures().csi()) {
1263 connection.sendActive();
1264 }
1265 }
1266 }
1267 Log.d(Config.LOGTAG, "app switched into foreground");
1268 }
1269
1270 private void switchToBackground() {
1271 for (Account account : getAccounts()) {
1272 if (account.getStatus() == Account.State.ONLINE) {
1273 XmppConnection connection = account.getXmppConnection();
1274 if (connection != null && connection.getFeatures().csi()) {
1275 connection.sendInactive();
1276 }
1277 }
1278 }
1279 this.mNotificationService.setIsInForeground(false);
1280 Log.d(Config.LOGTAG, "app switched into background");
1281 }
1282
1283 private void connectMultiModeConversations(Account account) {
1284 List<Conversation> conversations = getConversations();
1285 for (Conversation conversation : conversations) {
1286 if ((conversation.getMode() == Conversation.MODE_MULTI)
1287 && (conversation.getAccount() == account)) {
1288 conversation.resetMucOptions();
1289 joinMuc(conversation);
1290 }
1291 }
1292 }
1293
1294 public void joinMuc(Conversation conversation) {
1295 Account account = conversation.getAccount();
1296 account.pendingConferenceJoins.remove(conversation);
1297 account.pendingConferenceLeaves.remove(conversation);
1298 if (account.getStatus() == Account.State.ONLINE) {
1299 final String nick = conversation.getMucOptions().getProposedNick();
1300 final Jid joinJid = conversation.getMucOptions().createJoinJid(nick);
1301 if (joinJid == null) {
1302 return; //safety net
1303 }
1304 Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": joining conversation " + joinJid.toString());
1305 PresencePacket packet = new PresencePacket();
1306 packet.setFrom(conversation.getAccount().getJid());
1307 packet.setTo(joinJid);
1308 Element x = packet.addChild("x","http://jabber.org/protocol/muc");
1309 if (conversation.getMucOptions().getPassword() != null) {
1310 x.addChild("password").setContent(conversation.getMucOptions().getPassword());
1311 }
1312 x.addChild("history").setAttribute("since",PresenceGenerator.getTimestamp(conversation.getLastMessageTransmitted()));
1313 String sig = account.getPgpSignature();
1314 if (sig != null) {
1315 packet.addChild("status").setContent("online");
1316 packet.addChild("x", "jabber:x:signed").setContent(sig);
1317 }
1318 sendPresencePacket(account, packet);
1319 fetchConferenceConfiguration(conversation);
1320 if (!joinJid.equals(conversation.getJid())) {
1321 conversation.setContactJid(joinJid);
1322 databaseBackend.updateConversation(conversation);
1323 }
1324 } else {
1325 account.pendingConferenceJoins.add(conversation);
1326 }
1327 }
1328
1329 public void providePasswordForMuc(Conversation conversation, String password) {
1330 if (conversation.getMode() == Conversation.MODE_MULTI) {
1331 conversation.getMucOptions().setPassword(password);
1332 if (conversation.getBookmark() != null) {
1333 conversation.getBookmark().setAutojoin(true);
1334 pushBookmarks(conversation.getAccount());
1335 }
1336 databaseBackend.updateConversation(conversation);
1337 joinMuc(conversation);
1338 }
1339 }
1340
1341 public void renameInMuc(final Conversation conversation, final String nick, final UiCallback<Conversation> callback) {
1342 final MucOptions options = conversation.getMucOptions();
1343 final Jid joinJid = options.createJoinJid(nick);
1344 if (options.online()) {
1345 Account account = conversation.getAccount();
1346 options.setOnRenameListener(new OnRenameListener() {
1347
1348 @Override
1349 public void onSuccess() {
1350 conversation.setContactJid(joinJid);
1351 databaseBackend.updateConversation(conversation);
1352 Bookmark bookmark = conversation.getBookmark();
1353 if (bookmark != null) {
1354 bookmark.setNick(nick);
1355 pushBookmarks(bookmark.getAccount());
1356 }
1357 callback.success(conversation);
1358 }
1359
1360 @Override
1361 public void onFailure() {
1362 callback.error(R.string.nick_in_use, conversation);
1363 }
1364 });
1365
1366 PresencePacket packet = new PresencePacket();
1367 packet.setTo(joinJid);
1368 packet.setFrom(conversation.getAccount().getJid());
1369
1370 String sig = account.getPgpSignature();
1371 if (sig != null) {
1372 packet.addChild("status").setContent("online");
1373 packet.addChild("x", "jabber:x:signed").setContent(sig);
1374 }
1375 sendPresencePacket(account, packet);
1376 } else {
1377 conversation.setContactJid(joinJid);
1378 databaseBackend.updateConversation(conversation);
1379 if (conversation.getAccount().getStatus() == Account.State.ONLINE) {
1380 Bookmark bookmark = conversation.getBookmark();
1381 if (bookmark != null) {
1382 bookmark.setNick(nick);
1383 pushBookmarks(bookmark.getAccount());
1384 }
1385 joinMuc(conversation);
1386 }
1387 }
1388 }
1389
1390 public void leaveMuc(Conversation conversation) {
1391 Account account = conversation.getAccount();
1392 account.pendingConferenceJoins.remove(conversation);
1393 account.pendingConferenceLeaves.remove(conversation);
1394 if (account.getStatus() == Account.State.ONLINE) {
1395 PresencePacket packet = new PresencePacket();
1396 packet.setTo(conversation.getJid());
1397 packet.setFrom(conversation.getAccount().getJid());
1398 packet.setAttribute("type", "unavailable");
1399 sendPresencePacket(conversation.getAccount(), packet);
1400 conversation.getMucOptions().setOffline();
1401 conversation.deregisterWithBookmark();
1402 Log.d(Config.LOGTAG, conversation.getAccount().getJid().toBareJid()
1403 + ": leaving muc " + conversation.getJid());
1404 } else {
1405 account.pendingConferenceLeaves.add(conversation);
1406 }
1407 }
1408
1409 private String findConferenceServer(final Account account) {
1410 String server;
1411 if (account.getXmppConnection() != null) {
1412 server = account.getXmppConnection().getMucServer();
1413 if (server != null) {
1414 return server;
1415 }
1416 }
1417 for(Account other : getAccounts()) {
1418 if (other != account && other.getXmppConnection() != null) {
1419 server = other.getXmppConnection().getMucServer();
1420 if (server != null) {
1421 return server;
1422 }
1423 }
1424 }
1425 return null;
1426 }
1427
1428 public void createAdhocConference(final Account account, final Iterable<Jid> jids, final UiCallback<Conversation> callback) {
1429 Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": creating adhoc conference with " + jids.toString());
1430 if (account.getStatus() == Account.State.ONLINE) {
1431 try {
1432 String server = findConferenceServer(account);
1433 if (server == null) {
1434 if (callback != null) {
1435 callback.error(R.string.no_conference_server_found,null);
1436 }
1437 return;
1438 }
1439 String name = new BigInteger(75,getRNG()).toString(32);
1440 Jid jid = Jid.fromParts(name,server,null);
1441 final Conversation conversation = findOrCreateConversation(account, jid, true);
1442 joinMuc(conversation);
1443 Bundle options = new Bundle();
1444 options.putString("muc#roomconfig_persistentroom", "1");
1445 options.putString("muc#roomconfig_membersonly", "1");
1446 options.putString("muc#roomconfig_publicroom", "0");
1447 options.putString("muc#roomconfig_whois", "anyone");
1448 pushConferenceConfiguration(conversation, options, new OnConferenceOptionsPushed() {
1449 @Override
1450 public void onPushSucceeded() {
1451 for(Jid invite : jids) {
1452 invite(conversation,invite);
1453 }
1454 if (callback != null) {
1455 callback.success(conversation);
1456 }
1457 }
1458
1459 @Override
1460 public void onPushFailed() {
1461 if (callback != null) {
1462 callback.error(R.string.conference_creation_failed, conversation);
1463 }
1464 }
1465 });
1466
1467 } catch (InvalidJidException e) {
1468 if (callback != null) {
1469 callback.error(R.string.conference_creation_failed, null);
1470 }
1471 }
1472 } else {
1473 if (callback != null) {
1474 callback.error(R.string.not_connected_try_again,null);
1475 }
1476 }
1477 }
1478
1479 public void fetchConferenceConfiguration(final Conversation conversation) {
1480 IqPacket request = new IqPacket(IqPacket.TYPE.GET);
1481 request.setTo(conversation.getJid().toBareJid());
1482 request.query("http://jabber.org/protocol/disco#info");
1483 sendIqPacket(conversation.getAccount(), request, new OnIqPacketReceived() {
1484 @Override
1485 public void onIqPacketReceived(Account account, IqPacket packet) {
1486 if (packet.getType() != IqPacket.TYPE.ERROR) {
1487 ArrayList<String> features = new ArrayList<String>();
1488 for (Element child : packet.query().getChildren()) {
1489 if (child != null && child.getName().equals("feature")) {
1490 String var = child.getAttribute("var");
1491 if (var != null) {
1492 features.add(var);
1493 }
1494 }
1495 }
1496 conversation.getMucOptions().updateFeatures(features);
1497 }
1498 }
1499 });
1500 }
1501
1502 public void pushConferenceConfiguration(final Conversation conversation,final Bundle options, final OnConferenceOptionsPushed callback) {
1503 IqPacket request = new IqPacket(IqPacket.TYPE.GET);
1504 request.setTo(conversation.getJid().toBareJid());
1505 request.query("http://jabber.org/protocol/muc#owner");
1506 sendIqPacket(conversation.getAccount(),request,new OnIqPacketReceived() {
1507 @Override
1508 public void onIqPacketReceived(Account account, IqPacket packet) {
1509 if (packet.getType() != IqPacket.TYPE.ERROR) {
1510 Data data = Data.parse(packet.query().findChild("x", "jabber:x:data"));
1511 for (Field field : data.getFields()) {
1512 if (options.containsKey(field.getName())) {
1513 field.setValue(options.getString(field.getName()));
1514 }
1515 }
1516 data.submit();
1517 IqPacket set = new IqPacket(IqPacket.TYPE.SET);
1518 set.setTo(conversation.getJid().toBareJid());
1519 set.query("http://jabber.org/protocol/muc#owner").addChild(data);
1520 sendIqPacket(account, set, new OnIqPacketReceived() {
1521 @Override
1522 public void onIqPacketReceived(Account account, IqPacket packet) {
1523 if (packet.getType() == IqPacket.TYPE.RESULT) {
1524 if (callback != null) {
1525 callback.onPushSucceeded();
1526 }
1527 } else {
1528 if (callback != null) {
1529 callback.onPushFailed();
1530 }
1531 }
1532 }
1533 });
1534 } else {
1535 if (callback != null) {
1536 callback.onPushFailed();
1537 }
1538 }
1539 }
1540 });
1541 }
1542
1543 public void changeAffiliationInConference(final Conversation conference, Jid user, MucOptions.Affiliation affiliation, final OnAffiliationChanged callback) {
1544 final Jid jid = user.toBareJid();
1545 IqPacket request = this.mIqGenerator.changeAffiliation(conference, jid, affiliation.toString());
1546 Log.d(Config.LOGTAG,request.toString());
1547 sendIqPacket(conference.getAccount(), request, new OnIqPacketReceived() {
1548 @Override
1549 public void onIqPacketReceived(Account account, IqPacket packet) {
1550 Log.d(Config.LOGTAG, packet.toString());
1551 if (packet.getType() == IqPacket.TYPE.RESULT) {
1552 callback.onAffiliationChangedSuccessful(jid);
1553 } else {
1554 callback.onAffiliationChangeFailed(jid, R.string.could_not_change_affiliation);
1555 }
1556 }
1557 });
1558 }
1559
1560 public interface OnAffiliationChanged {
1561 public void onAffiliationChangedSuccessful(Jid jid);
1562 public void onAffiliationChangeFailed(Jid jid, int resId);
1563 }
1564
1565 public void disconnect(Account account, boolean force) {
1566 if ((account.getStatus() == Account.State.ONLINE)
1567 || (account.getStatus() == Account.State.DISABLED)) {
1568 if (!force) {
1569 List<Conversation> conversations = getConversations();
1570 for (Conversation conversation : conversations) {
1571 if (conversation.getAccount() == account) {
1572 if (conversation.getMode() == Conversation.MODE_MULTI) {
1573 leaveMuc(conversation);
1574 } else {
1575 if (conversation.endOtrIfNeeded()) {
1576 Log.d(Config.LOGTAG, account.getJid().toBareJid()
1577 + ": ended otr session with "
1578 + conversation.getJid());
1579 }
1580 }
1581 }
1582 }
1583 }
1584 account.getXmppConnection().disconnect(force);
1585 }
1586 }
1587
1588 @Override
1589 public IBinder onBind(Intent intent) {
1590 return mBinder;
1591 }
1592
1593 public void updateMessage(Message message) {
1594 databaseBackend.updateMessage(message);
1595 updateConversationUi();
1596 }
1597
1598 protected void syncDirtyContacts(Account account) {
1599 for (Contact contact : account.getRoster().getContacts()) {
1600 if (contact.getOption(Contact.Options.DIRTY_PUSH)) {
1601 pushContactToServer(contact);
1602 }
1603 if (contact.getOption(Contact.Options.DIRTY_DELETE)) {
1604 deleteContactOnServer(contact);
1605 }
1606 }
1607 }
1608
1609 public void createContact(Contact contact) {
1610 SharedPreferences sharedPref = getPreferences();
1611 boolean autoGrant = sharedPref.getBoolean("grant_new_contacts", true);
1612 if (autoGrant) {
1613 contact.setOption(Contact.Options.PREEMPTIVE_GRANT);
1614 contact.setOption(Contact.Options.ASKING);
1615 }
1616 pushContactToServer(contact);
1617 }
1618
1619 public void onOtrSessionEstablished(Conversation conversation) {
1620 final Account account = conversation.getAccount();
1621 final Session otrSession = conversation.getOtrSession();
1622 Log.d(Config.LOGTAG,
1623 account.getJid().toBareJid() + " otr session established with "
1624 + conversation.getJid() + "/"
1625 + otrSession.getSessionID().getUserID());
1626 conversation.findUnsentMessagesWithOtrEncryption(new Conversation.OnMessageFound() {
1627
1628 @Override
1629 public void onMessageFound(Message message) {
1630 SessionID id = otrSession.getSessionID();
1631 try {
1632 message.setCounterpart(Jid.fromString(id.getAccountID() + "/" + id.getUserID()));
1633 } catch (InvalidJidException e) {
1634 return;
1635 }
1636 if (message.getType() == Message.TYPE_TEXT) {
1637 MessagePacket outPacket = mMessageGenerator.generateOtrChat(message, true);
1638 if (outPacket != null) {
1639 message.setStatus(Message.STATUS_SEND);
1640 databaseBackend.updateMessage(message);
1641 sendMessagePacket(account, outPacket);
1642 }
1643 } else if (message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE) {
1644 mJingleConnectionManager.createNewConnection(message);
1645 }
1646 updateConversationUi();
1647 }
1648 });
1649 }
1650
1651 public boolean renewSymmetricKey(Conversation conversation) {
1652 Account account = conversation.getAccount();
1653 byte[] symmetricKey = new byte[32];
1654 this.mRandom.nextBytes(symmetricKey);
1655 Session otrSession = conversation.getOtrSession();
1656 if (otrSession != null) {
1657 MessagePacket packet = new MessagePacket();
1658 packet.setType(MessagePacket.TYPE_CHAT);
1659 packet.setFrom(account.getJid());
1660 packet.addChild("private", "urn:xmpp:carbons:2");
1661 packet.addChild("no-copy", "urn:xmpp:hints");
1662 packet.setAttribute("to", otrSession.getSessionID().getAccountID() + "/"
1663 + otrSession.getSessionID().getUserID());
1664 try {
1665 packet.setBody(otrSession
1666 .transformSending(CryptoHelper.FILETRANSFER
1667 + CryptoHelper.bytesToHex(symmetricKey)));
1668 sendMessagePacket(account, packet);
1669 conversation.setSymmetricKey(symmetricKey);
1670 return true;
1671 } catch (OtrException e) {
1672 return false;
1673 }
1674 }
1675 return false;
1676 }
1677
1678 public void pushContactToServer(final Contact contact) {
1679 contact.resetOption(Contact.Options.DIRTY_DELETE);
1680 contact.setOption(Contact.Options.DIRTY_PUSH);
1681 final Account account = contact.getAccount();
1682 if (account.getStatus() == Account.State.ONLINE) {
1683 final boolean ask = contact.getOption(Contact.Options.ASKING);
1684 final boolean sendUpdates = contact
1685 .getOption(Contact.Options.PENDING_SUBSCRIPTION_REQUEST)
1686 && contact.getOption(Contact.Options.PREEMPTIVE_GRANT);
1687 final IqPacket iq = new IqPacket(IqPacket.TYPE.SET);
1688 iq.query(Xmlns.ROSTER).addChild(contact.asElement());
1689 account.getXmppConnection().sendIqPacket(iq, null);
1690 if (sendUpdates) {
1691 sendPresencePacket(account,
1692 mPresenceGenerator.sendPresenceUpdatesTo(contact));
1693 }
1694 if (ask) {
1695 sendPresencePacket(account,
1696 mPresenceGenerator.requestPresenceUpdatesFrom(contact));
1697 }
1698 }
1699 }
1700
1701 public void publishAvatar(final Account account,
1702 final Uri image,
1703 final UiCallback<Avatar> callback) {
1704 final Bitmap.CompressFormat format = Config.AVATAR_FORMAT;
1705 final int size = Config.AVATAR_SIZE;
1706 final Avatar avatar = getFileBackend()
1707 .getPepAvatar(image, size, format);
1708 if (avatar != null) {
1709 avatar.height = size;
1710 avatar.width = size;
1711 if (format.equals(Bitmap.CompressFormat.WEBP)) {
1712 avatar.type = "image/webp";
1713 } else if (format.equals(Bitmap.CompressFormat.JPEG)) {
1714 avatar.type = "image/jpeg";
1715 } else if (format.equals(Bitmap.CompressFormat.PNG)) {
1716 avatar.type = "image/png";
1717 }
1718 if (!getFileBackend().save(avatar)) {
1719 callback.error(R.string.error_saving_avatar, avatar);
1720 return;
1721 }
1722 final IqPacket packet = this.mIqGenerator.publishAvatar(avatar);
1723 this.sendIqPacket(account, packet, new OnIqPacketReceived() {
1724
1725 @Override
1726 public void onIqPacketReceived(Account account, IqPacket result) {
1727 if (result.getType() == IqPacket.TYPE.RESULT) {
1728 final IqPacket packet = XmppConnectionService.this.mIqGenerator
1729 .publishAvatarMetadata(avatar);
1730 sendIqPacket(account, packet, new OnIqPacketReceived() {
1731
1732 @Override
1733 public void onIqPacketReceived(Account account,
1734 IqPacket result) {
1735 if (result.getType() == IqPacket.TYPE.RESULT) {
1736 if (account.setAvatar(avatar.getFilename())) {
1737 databaseBackend.updateAccount(account);
1738 }
1739 callback.success(avatar);
1740 } else {
1741 callback.error(
1742 R.string.error_publish_avatar_server_reject,
1743 avatar);
1744 }
1745 }
1746 });
1747 } else {
1748 callback.error(
1749 R.string.error_publish_avatar_server_reject,
1750 avatar);
1751 }
1752 }
1753 });
1754 } else {
1755 callback.error(R.string.error_publish_avatar_converting, null);
1756 }
1757 }
1758
1759 public void fetchAvatar(Account account, Avatar avatar) {
1760 fetchAvatar(account, avatar, null);
1761 }
1762
1763 public void fetchAvatar(Account account, final Avatar avatar,
1764 final UiCallback<Avatar> callback) {
1765 IqPacket packet = this.mIqGenerator.retrieveAvatar(avatar);
1766 sendIqPacket(account, packet, new OnIqPacketReceived() {
1767
1768 @Override
1769 public void onIqPacketReceived(Account account, IqPacket result) {
1770 final String ERROR = account.getJid().toBareJid()
1771 + ": fetching avatar for " + avatar.owner + " failed ";
1772 if (result.getType() == IqPacket.TYPE.RESULT) {
1773 avatar.image = mIqParser.avatarData(result);
1774 if (avatar.image != null) {
1775 if (getFileBackend().save(avatar)) {
1776 if (account.getJid().toBareJid().equals(avatar.owner)) {
1777 if (account.setAvatar(avatar.getFilename())) {
1778 databaseBackend.updateAccount(account);
1779 }
1780 getAvatarService().clear(account);
1781 updateConversationUi();
1782 updateAccountUi();
1783 } else {
1784 Contact contact = account.getRoster()
1785 .getContact(avatar.owner);
1786 contact.setAvatar(avatar.getFilename());
1787 getAvatarService().clear(contact);
1788 updateConversationUi();
1789 updateRosterUi();
1790 }
1791 if (callback != null) {
1792 callback.success(avatar);
1793 }
1794 Log.d(Config.LOGTAG, account.getJid().toBareJid()
1795 + ": succesfully fetched avatar for "
1796 + avatar.owner);
1797 return;
1798 }
1799 } else {
1800
1801 Log.d(Config.LOGTAG, ERROR + "(parsing error)");
1802 }
1803 } else {
1804 Element error = result.findChild("error");
1805 if (error == null) {
1806 Log.d(Config.LOGTAG, ERROR + "(server error)");
1807 } else {
1808 Log.d(Config.LOGTAG, ERROR + error.toString());
1809 }
1810 }
1811 if (callback != null) {
1812 callback.error(0, null);
1813 }
1814
1815 }
1816 });
1817 }
1818
1819 public void checkForAvatar(Account account,
1820 final UiCallback<Avatar> callback) {
1821 IqPacket packet = this.mIqGenerator.retrieveAvatarMetaData(null);
1822 this.sendIqPacket(account, packet, new OnIqPacketReceived() {
1823
1824 @Override
1825 public void onIqPacketReceived(Account account, IqPacket packet) {
1826 if (packet.getType() == IqPacket.TYPE.RESULT) {
1827 Element pubsub = packet.findChild("pubsub",
1828 "http://jabber.org/protocol/pubsub");
1829 if (pubsub != null) {
1830 Element items = pubsub.findChild("items");
1831 if (items != null) {
1832 Avatar avatar = Avatar.parseMetadata(items);
1833 if (avatar != null) {
1834 avatar.owner = account.getJid().toBareJid();
1835 if (fileBackend.isAvatarCached(avatar)) {
1836 if (account.setAvatar(avatar.getFilename())) {
1837 databaseBackend.updateAccount(account);
1838 }
1839 getAvatarService().clear(account);
1840 callback.success(avatar);
1841 } else {
1842 fetchAvatar(account, avatar, callback);
1843 }
1844 return;
1845 }
1846 }
1847 }
1848 }
1849 callback.error(0, null);
1850 }
1851 });
1852 }
1853
1854 public void deleteContactOnServer(Contact contact) {
1855 contact.resetOption(Contact.Options.PREEMPTIVE_GRANT);
1856 contact.resetOption(Contact.Options.DIRTY_PUSH);
1857 contact.setOption(Contact.Options.DIRTY_DELETE);
1858 Account account = contact.getAccount();
1859 if (account.getStatus() == Account.State.ONLINE) {
1860 IqPacket iq = new IqPacket(IqPacket.TYPE.SET);
1861 Element item = iq.query(Xmlns.ROSTER).addChild("item");
1862 item.setAttribute("jid", contact.getJid().toString());
1863 item.setAttribute("subscription", "remove");
1864 account.getXmppConnection().sendIqPacket(iq, null);
1865 }
1866 }
1867
1868 public void updateConversation(Conversation conversation) {
1869 this.databaseBackend.updateConversation(conversation);
1870 }
1871
1872 public void reconnectAccount(final Account account, final boolean force) {
1873 new Thread(new Runnable() {
1874
1875 @Override
1876 public void run() {
1877 if (account.getXmppConnection() != null) {
1878 disconnect(account, force);
1879 }
1880 if (!account.isOptionSet(Account.OPTION_DISABLED)) {
1881 if (account.getXmppConnection() == null) {
1882 account.setXmppConnection(createConnection(account));
1883 }
1884 Thread thread = new Thread(account.getXmppConnection());
1885 thread.start();
1886 scheduleWakeUpCall(Config.CONNECT_TIMEOUT,account.getUuid().hashCode());
1887 } else {
1888 account.getRoster().clearPresences();
1889 account.setXmppConnection(null);
1890 }
1891 }
1892 }).start();
1893 }
1894
1895 public void invite(Conversation conversation, Jid contact) {
1896 MessagePacket packet = mMessageGenerator.invite(conversation, contact);
1897 sendMessagePacket(conversation.getAccount(), packet);
1898 }
1899
1900 public void resetSendingToWaiting(Account account) {
1901 for (Conversation conversation : getConversations()) {
1902 if (conversation.getAccount() == account) {
1903 conversation.findUnsentTextMessages(new Conversation.OnMessageFound() {
1904
1905 @Override
1906 public void onMessageFound(Message message) {
1907 markMessage(message, Message.STATUS_WAITING);
1908 }
1909 });
1910 }
1911 }
1912 }
1913
1914 public boolean markMessage(final Account account, final Jid recipient, final String uuid,
1915 final int status) {
1916 if (uuid == null) {
1917 return false;
1918 } else {
1919 for (Conversation conversation : getConversations()) {
1920 if (conversation.getJid().equals(recipient)
1921 && conversation.getAccount().equals(account)) {
1922 return markMessage(conversation, uuid, status);
1923 }
1924 }
1925 return false;
1926 }
1927 }
1928
1929 public boolean markMessage(Conversation conversation, String uuid,
1930 int status) {
1931 if (uuid == null) {
1932 return false;
1933 } else {
1934 Message message = conversation.findSentMessageWithUuid(uuid);
1935 if (message!=null) {
1936 markMessage(message,status);
1937 return true;
1938 } else {
1939 return false;
1940 }
1941 }
1942 }
1943
1944 public void markMessage(Message message, int status) {
1945 if (status == Message.STATUS_SEND_FAILED
1946 && (message.getStatus() == Message.STATUS_SEND_RECEIVED || message
1947 .getStatus() == Message.STATUS_SEND_DISPLAYED)) {
1948 return;
1949 }
1950 message.setStatus(status);
1951 databaseBackend.updateMessage(message);
1952 updateConversationUi();
1953 }
1954
1955 public SharedPreferences getPreferences() {
1956 return PreferenceManager
1957 .getDefaultSharedPreferences(getApplicationContext());
1958 }
1959
1960 public boolean forceEncryption() {
1961 return getPreferences().getBoolean("force_encryption", false);
1962 }
1963
1964 public boolean confirmMessages() {
1965 return getPreferences().getBoolean("confirm_messages", true);
1966 }
1967
1968 public boolean saveEncryptedMessages() {
1969 return !getPreferences().getBoolean("dont_save_encrypted", false);
1970 }
1971
1972 public boolean indicateReceived() {
1973 return getPreferences().getBoolean("indicate_received", false);
1974 }
1975
1976 public void updateConversationUi() {
1977 if (mOnConversationUpdate != null) {
1978 mOnConversationUpdate.onConversationUpdate();
1979 }
1980 }
1981
1982 public void updateAccountUi() {
1983 if (mOnAccountUpdate != null) {
1984 mOnAccountUpdate.onAccountUpdate();
1985 }
1986 }
1987
1988 public void updateRosterUi() {
1989 if (mOnRosterUpdate != null) {
1990 mOnRosterUpdate.onRosterUpdate();
1991 }
1992 }
1993
1994 public void updateBlocklistUi(final OnUpdateBlocklist.Status status) {
1995 if (mOnUpdateBlocklist != null) {
1996 mOnUpdateBlocklist.OnUpdateBlocklist(status);
1997 }
1998 }
1999
2000 public void updateMucRosterUi() {
2001 if (mOnMucRosterUpdate != null) {
2002 mOnMucRosterUpdate.onMucRosterUpdate();
2003 }
2004 }
2005
2006 public Account findAccountByJid(final Jid accountJid) {
2007 for (Account account : this.accounts) {
2008 if (account.getJid().toBareJid().equals(accountJid.toBareJid())) {
2009 return account;
2010 }
2011 }
2012 return null;
2013 }
2014
2015 public Conversation findConversationByUuid(String uuid) {
2016 for (Conversation conversation : getConversations()) {
2017 if (conversation.getUuid().equals(uuid)) {
2018 return conversation;
2019 }
2020 }
2021 return null;
2022 }
2023
2024 public void markRead(final Conversation conversation) {
2025 mNotificationService.clear(conversation);
2026 conversation.markRead();
2027 }
2028
2029 public void sendReadMarker(final Conversation conversation) {
2030 final Message markable = conversation.getLatestMarkableMessage();
2031 this.markRead(conversation);
2032 if (confirmMessages() && markable != null && markable.getRemoteMsgId() != null) {
2033 Log.d(Config.LOGTAG, conversation.getAccount().getJid().toBareJid()+ ": sending read marker to " + markable.getCounterpart().toString());
2034 Account account = conversation.getAccount();
2035 final Jid to = markable.getCounterpart();
2036 MessagePacket packet = mMessageGenerator.confirm(account, to, markable.getRemoteMsgId());
2037 this.sendMessagePacket(conversation.getAccount(),packet);
2038 }
2039 updateConversationUi();
2040 }
2041
2042 public SecureRandom getRNG() {
2043 return this.mRandom;
2044 }
2045
2046 public MemorizingTrustManager getMemorizingTrustManager() {
2047 return this.mMemorizingTrustManager;
2048 }
2049
2050 public PowerManager getPowerManager() {
2051 return this.pm;
2052 }
2053
2054 public LruCache<String, Bitmap> getBitmapCache() {
2055 return this.mBitmapCache;
2056 }
2057
2058 public void syncRosterToDisk(final Account account) {
2059 new Thread(new Runnable() {
2060
2061 @Override
2062 public void run() {
2063 databaseBackend.writeRoster(account.getRoster());
2064 }
2065 }).start();
2066
2067 }
2068
2069 public List<String> getKnownHosts() {
2070 final List<String> hosts = new ArrayList<>();
2071 for (final Account account : getAccounts()) {
2072 if (!hosts.contains(account.getServer().toString())) {
2073 hosts.add(account.getServer().toString());
2074 }
2075 for (final Contact contact : account.getRoster().getContacts()) {
2076 if (contact.showInRoster()) {
2077 final String server = contact.getServer().toString();
2078 if (server != null && !hosts.contains(server)) {
2079 hosts.add(server);
2080 }
2081 }
2082 }
2083 }
2084 return hosts;
2085 }
2086
2087 public List<String> getKnownConferenceHosts() {
2088 final ArrayList<String> mucServers = new ArrayList<>();
2089 for (final Account account : accounts) {
2090 if (account.getXmppConnection() != null) {
2091 final String server = account.getXmppConnection().getMucServer();
2092 if (server != null && !mucServers.contains(server)) {
2093 mucServers.add(server);
2094 }
2095 }
2096 }
2097 return mucServers;
2098 }
2099
2100 public void sendMessagePacket(Account account, MessagePacket packet) {
2101 XmppConnection connection = account.getXmppConnection();
2102 if (connection != null) {
2103 connection.sendMessagePacket(packet);
2104 }
2105 }
2106
2107 public void sendPresencePacket(Account account, PresencePacket packet) {
2108 XmppConnection connection = account.getXmppConnection();
2109 if (connection != null) {
2110 connection.sendPresencePacket(packet);
2111 }
2112 }
2113
2114 public void sendIqPacket(final Account account, final IqPacket packet, final OnIqPacketReceived callback) {
2115 final XmppConnection connection = account.getXmppConnection();
2116 if (connection != null) {
2117 connection.sendIqPacket(packet, callback);
2118 }
2119 }
2120
2121 public MessageGenerator getMessageGenerator() {
2122 return this.mMessageGenerator;
2123 }
2124
2125 public PresenceGenerator getPresenceGenerator() {
2126 return this.mPresenceGenerator;
2127 }
2128
2129 public IqGenerator getIqGenerator() {
2130 return this.mIqGenerator;
2131 }
2132
2133 public IqParser getIqParser() { return this.mIqParser; }
2134
2135 public JingleConnectionManager getJingleConnectionManager() {
2136 return this.mJingleConnectionManager;
2137 }
2138
2139 public MessageArchiveService getMessageArchiveService() {
2140 return this.mMessageArchiveService;
2141 }
2142
2143 public List<Contact> findContacts(Jid jid) {
2144 ArrayList<Contact> contacts = new ArrayList<>();
2145 for (Account account : getAccounts()) {
2146 if (!account.isOptionSet(Account.OPTION_DISABLED)) {
2147 Contact contact = account.getRoster().getContactFromRoster(jid);
2148 if (contact != null) {
2149 contacts.add(contact);
2150 }
2151 }
2152 }
2153 return contacts;
2154 }
2155
2156 public NotificationService getNotificationService() {
2157 return this.mNotificationService;
2158 }
2159
2160 public HttpConnectionManager getHttpConnectionManager() {
2161 return this.mHttpConnectionManager;
2162 }
2163
2164 public void resendFailedMessages(final Message message) {
2165 final Collection<Message> messages = new ArrayList<>();
2166 Message current = message;
2167 while (current.getStatus() == Message.STATUS_SEND_FAILED) {
2168 messages.add(current);
2169 if (current.mergeable(current.next())) {
2170 current = current.next();
2171 } else {
2172 break;
2173 }
2174 }
2175 for (final Message msg : messages) {
2176 markMessage(msg, Message.STATUS_WAITING);
2177 this.resendMessage(msg);
2178 }
2179 }
2180
2181 public void clearConversationHistory(final Conversation conversation) {
2182 conversation.clearMessages();
2183 conversation.setHasMessagesLeftOnServer(false); //avoid messages getting loaded through mam
2184 new Thread(new Runnable() {
2185 @Override
2186 public void run() {
2187 databaseBackend.deleteMessagesInConversation(conversation);
2188 }
2189 }).start();
2190 }
2191
2192 public interface OnConversationUpdate {
2193 public void onConversationUpdate();
2194 }
2195
2196 public interface OnAccountUpdate {
2197 public void onAccountUpdate();
2198 }
2199
2200 public interface OnRosterUpdate {
2201 public void onRosterUpdate();
2202 }
2203
2204 public interface OnMucRosterUpdate {
2205 public void onMucRosterUpdate();
2206 }
2207
2208 private interface OnConferenceOptionsPushed {
2209 public void onPushSucceeded();
2210 public void onPushFailed();
2211 }
2212
2213 public class XmppConnectionBinder extends Binder {
2214 public XmppConnectionService getService() {
2215 return XmppConnectionService.this;
2216 }
2217 }
2218
2219 public void sendBlockRequest(final Blockable blockable) {
2220 if (blockable != null && blockable.getBlockedJid() != null) {
2221 final Jid jid = blockable.getBlockedJid();
2222 this.sendIqPacket(blockable.getAccount(), getIqGenerator().generateSetBlockRequest(jid), new OnIqPacketReceived() {
2223
2224 @Override
2225 public void onIqPacketReceived(final Account account, final IqPacket packet) {
2226 if (packet.getType() == IqPacket.TYPE.RESULT) {
2227 account.getBlocklist().add(jid);
2228 updateBlocklistUi(OnUpdateBlocklist.Status.BLOCKED);
2229 }
2230 }
2231 });
2232 }
2233 }
2234
2235 public void sendUnblockRequest(final Blockable blockable) {
2236 if (blockable != null && blockable.getJid() != null) {
2237 final Jid jid = blockable.getBlockedJid();
2238 this.sendIqPacket(blockable.getAccount(), getIqGenerator().generateSetUnblockRequest(jid), new OnIqPacketReceived() {
2239 @Override
2240 public void onIqPacketReceived(final Account account, final IqPacket packet) {
2241 if (packet.getType() == IqPacket.TYPE.RESULT) {
2242 account.getBlocklist().remove(jid);
2243 updateBlocklistUi(OnUpdateBlocklist.Status.UNBLOCKED);
2244 }
2245 }
2246 });
2247 }
2248 }
2249}