XmppConnectionService.java

   1package eu.siacs.conversations.services;
   2
   3import java.security.SecureRandom;
   4import java.text.SimpleDateFormat;
   5import java.util.ArrayList;
   6import java.util.Collections;
   7import java.util.Comparator;
   8import java.util.Date;
   9import java.util.Hashtable;
  10import java.util.List;
  11import java.util.Locale;
  12import java.util.TimeZone;
  13import java.util.concurrent.CopyOnWriteArrayList;
  14
  15import org.openintents.openpgp.util.OpenPgpApi;
  16import org.openintents.openpgp.util.OpenPgpServiceConnection;
  17
  18import net.java.otr4j.OtrException;
  19import net.java.otr4j.session.Session;
  20import net.java.otr4j.session.SessionStatus;
  21import eu.siacs.conversations.crypto.PgpEngine;
  22import eu.siacs.conversations.entities.Account;
  23import eu.siacs.conversations.entities.Contact;
  24import eu.siacs.conversations.entities.Conversation;
  25import eu.siacs.conversations.entities.Message;
  26import eu.siacs.conversations.entities.MucOptions;
  27import eu.siacs.conversations.entities.MucOptions.OnRenameListener;
  28import eu.siacs.conversations.entities.Presences;
  29import eu.siacs.conversations.generator.MessageGenerator;
  30import eu.siacs.conversations.generator.PresenceGenerator;
  31import eu.siacs.conversations.parser.IqParser;
  32import eu.siacs.conversations.parser.MessageParser;
  33import eu.siacs.conversations.parser.PresenceParser;
  34import eu.siacs.conversations.persistance.DatabaseBackend;
  35import eu.siacs.conversations.persistance.FileBackend;
  36import eu.siacs.conversations.ui.OnAccountListChangedListener;
  37import eu.siacs.conversations.ui.OnConversationListChangedListener;
  38import eu.siacs.conversations.ui.UiCallback;
  39import eu.siacs.conversations.utils.CryptoHelper;
  40import eu.siacs.conversations.utils.ExceptionHelper;
  41import eu.siacs.conversations.utils.OnPhoneContactsLoadedListener;
  42import eu.siacs.conversations.utils.PRNGFixes;
  43import eu.siacs.conversations.utils.PhoneHelper;
  44import eu.siacs.conversations.utils.UIHelper;
  45import eu.siacs.conversations.xml.Element;
  46import eu.siacs.conversations.xmpp.OnBindListener;
  47import eu.siacs.conversations.xmpp.OnContactStatusChanged;
  48import eu.siacs.conversations.xmpp.OnIqPacketReceived;
  49import eu.siacs.conversations.xmpp.OnStatusChanged;
  50import eu.siacs.conversations.xmpp.OnTLSExceptionReceived;
  51import eu.siacs.conversations.xmpp.XmppConnection;
  52import eu.siacs.conversations.xmpp.jingle.JingleConnectionManager;
  53import eu.siacs.conversations.xmpp.jingle.OnJinglePacketReceived;
  54import eu.siacs.conversations.xmpp.jingle.stanzas.JinglePacket;
  55import eu.siacs.conversations.xmpp.stanzas.IqPacket;
  56import eu.siacs.conversations.xmpp.stanzas.MessagePacket;
  57import eu.siacs.conversations.xmpp.stanzas.PresencePacket;
  58import android.annotation.SuppressLint;
  59import android.app.AlarmManager;
  60import android.app.PendingIntent;
  61import android.app.Service;
  62import android.content.Context;
  63import android.content.Intent;
  64import android.content.SharedPreferences;
  65import android.database.ContentObserver;
  66import android.net.ConnectivityManager;
  67import android.net.NetworkInfo;
  68import android.net.Uri;
  69import android.os.Binder;
  70import android.os.Bundle;
  71import android.os.IBinder;
  72import android.os.PowerManager;
  73import android.os.PowerManager.WakeLock;
  74import android.os.SystemClock;
  75import android.preference.PreferenceManager;
  76import android.provider.ContactsContract;
  77import android.util.Log;
  78
  79public class XmppConnectionService extends Service {
  80
  81	protected static final String LOGTAG = "xmppService";
  82	public DatabaseBackend databaseBackend;
  83	private FileBackend fileBackend;
  84
  85	public long startDate;
  86
  87	private static final int PING_MAX_INTERVAL = 300;
  88	private static final int PING_MIN_INTERVAL = 30;
  89	private static final int PING_TIMEOUT = 10;
  90	private static final int CONNECT_TIMEOUT = 90;
  91	public static final long CARBON_GRACE_PERIOD = 60000L;
  92
  93	private static String ACTION_MERGE_PHONE_CONTACTS = "merge_phone_contacts";
  94
  95	private MessageParser mMessageParser = new MessageParser(this);
  96	private PresenceParser mPresenceParser = new PresenceParser(this);
  97	private IqParser mIqParser = new IqParser(this);
  98	private MessageGenerator mMessageGenerator = new MessageGenerator();
  99	private PresenceGenerator mPresenceGenerator = new PresenceGenerator();
 100	
 101	private List<Account> accounts;
 102	private CopyOnWriteArrayList<Conversation> conversations = null;
 103	private JingleConnectionManager mJingleConnectionManager = new JingleConnectionManager(
 104			this);
 105
 106	private OnConversationListChangedListener convChangedListener = null;
 107	private int convChangedListenerCount = 0;
 108	private OnAccountListChangedListener accountChangedListener = null;
 109	private OnTLSExceptionReceived tlsException = null;
 110	public OnContactStatusChanged onContactStatusChanged = new OnContactStatusChanged() {
 111
 112		@Override
 113		public void onContactStatusChanged(Contact contact, boolean online) {
 114			Conversation conversation = findActiveConversation(contact);
 115			if (conversation != null) {
 116				conversation.endOtrIfNeeded();
 117				if (online && (contact.getPresences().size() == 1)) {
 118					sendUnsendMessages(conversation);
 119				}
 120			}
 121		}
 122	};
 123
 124	public void setOnTLSExceptionReceivedListener(
 125			OnTLSExceptionReceived listener) {
 126		tlsException = listener;
 127	}
 128
 129	private SecureRandom mRandom;
 130
 131	private ContentObserver contactObserver = new ContentObserver(null) {
 132		@Override
 133		public void onChange(boolean selfChange) {
 134			super.onChange(selfChange);
 135			Intent intent = new Intent(getApplicationContext(),
 136					XmppConnectionService.class);
 137			intent.setAction(ACTION_MERGE_PHONE_CONTACTS);
 138			startService(intent);
 139		}
 140	};
 141
 142	private final IBinder mBinder = new XmppConnectionBinder();
 143	private OnStatusChanged statusListener = new OnStatusChanged() {
 144
 145		@Override
 146		public void onStatusChanged(Account account) {
 147			if (accountChangedListener != null) {
 148				accountChangedListener.onAccountListChangedListener();
 149			}
 150			if (account.getStatus() == Account.STATUS_ONLINE) {
 151				mJingleConnectionManager.cancelInTransmission();
 152				List<Conversation> conversations = getConversations();
 153				for (int i = 0; i < conversations.size(); ++i) {
 154					if (conversations.get(i).getAccount() == account) {
 155						conversations.get(i).startOtrIfNeeded();
 156						sendUnsendMessages(conversations.get(i));
 157					}
 158				}
 159				syncDirtyContacts(account);
 160				scheduleWakeupCall(PING_MAX_INTERVAL, true);
 161			} else if (account.getStatus() == Account.STATUS_OFFLINE) {
 162				if (!account.isOptionSet(Account.OPTION_DISABLED)) {
 163					int timeToReconnect = mRandom.nextInt(50) + 10;
 164					scheduleWakeupCall(timeToReconnect, false);
 165				}
 166
 167			} else if (account.getStatus() == Account.STATUS_REGISTRATION_SUCCESSFULL) {
 168				databaseBackend.updateAccount(account);
 169				reconnectAccount(account, true);
 170			} else if ((account.getStatus() != Account.STATUS_CONNECTING)
 171					&& (account.getStatus() != Account.STATUS_NO_INTERNET)) {
 172				int next = account.getXmppConnection().getTimeToNextAttempt();
 173				Log.d(LOGTAG, account.getJid()
 174						+ ": error connecting account. try again in " + next
 175						+ "s for the "
 176						+ (account.getXmppConnection().getAttempt() + 1)
 177						+ " time");
 178				scheduleWakeupCall(next, false);
 179			}
 180			UIHelper.showErrorNotification(getApplicationContext(),
 181					getAccounts());
 182		}
 183	};
 184
 185	private OnJinglePacketReceived jingleListener = new OnJinglePacketReceived() {
 186
 187		@Override
 188		public void onJinglePacketReceived(Account account, JinglePacket packet) {
 189			mJingleConnectionManager.deliverPacket(account, packet);
 190		}
 191	};
 192
 193	private OpenPgpServiceConnection pgpServiceConnection;
 194	private PgpEngine mPgpEngine = null;
 195	private Intent pingIntent;
 196	private PendingIntent pendingPingIntent = null;
 197	private WakeLock wakeLock;
 198	private PowerManager pm;
 199
 200	public PgpEngine getPgpEngine() {
 201		if (pgpServiceConnection.isBound()) {
 202			if (this.mPgpEngine == null) {
 203				this.mPgpEngine = new PgpEngine(new OpenPgpApi(
 204						getApplicationContext(),
 205						pgpServiceConnection.getService()), this);
 206			}
 207			return mPgpEngine;
 208		} else {
 209			return null;
 210		}
 211
 212	}
 213
 214	public FileBackend getFileBackend() {
 215		return this.fileBackend;
 216	}
 217
 218	public Message attachImageToConversation(final Conversation conversation,
 219			final Uri uri, final UiCallback<Message> callback) {
 220		final Message message;
 221		if (conversation.getNextEncryption() == Message.ENCRYPTION_PGP) {
 222			message = new Message(conversation, "",
 223					Message.ENCRYPTION_DECRYPTED);
 224		} else {
 225			message = new Message(conversation, "",
 226					conversation.getNextEncryption());
 227		}
 228		message.setPresence(conversation.getNextPresence());
 229		message.setType(Message.TYPE_IMAGE);
 230		message.setStatus(Message.STATUS_OFFERED);
 231		new Thread(new Runnable() {
 232
 233			@Override
 234			public void run() {
 235				try {
 236					getFileBackend().copyImageToPrivateStorage(message, uri);
 237					if (conversation.getNextEncryption() == Message.ENCRYPTION_PGP) {
 238						getPgpEngine().encrypt(message, callback);
 239					} else {
 240						callback.success(message);
 241					}
 242				} catch (FileBackend.ImageCopyException e) {
 243					callback.error(e.getResId(), message);
 244				}
 245			}
 246		}).start();
 247		return message;
 248	}
 249
 250	public Conversation findMuc(String name, Account account) {
 251		for (Conversation conversation : this.conversations) {
 252			if (conversation.getContactJid().split("/")[0].equals(name)
 253					&& (conversation.getAccount() == account)) {
 254				return conversation;
 255			}
 256		}
 257		return null;
 258	}
 259
 260	public class XmppConnectionBinder extends Binder {
 261		public XmppConnectionService getService() {
 262			return XmppConnectionService.this;
 263		}
 264	}
 265
 266	@Override
 267	public int onStartCommand(Intent intent, int flags, int startId) {
 268		if ((intent != null)
 269				&& (ACTION_MERGE_PHONE_CONTACTS.equals(intent.getAction()))) {
 270			mergePhoneContactsWithRoster();
 271			return START_STICKY;
 272		} else if ((intent != null)
 273				&& (Intent.ACTION_SHUTDOWN.equals(intent.getAction()))) {
 274			logoutAndSave();
 275			return START_NOT_STICKY;
 276		}
 277		this.wakeLock.acquire();
 278		ConnectivityManager cm = (ConnectivityManager) getApplicationContext()
 279				.getSystemService(Context.CONNECTIVITY_SERVICE);
 280		NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
 281		boolean isConnected = activeNetwork != null
 282				&& activeNetwork.isConnected();
 283
 284		for (Account account : accounts) {
 285			if (!account.isOptionSet(Account.OPTION_DISABLED)) {
 286				if (!isConnected) {
 287					account.setStatus(Account.STATUS_NO_INTERNET);
 288					if (statusListener != null) {
 289						statusListener.onStatusChanged(account);
 290					}
 291				} else {
 292					if (account.getStatus() == Account.STATUS_NO_INTERNET) {
 293						account.setStatus(Account.STATUS_OFFLINE);
 294						if (statusListener != null) {
 295							statusListener.onStatusChanged(account);
 296						}
 297					}
 298					if (account.getStatus() == Account.STATUS_ONLINE) {
 299						long lastReceived = account.getXmppConnection().lastPaketReceived;
 300						long lastSent = account.getXmppConnection().lastPingSent;
 301						if (lastSent - lastReceived >= PING_TIMEOUT * 1000) {
 302							Log.d(LOGTAG, account.getJid() + ": ping timeout");
 303							this.reconnectAccount(account, true);
 304						} else if (SystemClock.elapsedRealtime() - lastReceived >= PING_MIN_INTERVAL * 1000) {
 305							account.getXmppConnection().sendPing();
 306							account.getXmppConnection().lastPingSent = SystemClock
 307									.elapsedRealtime();
 308							this.scheduleWakeupCall(2, false);
 309						}
 310					} else if (account.getStatus() == Account.STATUS_OFFLINE) {
 311						if (account.getXmppConnection() == null) {
 312							account.setXmppConnection(this
 313									.createConnection(account));
 314						}
 315						account.getXmppConnection().lastPingSent = SystemClock
 316								.elapsedRealtime();
 317						new Thread(account.getXmppConnection()).start();
 318					} else if ((account.getStatus() == Account.STATUS_CONNECTING)
 319							&& ((SystemClock.elapsedRealtime() - account
 320									.getXmppConnection().lastConnect) / 1000 >= CONNECT_TIMEOUT)) {
 321						Log.d(LOGTAG, account.getJid()
 322								+ ": time out during connect reconnecting");
 323						reconnectAccount(account, true);
 324					} else {
 325						if (account.getXmppConnection().getTimeToNextAttempt() <= 0) {
 326							reconnectAccount(account, true);
 327						}
 328					}
 329					// in any case. reschedule wakup call
 330					this.scheduleWakeupCall(PING_MAX_INTERVAL, true);
 331				}
 332				if (accountChangedListener != null) {
 333					accountChangedListener.onAccountListChangedListener();
 334				}
 335			}
 336		}
 337		if (wakeLock.isHeld()) {
 338			try {
 339				wakeLock.release();
 340			} catch (RuntimeException re) {
 341			}
 342		}
 343		return START_STICKY;
 344	}
 345
 346	@SuppressLint("TrulyRandom")
 347	@Override
 348	public void onCreate() {
 349		ExceptionHelper.init(getApplicationContext());
 350		PRNGFixes.apply();
 351		this.mRandom = new SecureRandom();
 352		this.databaseBackend = DatabaseBackend
 353				.getInstance(getApplicationContext());
 354		this.fileBackend = new FileBackend(getApplicationContext());
 355		this.accounts = databaseBackend.getAccounts();
 356
 357		for (Account account : this.accounts) {
 358			this.databaseBackend.readRoster(account.getRoster());
 359		}
 360		this.mergePhoneContactsWithRoster();
 361		this.getConversations();
 362
 363		getContentResolver().registerContentObserver(
 364				ContactsContract.Contacts.CONTENT_URI, true, contactObserver);
 365		this.pgpServiceConnection = new OpenPgpServiceConnection(
 366				getApplicationContext(), "org.sufficientlysecure.keychain");
 367		this.pgpServiceConnection.bindToService();
 368
 369		this.pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
 370		this.wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
 371				"XmppConnectionService");
 372	}
 373
 374	@Override
 375	public void onDestroy() {
 376		super.onDestroy();
 377		this.logoutAndSave();
 378	}
 379
 380	@Override
 381	public void onTaskRemoved(Intent rootIntent) {
 382		super.onTaskRemoved(rootIntent);
 383		this.logoutAndSave();
 384	}
 385
 386	private void logoutAndSave() {
 387		for (Account account : accounts) {
 388			databaseBackend.writeRoster(account.getRoster());
 389			if (account.getXmppConnection() != null) {
 390				disconnect(account, false);
 391			}
 392		}
 393		Context context = getApplicationContext();
 394		AlarmManager alarmManager = (AlarmManager) context
 395				.getSystemService(Context.ALARM_SERVICE);
 396		Intent intent = new Intent(context, EventReceiver.class);
 397		alarmManager.cancel(PendingIntent.getBroadcast(context, 0, intent, 0));
 398		Log.d(LOGTAG, "good bye");
 399		stopSelf();
 400	}
 401
 402	protected void scheduleWakeupCall(int seconds, boolean ping) {
 403		long timeToWake = SystemClock.elapsedRealtime() + seconds * 1000;
 404		Context context = getApplicationContext();
 405		AlarmManager alarmManager = (AlarmManager) context
 406				.getSystemService(Context.ALARM_SERVICE);
 407
 408		if (ping) {
 409			if (this.pingIntent == null) {
 410				this.pingIntent = new Intent(context, EventReceiver.class);
 411				this.pingIntent.setAction("ping");
 412				this.pingIntent.putExtra("time", timeToWake);
 413				this.pendingPingIntent = PendingIntent.getBroadcast(context, 0,
 414						this.pingIntent, 0);
 415				alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
 416						timeToWake, pendingPingIntent);
 417			} else {
 418				long scheduledTime = this.pingIntent.getLongExtra("time", 0);
 419				if (scheduledTime < SystemClock.elapsedRealtime()
 420						|| (scheduledTime > timeToWake)) {
 421					this.pingIntent.putExtra("time", timeToWake);
 422					alarmManager.cancel(this.pendingPingIntent);
 423					this.pendingPingIntent = PendingIntent.getBroadcast(
 424							context, 0, this.pingIntent, 0);
 425					alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
 426							timeToWake, pendingPingIntent);
 427				}
 428			}
 429		} else {
 430			Intent intent = new Intent(context, EventReceiver.class);
 431			intent.setAction("ping_check");
 432			PendingIntent alarmIntent = PendingIntent.getBroadcast(context, 0,
 433					intent, 0);
 434			alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, timeToWake,
 435					alarmIntent);
 436		}
 437
 438	}
 439
 440	public XmppConnection createConnection(Account account) {
 441		SharedPreferences sharedPref = getPreferences();
 442		account.setResource(sharedPref.getString("resource", "mobile")
 443				.toLowerCase(Locale.getDefault()));
 444		XmppConnection connection = new XmppConnection(account, this);
 445		connection.setOnMessagePacketReceivedListener(this.mMessageParser);
 446		connection.setOnStatusChangedListener(this.statusListener);
 447		connection.setOnPresencePacketReceivedListener(this.mPresenceParser);
 448		connection
 449				.setOnUnregisteredIqPacketReceivedListener(this.mIqParser);
 450		connection.setOnJinglePacketReceivedListener(this.jingleListener);
 451		connection
 452				.setOnTLSExceptionReceivedListener(new OnTLSExceptionReceived() {
 453
 454					@Override
 455					public void onTLSExceptionReceived(String fingerprint,
 456							Account account) {
 457						Log.d(LOGTAG, "tls exception arrived in service");
 458						if (tlsException != null) {
 459							tlsException.onTLSExceptionReceived(fingerprint,
 460									account);
 461						}
 462					}
 463				});
 464		connection.setOnBindListener(new OnBindListener() {
 465
 466			@Override
 467			public void onBind(final Account account) {
 468				account.getRoster().clearPresences();
 469				account.clearPresences(); // self presences
 470				fetchRosterFromServer(account);
 471				sendPresencePacket(account, mPresenceGenerator.sendPresence(account));
 472				connectMultiModeConversations(account);
 473				if (convChangedListener != null) {
 474					convChangedListener.onConversationListChanged();
 475				}
 476			}
 477		});
 478		return connection;
 479	}
 480
 481	synchronized public void sendMessage(Message message) {
 482		Account account = message.getConversation().getAccount();
 483		Conversation conv = message.getConversation();
 484		MessagePacket packet = null;
 485		boolean saveInDb = true;
 486		boolean send = false;
 487		if (account.getStatus() == Account.STATUS_ONLINE) {
 488			if (message.getType() == Message.TYPE_IMAGE) {
 489				if (message.getPresence() != null) {
 490					if (message.getEncryption() == Message.ENCRYPTION_OTR) {
 491						if (!conv.hasValidOtrSession()
 492								&& (message.getPresence() != null)) {
 493							conv.startOtrSession(getApplicationContext(),
 494									message.getPresence(), true);
 495							message.setStatus(Message.STATUS_WAITING);
 496						} else if (conv.hasValidOtrSession()
 497								&& conv.getOtrSession().getSessionStatus() == SessionStatus.ENCRYPTED) {
 498							mJingleConnectionManager
 499									.createNewConnection(message);
 500						} else if (message.getPresence() == null) {
 501							message.setStatus(Message.STATUS_WAITING);
 502						}
 503					} else {
 504						mJingleConnectionManager.createNewConnection(message);
 505					}
 506				} else {
 507					message.setStatus(Message.STATUS_WAITING);
 508				}
 509			} else {
 510				if (message.getEncryption() == Message.ENCRYPTION_OTR) {
 511					if (!conv.hasValidOtrSession()
 512							&& (message.getPresence() != null)) {
 513						conv.startOtrSession(getApplicationContext(),
 514								message.getPresence(), true);
 515						message.setStatus(Message.STATUS_WAITING);
 516					} else if (conv.hasValidOtrSession()
 517							&& conv.getOtrSession().getSessionStatus() == SessionStatus.ENCRYPTED) {
 518						message.setPresence(conv.getOtrSession().getSessionID()
 519								.getUserID());
 520						packet = mMessageGenerator.generateOtrChat(message);
 521						send = true;
 522						message.setStatus(Message.STATUS_SEND);
 523						
 524					} else if (message.getPresence() == null) {
 525						message.setStatus(Message.STATUS_WAITING);
 526					}
 527				} else if (message.getEncryption() == Message.ENCRYPTION_DECRYPTED) {
 528					message.getConversation().endOtrIfNeeded();
 529					packet = mMessageGenerator.generatePgpChat(message);
 530					message.setStatus(Message.STATUS_SEND);
 531					send = true;
 532				} else {
 533					message.getConversation().endOtrIfNeeded();
 534					if (message.getConversation().getMode() == Conversation.MODE_SINGLE) {
 535						message.setStatus(Message.STATUS_SEND);
 536					}
 537					packet = mMessageGenerator.generateChat(message);
 538					send = true;
 539				}
 540			}
 541		} else {
 542			message.setStatus(Message.STATUS_WAITING);
 543			if (message.getType() == Message.TYPE_TEXT) {
 544				if (message.getEncryption() == Message.ENCRYPTION_DECRYPTED) {
 545					String pgpBody = message.getEncryptedBody();
 546					String decryptedBody = message.getBody();
 547					message.setBody(pgpBody);
 548					databaseBackend.createMessage(message);
 549					saveInDb = false;
 550					message.setBody(decryptedBody);
 551				} else if (message.getEncryption() == Message.ENCRYPTION_OTR) {
 552					if (conv.hasValidOtrSession()) {
 553						message.setPresence(conv.getOtrSession().getSessionID()
 554								.getUserID());
 555					} else if (!conv.hasValidOtrSession()
 556							&& message.getPresence() != null) {
 557						conv.startOtrSession(getApplicationContext(),
 558								message.getPresence(), false);
 559					}
 560				}
 561			}
 562
 563		}
 564		if (saveInDb) {
 565			databaseBackend.createMessage(message);
 566		}
 567		conv.getMessages().add(message);
 568		if (convChangedListener != null) {
 569			convChangedListener.onConversationListChanged();
 570		}
 571		if ((send) && (packet != null)) {
 572			sendMessagePacket(account, packet);
 573		}
 574
 575	}
 576
 577	private void sendUnsendMessages(Conversation conversation) {
 578		for (int i = 0; i < conversation.getMessages().size(); ++i) {
 579			int status = conversation.getMessages().get(i).getStatus();
 580			if (status == Message.STATUS_WAITING) {
 581				resendMessage(conversation.getMessages().get(i));
 582			}
 583		}
 584	}
 585
 586	private void resendMessage(Message message) {
 587		Account account = message.getConversation().getAccount();
 588		MessagePacket packet = null;
 589		if (message.getEncryption() == Message.ENCRYPTION_OTR) {
 590			Presences presences = message.getConversation().getContact()
 591					.getPresences();
 592			if (!message.getConversation().hasValidOtrSession()) {
 593				if ((message.getPresence() != null)
 594						&& (presences.has(message.getPresence()))) {
 595					message.getConversation().startOtrSession(
 596							getApplicationContext(), message.getPresence(),
 597							true);
 598				} else {
 599					if (presences.size() == 1) {
 600						String presence = presences.asStringArray()[0];
 601						message.getConversation().startOtrSession(
 602								getApplicationContext(), presence, true);
 603					}
 604				}
 605			} else {
 606				if (message.getConversation().getOtrSession()
 607						.getSessionStatus() == SessionStatus.ENCRYPTED) {
 608					if (message.getType() == Message.TYPE_TEXT) {
 609						packet = mMessageGenerator.generateOtrChat(message,
 610								true);
 611					} else if (message.getType() == Message.TYPE_IMAGE) {
 612						mJingleConnectionManager.createNewConnection(message);
 613					}
 614				}
 615			}
 616		} else if (message.getType() == Message.TYPE_TEXT) {
 617			if (message.getEncryption() == Message.ENCRYPTION_NONE) {
 618				packet = mMessageGenerator.generateChat(message, true);
 619			} else if ((message.getEncryption() == Message.ENCRYPTION_DECRYPTED)
 620					|| (message.getEncryption() == Message.ENCRYPTION_PGP)) {
 621				packet = mMessageGenerator.generatePgpChat(message, true);
 622			}
 623		} else if (message.getType() == Message.TYPE_IMAGE) {
 624			Presences presences = message.getConversation().getContact()
 625					.getPresences();
 626			if ((message.getPresence() != null)
 627					&& (presences.has(message.getPresence()))) {
 628				markMessage(message, Message.STATUS_OFFERED);
 629				mJingleConnectionManager.createNewConnection(message);
 630			} else {
 631				if (presences.size() == 1) {
 632					String presence = presences.asStringArray()[0];
 633					message.setPresence(presence);
 634					markMessage(message, Message.STATUS_OFFERED);
 635					mJingleConnectionManager.createNewConnection(message);
 636				}
 637			}
 638		}
 639		if (packet != null) {
 640			sendMessagePacket(account,packet);
 641			markMessage(message, Message.STATUS_SEND);
 642		}
 643	}
 644
 645	public void fetchRosterFromServer(Account account) {
 646		IqPacket iqPacket = new IqPacket(IqPacket.TYPE_GET);
 647		if (!"".equals(account.getRosterVersion())) {
 648			Log.d(LOGTAG, account.getJid() + ": fetching roster version "
 649					+ account.getRosterVersion());
 650		} else {
 651			Log.d(LOGTAG, account.getJid() + ": fetching roster");
 652		}
 653		iqPacket.query("jabber:iq:roster").setAttribute("ver",
 654				account.getRosterVersion());
 655		account.getXmppConnection().sendIqPacket(iqPacket,
 656				new OnIqPacketReceived() {
 657
 658					@Override
 659					public void onIqPacketReceived(final Account account,
 660							IqPacket packet) {
 661						Element query = packet.findChild("query");
 662						if (query != null) {
 663							account.getRoster().markAllAsNotInRoster();
 664							mIqParser.rosterItems(account, query);
 665						}
 666					}
 667				});
 668	}
 669
 670	private void mergePhoneContactsWithRoster() {
 671		PhoneHelper.loadPhoneContacts(getApplicationContext(),
 672				new OnPhoneContactsLoadedListener() {
 673					@Override
 674					public void onPhoneContactsLoaded(List<Bundle> phoneContacts) {
 675						for (Account account : accounts) {
 676							account.getRoster().clearSystemAccounts();
 677						}
 678						for (Bundle phoneContact : phoneContacts) {
 679							for (Account account : accounts) {
 680								String jid = phoneContact.getString("jid");
 681								Contact contact = account.getRoster()
 682										.getContact(jid);
 683								String systemAccount = phoneContact
 684										.getInt("phoneid")
 685										+ "#"
 686										+ phoneContact.getString("lookup");
 687								contact.setSystemAccount(systemAccount);
 688								contact.setPhotoUri(phoneContact
 689										.getString("photouri"));
 690								contact.setSystemName(phoneContact
 691										.getString("displayname"));
 692							}
 693						}
 694					}
 695				});
 696	}
 697
 698	public List<Conversation> getConversations() {
 699		if (this.conversations == null) {
 700			Hashtable<String, Account> accountLookupTable = new Hashtable<String, Account>();
 701			for (Account account : this.accounts) {
 702				accountLookupTable.put(account.getUuid(), account);
 703			}
 704			this.conversations = databaseBackend
 705					.getConversations(Conversation.STATUS_AVAILABLE);
 706			for (Conversation conv : this.conversations) {
 707				Account account = accountLookupTable.get(conv.getAccountUuid());
 708				conv.setAccount(account);
 709				conv.setMessages(databaseBackend.getMessages(conv, 50));
 710			}
 711		}
 712		
 713		return this.conversations;
 714	}
 715	
 716	public void populateWithOrderedConversations(List<Conversation> list) {
 717		list.clear();
 718		list.addAll(getConversations());
 719		Collections.sort(list, new Comparator<Conversation>() {
 720			@Override
 721			public int compare(Conversation lhs, Conversation rhs) {
 722				Message left = lhs.getLatestMessage();
 723				Message right = rhs.getLatestMessage();
 724				if (left.getTimeSent() > right.getTimeSent()) {
 725					return -1;
 726				} else if (left.getTimeSent() < right.getTimeSent()) {
 727					return 1;
 728				} else {
 729					return 0;
 730				}
 731			}
 732		});
 733	}
 734
 735	public List<Message> getMoreMessages(Conversation conversation,
 736			long timestamp) {
 737		List<Message> messages = databaseBackend.getMessages(conversation, 50,
 738				timestamp);
 739		for (Message message : messages) {
 740			message.setConversation(conversation);
 741		}
 742		return messages;
 743	}
 744
 745	public List<Account> getAccounts() {
 746		return this.accounts;
 747	}
 748
 749	public Conversation findActiveConversation(Contact contact) {
 750		for (Conversation conversation : this.getConversations()) {
 751			if (conversation.getContact() == contact) {
 752				return conversation;
 753			}
 754		}
 755		return null;
 756	}
 757
 758	public Conversation findOrCreateConversation(Account account, String jid,
 759			boolean muc) {
 760		for (Conversation conv : this.getConversations()) {
 761			if ((conv.getAccount().equals(account))
 762					&& (conv.getContactJid().split("/")[0].equals(jid))) {
 763				return conv;
 764			}
 765		}
 766		Conversation conversation = databaseBackend.findConversation(account,
 767				jid);
 768		if (conversation != null) {
 769			conversation.setStatus(Conversation.STATUS_AVAILABLE);
 770			conversation.setAccount(account);
 771			if (muc) {
 772				conversation.setMode(Conversation.MODE_MULTI);
 773			} else {
 774				conversation.setMode(Conversation.MODE_SINGLE);
 775			}
 776			conversation.setMessages(databaseBackend.getMessages(conversation,
 777					50));
 778			this.databaseBackend.updateConversation(conversation);
 779		} else {
 780			String conversationName;
 781			Contact contact = account.getRoster().getContact(jid);
 782			if (contact != null) {
 783				conversationName = contact.getDisplayName();
 784			} else {
 785				conversationName = jid.split("@")[0];
 786			}
 787			if (muc) {
 788				conversation = new Conversation(conversationName, account, jid,
 789						Conversation.MODE_MULTI);
 790			} else {
 791				conversation = new Conversation(conversationName, account, jid,
 792						Conversation.MODE_SINGLE);
 793			}
 794			this.databaseBackend.createConversation(conversation);
 795		}
 796		this.conversations.add(conversation);
 797		if ((account.getStatus() == Account.STATUS_ONLINE)
 798				&& (conversation.getMode() == Conversation.MODE_MULTI)) {
 799			joinMuc(conversation);
 800		}
 801		if (this.convChangedListener != null) {
 802			this.convChangedListener.onConversationListChanged();
 803		}
 804		return conversation;
 805	}
 806
 807	public void archiveConversation(Conversation conversation) {
 808		if (conversation.getMode() == Conversation.MODE_MULTI) {
 809			leaveMuc(conversation);
 810		} else {
 811			conversation.endOtrIfNeeded();
 812		}
 813		this.databaseBackend.updateConversation(conversation);
 814		this.conversations.remove(conversation);
 815		if (this.convChangedListener != null) {
 816			this.convChangedListener.onConversationListChanged();
 817		}
 818	}
 819
 820	public void clearConversationHistory(Conversation conversation) {
 821		this.databaseBackend.deleteMessagesInConversation(conversation);
 822		this.fileBackend.removeFiles(conversation);
 823		conversation.getMessages().clear();
 824		if (this.convChangedListener != null) {
 825			this.convChangedListener.onConversationListChanged();
 826		}
 827	}
 828
 829	public int getConversationCount() {
 830		return this.databaseBackend.getConversationCount();
 831	}
 832
 833	public void createAccount(Account account) {
 834		databaseBackend.createAccount(account);
 835		this.accounts.add(account);
 836		this.reconnectAccount(account, false);
 837		if (accountChangedListener != null)
 838			accountChangedListener.onAccountListChangedListener();
 839	}
 840
 841	public void updateAccount(Account account) {
 842		this.statusListener.onStatusChanged(account);
 843		databaseBackend.updateAccount(account);
 844		reconnectAccount(account, false);
 845		if (accountChangedListener != null) {
 846			accountChangedListener.onAccountListChangedListener();
 847		}
 848		UIHelper.showErrorNotification(getApplicationContext(), getAccounts());
 849	}
 850
 851	public void deleteAccount(Account account) {
 852		if (account.getXmppConnection() != null) {
 853			this.disconnect(account, true);
 854		}
 855		databaseBackend.deleteAccount(account);
 856		this.accounts.remove(account);
 857		if (accountChangedListener != null) {
 858			accountChangedListener.onAccountListChangedListener();
 859		}
 860		UIHelper.showErrorNotification(getApplicationContext(), getAccounts());
 861	}
 862
 863	public void setOnConversationListChangedListener(
 864			OnConversationListChangedListener listener) {
 865		this.convChangedListener = listener;
 866		this.convChangedListenerCount++;
 867	}
 868
 869	public void removeOnConversationListChangedListener() {
 870		this.convChangedListenerCount--;
 871		if (this.convChangedListenerCount == 0) {
 872			this.convChangedListener = null;
 873		}
 874	}
 875
 876	public void setOnAccountListChangedListener(
 877			OnAccountListChangedListener listener) {
 878		this.accountChangedListener = listener;
 879	}
 880
 881	public void removeOnAccountListChangedListener() {
 882		this.accountChangedListener = null;
 883	}
 884
 885	public void connectMultiModeConversations(Account account) {
 886		List<Conversation> conversations = getConversations();
 887		for (int i = 0; i < conversations.size(); i++) {
 888			Conversation conversation = conversations.get(i);
 889			if ((conversation.getMode() == Conversation.MODE_MULTI)
 890					&& (conversation.getAccount() == account)) {
 891				joinMuc(conversation);
 892			}
 893		}
 894	}
 895
 896	public void joinMuc(Conversation conversation) {
 897		Account account = conversation.getAccount();
 898		String[] mucParts = conversation.getContactJid().split("/");
 899		String muc;
 900		String nick;
 901		if (mucParts.length == 2) {
 902			muc = mucParts[0];
 903			nick = mucParts[1];
 904		} else {
 905			muc = mucParts[0];
 906			nick = account.getUsername();
 907		}
 908		PresencePacket packet = new PresencePacket();
 909		packet.setAttribute("to", muc + "/" + nick);
 910		Element x = new Element("x");
 911		x.setAttribute("xmlns", "http://jabber.org/protocol/muc");
 912		String sig = account.getPgpSignature();
 913		if (sig != null) {
 914			packet.addChild("status").setContent("online");
 915			packet.addChild("x", "jabber:x:signed").setContent(sig);
 916		}
 917		if (conversation.getMessages().size() != 0) {
 918			final SimpleDateFormat mDateFormat = new SimpleDateFormat(
 919					"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US);
 920			mDateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
 921			Date date = new Date(
 922					conversation.getLatestMessage().getTimeSent() + 1000);
 923			x.addChild("history").setAttribute("since",
 924					mDateFormat.format(date));
 925		}
 926		packet.addChild(x);
 927		sendPresencePacket(account, packet);
 928	}
 929
 930	private OnRenameListener renameListener = null;
 931
 932	public void setOnRenameListener(OnRenameListener listener) {
 933		this.renameListener = listener;
 934	}
 935
 936	public void renameInMuc(final Conversation conversation, final String nick) {
 937		final MucOptions options = conversation.getMucOptions();
 938		if (options.online()) {
 939			Account account = conversation.getAccount();
 940			options.setOnRenameListener(new OnRenameListener() {
 941
 942				@Override
 943				public void onRename(boolean success) {
 944					if (renameListener != null) {
 945						renameListener.onRename(success);
 946					}
 947					if (success) {
 948						String jid = conversation.getContactJid().split("/")[0]
 949								+ "/" + nick;
 950						conversation.setContactJid(jid);
 951						databaseBackend.updateConversation(conversation);
 952					}
 953				}
 954			});
 955			options.flagAboutToRename();
 956			PresencePacket packet = new PresencePacket();
 957			packet.setAttribute("to",
 958					conversation.getContactJid().split("/")[0] + "/" + nick);
 959			packet.setAttribute("from", conversation.getAccount().getFullJid());
 960
 961			String sig = account.getPgpSignature();
 962			if (sig != null) {
 963				packet.addChild("status").setContent("online");
 964				packet.addChild("x", "jabber:x:signed").setContent(sig);
 965			}
 966			sendPresencePacket(account,packet);
 967		} else {
 968			String jid = conversation.getContactJid().split("/")[0] + "/"
 969					+ nick;
 970			conversation.setContactJid(jid);
 971			databaseBackend.updateConversation(conversation);
 972			if (conversation.getAccount().getStatus() == Account.STATUS_ONLINE) {
 973				joinMuc(conversation);
 974			}
 975		}
 976	}
 977
 978	public void leaveMuc(Conversation conversation) {
 979		PresencePacket packet = new PresencePacket();
 980		packet.setAttribute("to", conversation.getContactJid().split("/")[0]
 981				+ "/" + conversation.getMucOptions().getNick());
 982		packet.setAttribute("from", conversation.getAccount().getFullJid());
 983		packet.setAttribute("type", "unavailable");
 984		Log.d(LOGTAG, "send leaving muc " + packet);
 985		sendPresencePacket(conversation.getAccount(),packet);
 986		conversation.getMucOptions().setOffline();
 987	}
 988
 989	public void disconnect(Account account, boolean force) {
 990		if ((account.getStatus() == Account.STATUS_ONLINE)
 991				|| (account.getStatus() == Account.STATUS_DISABLED)) {
 992			if (!force) {
 993				List<Conversation> conversations = getConversations();
 994				for (int i = 0; i < conversations.size(); i++) {
 995					Conversation conversation = conversations.get(i);
 996					if (conversation.getAccount() == account) {
 997						if (conversation.getMode() == Conversation.MODE_MULTI) {
 998							leaveMuc(conversation);
 999						} else {
1000							conversation.endOtrIfNeeded();
1001						}
1002					}
1003				}
1004			}
1005			account.getXmppConnection().disconnect(force);
1006		}
1007	}
1008
1009	@Override
1010	public IBinder onBind(Intent intent) {
1011		return mBinder;
1012	}
1013
1014	public void updateMessage(Message message) {
1015		databaseBackend.updateMessage(message);
1016	}
1017
1018	protected void syncDirtyContacts(Account account) {
1019		for (Contact contact : account.getRoster().getContacts()) {
1020			if (contact.getOption(Contact.Options.DIRTY_PUSH)) {
1021				pushContactToServer(contact);
1022			}
1023			if (contact.getOption(Contact.Options.DIRTY_DELETE)) {
1024				Log.d(LOGTAG, "dirty delete");
1025				deleteContactOnServer(contact);
1026			}
1027		}
1028	}
1029
1030	public void createContact(Contact contact) {
1031		SharedPreferences sharedPref = getPreferences();
1032		boolean autoGrant = sharedPref.getBoolean("grant_new_contacts", true);
1033		if (autoGrant) {
1034			contact.setOption(Contact.Options.PREEMPTIVE_GRANT);
1035			contact.setOption(Contact.Options.ASKING);
1036		}
1037		pushContactToServer(contact);
1038	}
1039
1040	public void onOtrSessionEstablished(Conversation conversation) {
1041		Account account = conversation.getAccount();
1042		List<Message> messages = conversation.getMessages();
1043		Session otrSession = conversation.getOtrSession();
1044		Log.d(LOGTAG, account.getJid() + " otr session established with "
1045				+ conversation.getContactJid() + "/"
1046				+ otrSession.getSessionID().getUserID());
1047		for (int i = 0; i < messages.size(); ++i) {
1048			Message msg = messages.get(i);
1049			if ((msg.getStatus() == Message.STATUS_UNSEND || msg.getStatus() == Message.STATUS_WAITING)
1050					&& (msg.getEncryption() == Message.ENCRYPTION_OTR)) {
1051				msg.setPresence(otrSession.getSessionID().getUserID());
1052				if (msg.getType() == Message.TYPE_TEXT) {
1053					MessagePacket outPacket = mMessageGenerator
1054							.generateOtrChat(msg, true);
1055					if (outPacket != null) {
1056						msg.setStatus(Message.STATUS_SEND);
1057						databaseBackend.updateMessage(msg);
1058						sendMessagePacket(account,outPacket);
1059					}
1060				} else if (msg.getType() == Message.TYPE_IMAGE) {
1061					mJingleConnectionManager.createNewConnection(msg);
1062				}
1063			}
1064		}
1065		updateUi(conversation, false);
1066	}
1067
1068	public boolean renewSymmetricKey(Conversation conversation) {
1069		Account account = conversation.getAccount();
1070		byte[] symmetricKey = new byte[32];
1071		this.mRandom.nextBytes(symmetricKey);
1072		Session otrSession = conversation.getOtrSession();
1073		if (otrSession != null) {
1074			MessagePacket packet = new MessagePacket();
1075			packet.setType(MessagePacket.TYPE_CHAT);
1076			packet.setFrom(account.getFullJid());
1077			packet.addChild("private", "urn:xmpp:carbons:2");
1078			packet.addChild("no-copy", "urn:xmpp:hints");
1079			packet.setTo(otrSession.getSessionID().getAccountID() + "/"
1080					+ otrSession.getSessionID().getUserID());
1081			try {
1082				packet.setBody(otrSession
1083						.transformSending(CryptoHelper.FILETRANSFER
1084								+ CryptoHelper.bytesToHex(symmetricKey)));
1085				sendMessagePacket(account,packet);
1086				conversation.setSymmetricKey(symmetricKey);
1087				return true;
1088			} catch (OtrException e) {
1089				return false;
1090			}
1091		}
1092		return false;
1093	}
1094
1095	public void pushContactToServer(Contact contact) {
1096		contact.resetOption(Contact.Options.DIRTY_DELETE);
1097		contact.setOption(Contact.Options.DIRTY_PUSH);
1098		Account account = contact.getAccount();
1099		if (account.getStatus() == Account.STATUS_ONLINE) {
1100			boolean ask = contact.getOption(Contact.Options.ASKING);
1101			boolean sendUpdates = contact.getOption(Contact.Options.PENDING_SUBSCRIPTION_REQUEST)
1102					&& contact.getOption(Contact.Options.PREEMPTIVE_GRANT);
1103			IqPacket iq = new IqPacket(IqPacket.TYPE_SET);
1104			iq.query("jabber:iq:roster").addChild(contact.asElement());
1105			account.getXmppConnection().sendIqPacket(iq, null);
1106			if (sendUpdates) {
1107				sendPresencePacket(account, mPresenceGenerator.sendPresenceUpdatesTo(contact));
1108			}
1109			if (ask) {
1110				sendPresencePacket(account, mPresenceGenerator.requestPresenceUpdatesFrom(contact));
1111			}
1112		}
1113	}
1114
1115	public void deleteContactOnServer(Contact contact) {
1116		contact.resetOption(Contact.Options.PREEMPTIVE_GRANT);
1117		contact.resetOption(Contact.Options.DIRTY_PUSH);
1118		contact.setOption(Contact.Options.DIRTY_DELETE);
1119		Account account = contact.getAccount();
1120		if (account.getStatus() == Account.STATUS_ONLINE) {
1121			IqPacket iq = new IqPacket(IqPacket.TYPE_SET);
1122			Element item = iq.query("jabber:iq:roster").addChild("item");
1123			item.setAttribute("jid", contact.getJid());
1124			item.setAttribute("subscription", "remove");
1125			account.getXmppConnection().sendIqPacket(iq, null);
1126		}
1127	}
1128
1129	public void updateConversation(Conversation conversation) {
1130		this.databaseBackend.updateConversation(conversation);
1131	}
1132
1133	public void removeOnTLSExceptionReceivedListener() {
1134		this.tlsException = null;
1135	}
1136
1137	public void reconnectAccount(final Account account, final boolean force) {
1138		new Thread(new Runnable() {
1139
1140			@Override
1141			public void run() {
1142				if (account.getXmppConnection() != null) {
1143					disconnect(account, force);
1144				}
1145				if (!account.isOptionSet(Account.OPTION_DISABLED)) {
1146					if (account.getXmppConnection() == null) {
1147						account.setXmppConnection(createConnection(account));
1148					}
1149					Thread thread = new Thread(account.getXmppConnection());
1150					thread.start();
1151					scheduleWakeupCall((int) (CONNECT_TIMEOUT * 1.2), false);
1152				}
1153			}
1154		}).start();
1155	}
1156
1157	public void inviteToConference(Conversation conversation,
1158			List<Contact> contacts) {
1159		for (Contact contact : contacts) {
1160			MessagePacket packet = new MessagePacket();
1161			packet.setTo(conversation.getContactJid().split("/")[0]);
1162			packet.setFrom(conversation.getAccount().getFullJid());
1163			Element x = new Element("x");
1164			x.setAttribute("xmlns", "http://jabber.org/protocol/muc#user");
1165			Element invite = new Element("invite");
1166			invite.setAttribute("to", contact.getJid());
1167			x.addChild(invite);
1168			packet.addChild(x);
1169			Log.d(LOGTAG, packet.toString());
1170			sendMessagePacket(conversation.getAccount(),packet);
1171		}
1172
1173	}
1174
1175	public boolean markMessage(Account account, String recipient, String uuid,
1176			int status) {
1177		for (Conversation conversation : getConversations()) {
1178			if (conversation.getContactJid().equals(recipient)
1179					&& conversation.getAccount().equals(account)) {
1180				return markMessage(conversation, uuid, status);
1181			}
1182		}
1183		return false;
1184	}
1185
1186	public boolean markMessage(Conversation conversation, String uuid,
1187			int status) {
1188		for (Message message : conversation.getMessages()) {
1189			if (message.getUuid().equals(uuid)) {
1190				markMessage(message, status);
1191				return true;
1192			}
1193		}
1194		return false;
1195	}
1196
1197	public void markMessage(Message message, int status) {
1198		message.setStatus(status);
1199		databaseBackend.updateMessage(message);
1200		if (convChangedListener != null) {
1201			convChangedListener.onConversationListChanged();
1202		}
1203	}
1204
1205	public SharedPreferences getPreferences() {
1206		return PreferenceManager
1207				.getDefaultSharedPreferences(getApplicationContext());
1208	}
1209
1210	public boolean confirmMessages() {
1211		return getPreferences().getBoolean("confirm_messages", true);
1212	}
1213
1214	public void updateUi(Conversation conversation, boolean notify) {
1215		if (convChangedListener != null) {
1216			convChangedListener.onConversationListChanged();
1217		} else {
1218			UIHelper.updateNotification(getApplicationContext(),
1219					getConversations(), conversation, notify);
1220		}
1221	}
1222
1223	public Account findAccountByJid(String accountJid) {
1224		for (Account account : this.accounts) {
1225			if (account.getJid().equals(accountJid)) {
1226				return account;
1227			}
1228		}
1229		return null;
1230	}
1231
1232	public void markRead(Conversation conversation) {
1233		conversation.markRead();
1234		String id = conversation.popLatestMarkableMessageId();
1235		if (confirmMessages() && id != null) {
1236			Account account = conversation.getAccount();
1237			String to = conversation.getContactJid();
1238			this.sendMessagePacket(conversation.getAccount(), mMessageGenerator.confirm(account, to, id));
1239		}
1240	}
1241
1242	public SecureRandom getRNG() {
1243		return this.mRandom;
1244	}
1245
1246	public PowerManager getPowerManager() {
1247		return this.pm;
1248	}
1249
1250	public void replyWithNotAcceptable(Account account, MessagePacket packet) {
1251		if (account.getStatus() == Account.STATUS_ONLINE) {
1252			MessagePacket error = this.mMessageGenerator
1253					.generateNotAcceptable(packet);
1254			sendMessagePacket(account,error);
1255		}
1256	}
1257
1258	public void syncRosterToDisk(final Account account) {
1259		new Thread(new Runnable() {
1260
1261			@Override
1262			public void run() {
1263				databaseBackend.writeRoster(account.getRoster());
1264			}
1265		}).start();
1266
1267	}
1268
1269	public List<String> getKnownHosts() {
1270		List<String> hosts = new ArrayList<String>();
1271		for (Account account : getAccounts()) {
1272			if (!hosts.contains(account.getServer())) {
1273				hosts.add(account.getServer());
1274			}
1275			for (Contact contact : account.getRoster().getContacts()) {
1276				if (contact.showInRoster()) {
1277					String server = contact.getServer();
1278					if (server != null && !hosts.contains(server)) {
1279						hosts.add(server);
1280					}
1281				}
1282			}
1283		}
1284		return hosts;
1285	}
1286
1287	public List<String> getKnownConferenceHosts() {
1288		ArrayList<String> mucServers = new ArrayList<String>();
1289		for (Account account : accounts) {
1290			if (account.getXmppConnection() != null) {
1291				String server = account.getXmppConnection().getMucServer();
1292				if (server != null) {
1293					mucServers.add(server);
1294				}
1295			}
1296		}
1297		return mucServers;
1298	}
1299	
1300	public void sendMessagePacket(Account account, MessagePacket packet) {
1301		account.getXmppConnection().sendMessagePacket(packet);
1302	}
1303	
1304	public void sendPresencePacket(Account account, PresencePacket packet) {
1305		account.getXmppConnection().sendPresencePacket(packet);
1306	}
1307	
1308	public MessageGenerator getMessageGenerator() {
1309		return this.mMessageGenerator;
1310	}
1311	
1312	public PresenceGenerator getPresenceGenerator() {
1313		return this.mPresenceGenerator;
1314	}
1315	
1316	public JingleConnectionManager getJingleConnectionManager() {
1317		return this.mJingleConnectionManager;
1318	}
1319}