XmppConnectionService.java

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