XmppConnectionService.java

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