XmppConnectionService.java

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