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