XmppConnectionService.java

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