diff --git a/briar-android/res/values/roboguice.xml b/briar-android/res/values/roboguice.xml
index 32110839f6c3ba3da878e67e248b2199e2918b55..3ac5b40bf34d197c79c17d068ebf3410851ab9a0 100644
--- a/briar-android/res/values/roboguice.xml
+++ b/briar-android/res/values/roboguice.xml
@@ -4,6 +4,7 @@
 		<item>org.briarproject.android.AndroidModule</item>
 		<item>org.briarproject.crypto.CryptoModule</item>
 		<item>org.briarproject.db.DatabaseModule</item>
+		<item>org.briarproject.event.EventModule</item>
 		<item>org.briarproject.invitation.InvitationModule</item>
 		<item>org.briarproject.lifecycle.LifecycleModule</item>
 		<item>org.briarproject.messaging.MessagingModule</item>
diff --git a/briar-android/src/org/briarproject/android/AndroidNotificationManagerImpl.java b/briar-android/src/org/briarproject/android/AndroidNotificationManagerImpl.java
index f65d71f704fd88514ca4d983bcac98663dd7981a..597988cf90ce5f0a31958209de5a1a68866f640e 100644
--- a/briar-android/src/org/briarproject/android/AndroidNotificationManagerImpl.java
+++ b/briar-android/src/org/briarproject/android/AndroidNotificationManagerImpl.java
@@ -27,6 +27,7 @@ import org.briarproject.api.db.DatabaseComponent;
 import org.briarproject.api.db.DatabaseExecutor;
 import org.briarproject.api.db.DbException;
 import org.briarproject.api.event.Event;
+import org.briarproject.api.event.EventBus;
 import org.briarproject.api.event.EventListener;
 import org.briarproject.api.event.SettingsUpdatedEvent;
 import org.briarproject.api.lifecycle.Service;
@@ -52,6 +53,7 @@ Service, EventListener {
 
 	private final DatabaseComponent db;
 	private final Executor dbExecutor;
+	private final EventBus eventBus;
 	private final Context appContext;
 	private final Map<ContactId, Integer> contactCounts =
 			new HashMap<ContactId, Integer>(); // Locking: this
@@ -65,14 +67,16 @@ Service, EventListener {
 
 	@Inject
 	public AndroidNotificationManagerImpl(DatabaseComponent db,
-			@DatabaseExecutor Executor dbExecutor, Application app) {
+			@DatabaseExecutor Executor dbExecutor, EventBus eventBus,
+			Application app) {
 		this.db = db;
 		this.dbExecutor = dbExecutor;
+		this.eventBus = eventBus;
 		appContext = app.getApplicationContext();
 	}
 
 	public boolean start() {
-		db.addListener(this);
+		eventBus.addListener(this);
 		loadSettings();
 		return true;
 	}
@@ -91,7 +95,7 @@ Service, EventListener {
 	}
 
 	public boolean stop() {
-		db.removeListener(this);
+		eventBus.removeListener(this);
 		return true;
 	}
 
diff --git a/briar-android/src/org/briarproject/android/BriarService.java b/briar-android/src/org/briarproject/android/BriarService.java
index 36493ee44d967309634fff8e84e140cd2b2e0d81..ab28acce75f3ae2887898d9e5757425c9e96155d 100644
--- a/briar-android/src/org/briarproject/android/BriarService.java
+++ b/briar-android/src/org/briarproject/android/BriarService.java
@@ -23,6 +23,7 @@ import org.briarproject.api.db.DatabaseConfig;
 import org.briarproject.api.db.DatabaseExecutor;
 import org.briarproject.api.db.DbException;
 import org.briarproject.api.event.Event;
+import org.briarproject.api.event.EventBus;
 import org.briarproject.api.event.EventListener;
 import org.briarproject.api.event.MessageAddedEvent;
 import org.briarproject.api.lifecycle.LifecycleManager;
@@ -58,6 +59,7 @@ public class BriarService extends RoboService implements EventListener {
 	@Inject private volatile AndroidExecutor androidExecutor;
 	@Inject @DatabaseExecutor private volatile Executor dbExecutor;
 	@Inject private volatile DatabaseComponent db;
+	@Inject private volatile EventBus eventBus;
 	private volatile boolean started = false;
 
 	@Override
@@ -92,7 +94,7 @@ public class BriarService extends RoboService implements EventListener {
 			public void run() {
 				StartResult result = lifecycleManager.startServices();
 				if(result == SUCCESS) {
-					db.addListener(BriarService.this);
+					eventBus.addListener(BriarService.this);
 					started = true;
 				} else if(result == ALREADY_RUNNING) {
 					LOG.info("Already running");
@@ -146,7 +148,7 @@ public class BriarService extends RoboService implements EventListener {
 			@Override
 			public void run() {
 				if(started) {
-					db.removeListener(BriarService.this);
+					eventBus.removeListener(BriarService.this);
 					lifecycleManager.stopServices();
 				}
 				androidExecutor.shutdown();
diff --git a/briar-android/src/org/briarproject/android/SettingsActivity.java b/briar-android/src/org/briarproject/android/SettingsActivity.java
index f5f4e8236a867560ef380deb7104be374ccb8ab7..0d508bba097cd59002cfb162c4f387b5f75c21ae 100644
--- a/briar-android/src/org/briarproject/android/SettingsActivity.java
+++ b/briar-android/src/org/briarproject/android/SettingsActivity.java
@@ -35,6 +35,7 @@ import org.briarproject.api.TransportId;
 import org.briarproject.api.db.DatabaseComponent;
 import org.briarproject.api.db.DbException;
 import org.briarproject.api.event.Event;
+import org.briarproject.api.event.EventBus;
 import org.briarproject.api.event.EventListener;
 import org.briarproject.api.event.SettingsUpdatedEvent;
 import org.briarproject.util.StringUtils;
@@ -72,6 +73,7 @@ OnClickListener {
 
 	// Fields that are accessed from background threads must be volatile
 	@Inject private volatile DatabaseComponent db;
+	@Inject private volatile EventBus eventBus;
 	private volatile Settings settings;
 	private volatile boolean bluetoothSetting = true;
 
@@ -201,7 +203,7 @@ OnClickListener {
 	@Override
 	public void onResume() {
 		super.onResume();
-		db.addListener(this);
+		eventBus.addListener(this);
 		loadSettings();
 	}
 
@@ -262,7 +264,7 @@ OnClickListener {
 	@Override
 	public void onPause() {
 		super.onPause();
-		db.removeListener(this);
+		eventBus.removeListener(this);
 	}
 
 	public void onClick(View view) {
diff --git a/briar-android/src/org/briarproject/android/contact/ContactListActivity.java b/briar-android/src/org/briarproject/android/contact/ContactListActivity.java
index 895332d354d9340c9271ed7a60033e8eea395716..b72a0276b3766204fe8518159fcddf55b17d26cb 100644
--- a/briar-android/src/org/briarproject/android/contact/ContactListActivity.java
+++ b/briar-android/src/org/briarproject/android/contact/ContactListActivity.java
@@ -33,6 +33,7 @@ import org.briarproject.api.db.NoSuchContactException;
 import org.briarproject.api.event.ContactAddedEvent;
 import org.briarproject.api.event.ContactRemovedEvent;
 import org.briarproject.api.event.Event;
+import org.briarproject.api.event.EventBus;
 import org.briarproject.api.event.EventListener;
 import org.briarproject.api.event.MessageAddedEvent;
 import org.briarproject.api.event.MessageExpiredEvent;
@@ -75,6 +76,7 @@ EventListener, ConnectionListener {
 
 	// Fields that are accessed from background threads must be volatile
 	@Inject private volatile DatabaseComponent db;
+	@Inject private volatile EventBus eventBus;
 
 	@Override
 	public void onCreate(Bundle state) {
@@ -125,7 +127,7 @@ EventListener, ConnectionListener {
 	@Override
 	public void onResume() {
 		super.onResume();
-		db.addListener(this);
+		eventBus.addListener(this);
 		connectionRegistry.addListener(this);
 		loadContacts();
 	}
@@ -210,7 +212,7 @@ EventListener, ConnectionListener {
 	@Override
 	public void onPause() {
 		super.onPause();
-		db.removeListener(this);
+		eventBus.removeListener(this);
 		connectionRegistry.removeListener(this);
 	}
 
diff --git a/briar-android/src/org/briarproject/android/contact/ConversationActivity.java b/briar-android/src/org/briarproject/android/contact/ConversationActivity.java
index d097024fba8f2a73ac448ca58b1e434fae0307f8..b951ae91eef5cb7b64acf2d8c838f8087ab5b809 100644
--- a/briar-android/src/org/briarproject/android/contact/ConversationActivity.java
+++ b/briar-android/src/org/briarproject/android/contact/ConversationActivity.java
@@ -50,6 +50,7 @@ import org.briarproject.api.db.NoSuchMessageException;
 import org.briarproject.api.db.NoSuchSubscriptionException;
 import org.briarproject.api.event.ContactRemovedEvent;
 import org.briarproject.api.event.Event;
+import org.briarproject.api.event.EventBus;
 import org.briarproject.api.event.EventListener;
 import org.briarproject.api.event.MessageAddedEvent;
 import org.briarproject.api.event.MessageExpiredEvent;
@@ -96,6 +97,7 @@ implements EventListener, OnClickListener, OnItemClickListener {
 
 	// Fields that are accessed from background threads must be volatile
 	@Inject private volatile DatabaseComponent db;
+	@Inject private volatile EventBus eventBus;
 	@Inject private volatile MessageFactory messageFactory;
 	private volatile ContactId contactId = null;
 	private volatile String contactName = null;
@@ -188,7 +190,7 @@ implements EventListener, OnClickListener, OnItemClickListener {
 	@Override
 	public void onResume() {
 		super.onResume();
-		db.addListener(this);
+		eventBus.addListener(this);
 		loadContactAndGroup();
 		loadHeaders();
 	}
@@ -331,7 +333,7 @@ implements EventListener, OnClickListener, OnItemClickListener {
 	@Override
 	public void onPause() {
 		super.onPause();
-		db.removeListener(this);
+		eventBus.removeListener(this);
 		if(isFinishing()) markMessagesRead();
 	}
 
diff --git a/briar-android/src/org/briarproject/android/groups/ConfigureGroupActivity.java b/briar-android/src/org/briarproject/android/groups/ConfigureGroupActivity.java
index b0ba112b3b2466044ec9e05190aadc08e49f99c3..4c7a2079ed0e83a7c1b2c496b81206609a9d9c49 100644
--- a/briar-android/src/org/briarproject/android/groups/ConfigureGroupActivity.java
+++ b/briar-android/src/org/briarproject/android/groups/ConfigureGroupActivity.java
@@ -30,6 +30,7 @@ import org.briarproject.api.ContactId;
 import org.briarproject.api.db.DatabaseComponent;
 import org.briarproject.api.db.DbException;
 import org.briarproject.api.event.Event;
+import org.briarproject.api.event.EventBus;
 import org.briarproject.api.event.EventListener;
 import org.briarproject.api.event.LocalSubscriptionsUpdatedEvent;
 import org.briarproject.api.event.RemoteSubscriptionsUpdatedEvent;
@@ -69,6 +70,7 @@ SelectContactsDialog.Listener {
 
 	// Fields that are accessed from background threads must be volatile
 	@Inject private volatile DatabaseComponent db;
+	@Inject private volatile EventBus eventBus;
 	private volatile GroupId groupId = null;
 	private volatile Group group = null;
 	private volatile boolean subscribed = false;
@@ -171,7 +173,7 @@ SelectContactsDialog.Listener {
 	@Override
 	public void onResume() {
 		super.onResume();
-		db.addListener(this);
+		eventBus.addListener(this);
 		loadSubscribers();
 	}
 
@@ -224,7 +226,7 @@ SelectContactsDialog.Listener {
 	@Override
 	public void onPause() {
 		super.onPause();
-		db.removeListener(this);
+		eventBus.removeListener(this);
 	}
 
 	public void onClick(View view) {
diff --git a/briar-android/src/org/briarproject/android/groups/GroupActivity.java b/briar-android/src/org/briarproject/android/groups/GroupActivity.java
index d5fffe6e9313cd5ef7008a5f7b03c156dade190c..7d8aa13d42ba1620311bd3fff6e6d97f5bbad14f 100644
--- a/briar-android/src/org/briarproject/android/groups/GroupActivity.java
+++ b/briar-android/src/org/briarproject/android/groups/GroupActivity.java
@@ -35,6 +35,7 @@ import org.briarproject.api.db.MessageHeader;
 import org.briarproject.api.db.NoSuchMessageException;
 import org.briarproject.api.db.NoSuchSubscriptionException;
 import org.briarproject.api.event.Event;
+import org.briarproject.api.event.EventBus;
 import org.briarproject.api.event.EventListener;
 import org.briarproject.api.event.MessageAddedEvent;
 import org.briarproject.api.event.MessageExpiredEvent;
@@ -72,6 +73,7 @@ OnClickListener, OnItemClickListener {
 
 	// Fields that are accessed from background threads must be volatile
 	@Inject private volatile DatabaseComponent db;
+	@Inject private volatile EventBus eventBus;
 	private volatile GroupId groupId = null;
 	private volatile Group group = null;
 
@@ -139,7 +141,7 @@ OnClickListener, OnItemClickListener {
 	@Override
 	public void onResume() {
 		super.onResume();
-		db.addListener(this);
+		eventBus.addListener(this);
 		loadGroup();
 		loadHeaders();
 	}
@@ -272,7 +274,7 @@ OnClickListener, OnItemClickListener {
 	@Override
 	public void onPause() {
 		super.onPause();
-		db.removeListener(this);
+		eventBus.removeListener(this);
 		if(isFinishing()) markMessagesRead();
 	}
 
diff --git a/briar-android/src/org/briarproject/android/groups/GroupListActivity.java b/briar-android/src/org/briarproject/android/groups/GroupListActivity.java
index 531b63e95632a0a3ae63ec94229227374c6b1a8f..769818a42d2bc986184034f09b3b4d19e402516b 100644
--- a/briar-android/src/org/briarproject/android/groups/GroupListActivity.java
+++ b/briar-android/src/org/briarproject/android/groups/GroupListActivity.java
@@ -30,6 +30,7 @@ import org.briarproject.api.db.DbException;
 import org.briarproject.api.db.MessageHeader;
 import org.briarproject.api.db.NoSuchSubscriptionException;
 import org.briarproject.api.event.Event;
+import org.briarproject.api.event.EventBus;
 import org.briarproject.api.event.EventListener;
 import org.briarproject.api.event.MessageAddedEvent;
 import org.briarproject.api.event.MessageExpiredEvent;
@@ -70,6 +71,7 @@ implements EventListener, OnClickListener, OnItemClickListener {
 
 	// Fields that are accessed from background threads must be volatile
 	@Inject private volatile DatabaseComponent db;
+	@Inject private volatile EventBus eventBus;
 
 	@Override
 	public void onCreate(Bundle state) {
@@ -143,7 +145,7 @@ implements EventListener, OnClickListener, OnItemClickListener {
 	@Override
 	public void onResume() {
 		super.onResume();
-		db.addListener(this);
+		eventBus.addListener(this);
 		loadHeaders();
 	}
 
@@ -210,7 +212,7 @@ implements EventListener, OnClickListener, OnItemClickListener {
 				adapter.sort(GroupListItemComparator.INSTANCE);
 				adapter.notifyDataSetChanged();
 				selectFirstUnread();
-			} 
+			}
 		});
 	}
 
@@ -255,7 +257,7 @@ implements EventListener, OnClickListener, OnItemClickListener {
 	@Override
 	public void onPause() {
 		super.onPause();
-		db.removeListener(this);
+		eventBus.removeListener(this);
 	}
 
 	public void eventOccurred(Event e) {
diff --git a/briar-android/src/org/briarproject/android/groups/ManageGroupsActivity.java b/briar-android/src/org/briarproject/android/groups/ManageGroupsActivity.java
index 7ac4f306592540c13b9f746c823f95f43972dcbd..799bc84b279eca66c26f47b3966da6337ce76131 100644
--- a/briar-android/src/org/briarproject/android/groups/ManageGroupsActivity.java
+++ b/briar-android/src/org/briarproject/android/groups/ManageGroupsActivity.java
@@ -16,6 +16,7 @@ import org.briarproject.android.util.ListLoadingProgressBar;
 import org.briarproject.api.db.DatabaseComponent;
 import org.briarproject.api.db.DbException;
 import org.briarproject.api.event.Event;
+import org.briarproject.api.event.EventBus;
 import org.briarproject.api.event.EventListener;
 import org.briarproject.api.event.RemoteSubscriptionsUpdatedEvent;
 import org.briarproject.api.event.SubscriptionAddedEvent;
@@ -44,6 +45,7 @@ implements EventListener, OnItemClickListener {
 
 	// Fields that are accessed from background threads must be volatile
 	@Inject private volatile DatabaseComponent db;
+	@Inject private volatile EventBus eventBus;
 
 	@Override
 	public void onCreate(Bundle state) {
@@ -69,7 +71,7 @@ implements EventListener, OnItemClickListener {
 	@Override
 	public void onResume() {
 		super.onResume();
-		db.addListener(this);
+		eventBus.addListener(this);
 		loadGroups();
 	}
 
@@ -111,7 +113,7 @@ implements EventListener, OnItemClickListener {
 	@Override
 	public void onPause() {
 		super.onPause();
-		db.removeListener(this);
+		eventBus.removeListener(this);
 	}
 
 	public void eventOccurred(Event e) {
diff --git a/briar-api/src/org/briarproject/api/db/DatabaseComponent.java b/briar-api/src/org/briarproject/api/db/DatabaseComponent.java
index 2d35402566e204a09f0ab60f0e3320f08f8be5ee..751ef78c9a5774d0a138e4adcaec714eaf905c4d 100644
--- a/briar-api/src/org/briarproject/api/db/DatabaseComponent.java
+++ b/briar-api/src/org/briarproject/api/db/DatabaseComponent.java
@@ -13,7 +13,6 @@ import org.briarproject.api.Settings;
 import org.briarproject.api.TransportConfig;
 import org.briarproject.api.TransportId;
 import org.briarproject.api.TransportProperties;
-import org.briarproject.api.event.EventListener;
 import org.briarproject.api.messaging.Ack;
 import org.briarproject.api.messaging.Group;
 import org.briarproject.api.messaging.GroupId;
@@ -43,12 +42,6 @@ public interface DatabaseComponent {
 	/** Waits for any open transactions to finish and closes the database. */
 	void close() throws DbException, IOException;
 
-	/** Adds a listener to be notified when database events occur. */
-	void addListener(EventListener l);
-
-	/** Removes a listener. */
-	void removeListener(EventListener l);
-
 	/**
 	 * Stores a contact associated with the given local and remote pseudonyms,
 	 * and returns an ID for the contact.
diff --git a/briar-api/src/org/briarproject/api/event/EventBus.java b/briar-api/src/org/briarproject/api/event/EventBus.java
new file mode 100644
index 0000000000000000000000000000000000000000..0b49d81e6da09b7358820c69dda7dd70aa1381ad
--- /dev/null
+++ b/briar-api/src/org/briarproject/api/event/EventBus.java
@@ -0,0 +1,13 @@
+package org.briarproject.api.event;
+
+public interface EventBus {
+
+	/** Adds a listener to be notified when events occur. */
+	void addListener(EventListener l);
+
+	/** Removes a listener. */
+	void removeListener(EventListener l);
+
+	/** Notifies all listeners of an event. */
+	void broadcast(Event e);
+}
diff --git a/briar-core/src/org/briarproject/db/DatabaseComponentImpl.java b/briar-core/src/org/briarproject/db/DatabaseComponentImpl.java
index 79113fbd4371c4a9dd79019bb1be273787b6752e..6683d36702c6d13b7c5c89ded82292c70d28ffd8 100644
--- a/briar-core/src/org/briarproject/db/DatabaseComponentImpl.java
+++ b/briar-core/src/org/briarproject/db/DatabaseComponentImpl.java
@@ -15,7 +15,6 @@ import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
-import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.locks.ReentrantReadWriteLock;
 import java.util.logging.Logger;
 
@@ -42,8 +41,7 @@ import org.briarproject.api.db.NoSuchSubscriptionException;
 import org.briarproject.api.db.NoSuchTransportException;
 import org.briarproject.api.event.ContactAddedEvent;
 import org.briarproject.api.event.ContactRemovedEvent;
-import org.briarproject.api.event.Event;
-import org.briarproject.api.event.EventListener;
+import org.briarproject.api.event.EventBus;
 import org.briarproject.api.event.LocalAuthorAddedEvent;
 import org.briarproject.api.event.LocalAuthorRemovedEvent;
 import org.briarproject.api.event.LocalSubscriptionsUpdatedEvent;
@@ -95,21 +93,21 @@ DatabaseCleaner.Callback {
 
 	private final Database<T> db;
 	private final DatabaseCleaner cleaner;
+	private final EventBus eventBus;
 	private final ShutdownManager shutdown;
 
 	private final ReentrantReadWriteLock lock =
 			new ReentrantReadWriteLock(true);
-	private final Collection<EventListener> listeners =
-			new CopyOnWriteArrayList<EventListener>();
 
 	private boolean open = false; // Locking: lock.writeLock
 	private int shutdownHandle = -1; // Locking: lock.writeLock
 
 	@Inject
 	DatabaseComponentImpl(Database<T> db, DatabaseCleaner cleaner,
-			ShutdownManager shutdown) {
+			EventBus eventBus, ShutdownManager shutdown) {
 		this.db = db;
 		this.cleaner = cleaner;
+		this.eventBus = eventBus;
 		this.shutdown = shutdown;
 	}
 
@@ -158,19 +156,6 @@ DatabaseCleaner.Callback {
 		}
 	}
 
-	public void addListener(EventListener l) {
-		listeners.add(l);
-	}
-
-	public void removeListener(EventListener l) {
-		listeners.remove(l);
-	}
-
-	/** Notifies all listeners of a database event. */
-	private void callListeners(Event e) {
-		for(EventListener l : listeners) l.eventOccurred(e);
-	}
-
 	public ContactId addContact(Author remote, AuthorId local)
 			throws DbException {
 		ContactId c;
@@ -191,7 +176,7 @@ DatabaseCleaner.Callback {
 		} finally {
 			lock.writeLock().unlock();
 		}
-		callListeners(new ContactAddedEvent(c));
+		eventBus.broadcast(new ContactAddedEvent(c));
 		return c;
 	}
 
@@ -231,7 +216,7 @@ DatabaseCleaner.Callback {
 		} finally {
 			lock.writeLock().unlock();
 		}
-		if(added) callListeners(new SubscriptionAddedEvent(g));
+		if(added) eventBus.broadcast(new SubscriptionAddedEvent(g));
 		return added;
 	}
 
@@ -251,18 +236,18 @@ DatabaseCleaner.Callback {
 		} finally {
 			lock.writeLock().unlock();
 		}
-		callListeners(new LocalAuthorAddedEvent(a.getId()));
+		eventBus.broadcast(new LocalAuthorAddedEvent(a.getId()));
 	}
 
 	public void addLocalMessage(Message m) throws DbException {
-		boolean duplicate;
+		boolean duplicate, subscribed;
 		lock.writeLock().lock();
 		try {
 			T txn = db.startTransaction();
 			try {
 				duplicate = db.containsMessage(txn, m.getId());
-				if(!duplicate && db.containsGroup(txn, m.getGroup().getId()))
-					addMessage(txn, m, null);
+				subscribed = db.containsGroup(txn, m.getGroup().getId());
+				if(!duplicate && subscribed) addMessage(txn, m, null);
 				db.commitTransaction(txn);
 			} catch(DbException e) {
 				db.abortTransaction(txn);
@@ -271,7 +256,8 @@ DatabaseCleaner.Callback {
 		} finally {
 			lock.writeLock().unlock();
 		}
-		if(!duplicate) callListeners(new MessageAddedEvent(m.getGroup(), null));
+		if(!duplicate && subscribed)
+			eventBus.broadcast(new MessageAddedEvent(m.getGroup(), null));
 	}
 
 	/**
@@ -344,7 +330,7 @@ DatabaseCleaner.Callback {
 		} finally {
 			lock.writeLock().unlock();
 		}
-		if(added) callListeners(new TransportAddedEvent(t, maxLatency));
+		if(added) eventBus.broadcast(new TransportAddedEvent(t, maxLatency));
 		return added;
 	}
 
@@ -1062,7 +1048,7 @@ DatabaseCleaner.Callback {
 		} finally {
 			lock.writeLock().unlock();
 		}
-		if(changed) callListeners(new LocalTransportsUpdatedEvent());
+		if(changed) eventBus.broadcast(new LocalTransportsUpdatedEvent());
 	}
 
 	public void mergeSettings(Settings s) throws DbException {
@@ -1083,7 +1069,7 @@ DatabaseCleaner.Callback {
 		} finally {
 			lock.writeLock().unlock();
 		}
-		if(changed) callListeners(new SettingsUpdatedEvent());
+		if(changed) eventBus.broadcast(new SettingsUpdatedEvent());
 	}
 
 	public void receiveAck(ContactId c, Ack a) throws DbException {
@@ -1108,7 +1094,7 @@ DatabaseCleaner.Callback {
 		} finally {
 			lock.writeLock().unlock();
 		}
-		callListeners(new MessagesAckedEvent(c, acked));
+		eventBus.broadcast(new MessagesAckedEvent(c, acked));
 	}
 
 	public void receiveMessage(ContactId c, Message m) throws DbException {
@@ -1135,8 +1121,8 @@ DatabaseCleaner.Callback {
 		}
 		if(visible) {
 			if(!duplicate)
-				callListeners(new MessageAddedEvent(m.getGroup(), c));
-			callListeners(new MessageToAckEvent(c));
+				eventBus.broadcast(new MessageAddedEvent(m.getGroup(), c));
+			eventBus.broadcast(new MessageToAckEvent(c));
 		}
 	}
 
@@ -1168,8 +1154,8 @@ DatabaseCleaner.Callback {
 		} finally {
 			lock.writeLock().unlock();
 		}
-		if(ack) callListeners(new MessageToAckEvent(c));
-		if(request) callListeners(new MessageToRequestEvent(c));
+		if(ack) eventBus.broadcast(new MessageToAckEvent(c));
+		if(request) eventBus.broadcast(new MessageToRequestEvent(c));
 	}
 
 	public void receiveRequest(ContactId c, Request r) throws DbException {
@@ -1195,7 +1181,7 @@ DatabaseCleaner.Callback {
 		} finally {
 			lock.writeLock().unlock();
 		}
-		if(requested) callListeners(new MessageRequestedEvent(c));
+		if(requested) eventBus.broadcast(new MessageRequestedEvent(c));
 	}
 
 	public void receiveRetentionAck(ContactId c, RetentionAck a)
@@ -1236,7 +1222,7 @@ DatabaseCleaner.Callback {
 		} finally {
 			lock.writeLock().unlock();
 		}
-		if(updated) callListeners(new RemoteRetentionTimeUpdatedEvent(c));
+		if(updated) eventBus.broadcast(new RemoteRetentionTimeUpdatedEvent(c));
 	}
 
 	public void receiveSubscriptionAck(ContactId c, SubscriptionAck a)
@@ -1276,7 +1262,7 @@ DatabaseCleaner.Callback {
 		} finally {
 			lock.writeLock().unlock();
 		}
-		if(updated) callListeners(new RemoteSubscriptionsUpdatedEvent(c));
+		if(updated) eventBus.broadcast(new RemoteSubscriptionsUpdatedEvent(c));
 	}
 
 	public void receiveTransportAck(ContactId c, TransportAck a)
@@ -1322,7 +1308,7 @@ DatabaseCleaner.Callback {
 			lock.writeLock().unlock();
 		}
 		if(updated)
-			callListeners(new RemoteTransportsUpdatedEvent(c, u.getId()));
+			eventBus.broadcast(new RemoteTransportsUpdatedEvent(c, u.getId()));
 	}
 
 	public void removeContact(ContactId c) throws DbException {
@@ -1343,7 +1329,7 @@ DatabaseCleaner.Callback {
 		} finally {
 			lock.writeLock().unlock();
 		}
-		callListeners(new ContactRemovedEvent(c));
+		eventBus.broadcast(new ContactRemovedEvent(c));
 	}
 
 	public void removeGroup(Group g) throws DbException {
@@ -1365,8 +1351,8 @@ DatabaseCleaner.Callback {
 		} finally {
 			lock.writeLock().unlock();
 		}
-		callListeners(new SubscriptionRemovedEvent(g));
-		callListeners(new LocalSubscriptionsUpdatedEvent(affected));
+		eventBus.broadcast(new SubscriptionRemovedEvent(g));
+		eventBus.broadcast(new LocalSubscriptionsUpdatedEvent(affected));
 	}
 
 	public void removeLocalAuthor(AuthorId a) throws DbException {
@@ -1391,8 +1377,9 @@ DatabaseCleaner.Callback {
 		} finally {
 			lock.writeLock().unlock();
 		}
-		for(ContactId c : affected) callListeners(new ContactRemovedEvent(c));
-		callListeners(new LocalAuthorRemovedEvent(a));
+		for(ContactId c : affected)
+			eventBus.broadcast(new ContactRemovedEvent(c));
+		eventBus.broadcast(new LocalAuthorRemovedEvent(a));
 	}
 
 	public void removeTransport(TransportId t) throws DbException {
@@ -1411,7 +1398,7 @@ DatabaseCleaner.Callback {
 		} finally {
 			lock.writeLock().unlock();
 		}
-		callListeners(new TransportRemovedEvent(t));
+		eventBus.broadcast(new TransportRemovedEvent(t));
 	}
 
 	public void setConnectionWindow(ContactId c, TransportId t, long period,
@@ -1526,7 +1513,7 @@ DatabaseCleaner.Callback {
 			lock.writeLock().unlock();
 		}
 		if(!affected.isEmpty())
-			callListeners(new LocalSubscriptionsUpdatedEvent(affected));
+			eventBus.broadcast(new LocalSubscriptionsUpdatedEvent(affected));
 	}
 
 	public void setVisibleToAll(GroupId g, boolean all) throws DbException {
@@ -1559,7 +1546,7 @@ DatabaseCleaner.Callback {
 			lock.writeLock().unlock();
 		}
 		if(!affected.isEmpty())
-			callListeners(new LocalSubscriptionsUpdatedEvent(affected));
+			eventBus.broadcast(new LocalSubscriptionsUpdatedEvent(affected));
 	}
 
 	public void checkFreeSpaceAndClean() throws DbException {
@@ -1603,7 +1590,7 @@ DatabaseCleaner.Callback {
 			lock.writeLock().unlock();
 		}
 		if(expired.isEmpty()) return false;
-		callListeners(new MessageExpiredEvent());
+		eventBus.broadcast(new MessageExpiredEvent());
 		return true;
 	}
 
diff --git a/briar-core/src/org/briarproject/db/DatabaseModule.java b/briar-core/src/org/briarproject/db/DatabaseModule.java
index 11719acb602aeb80263927e876558a543c50d044..824b3e9dc6033d1527e0397b1fe3251b8428a841 100644
--- a/briar-core/src/org/briarproject/db/DatabaseModule.java
+++ b/briar-core/src/org/briarproject/db/DatabaseModule.java
@@ -15,6 +15,7 @@ import javax.inject.Singleton;
 import org.briarproject.api.db.DatabaseComponent;
 import org.briarproject.api.db.DatabaseConfig;
 import org.briarproject.api.db.DatabaseExecutor;
+import org.briarproject.api.event.EventBus;
 import org.briarproject.api.lifecycle.LifecycleManager;
 import org.briarproject.api.lifecycle.ShutdownManager;
 import org.briarproject.api.system.FileUtils;
@@ -38,6 +39,7 @@ public class DatabaseModule extends AbstractModule {
 				policy);
 	}
 
+	@Override
 	protected void configure() {
 		bind(DatabaseCleaner.class).to(DatabaseCleanerImpl.class);
 	}
@@ -50,8 +52,10 @@ public class DatabaseModule extends AbstractModule {
 
 	@Provides @Singleton
 	DatabaseComponent getDatabaseComponent(Database<Connection> db,
-			DatabaseCleaner cleaner, ShutdownManager shutdown) {
-		return new DatabaseComponentImpl<Connection>(db, cleaner, shutdown);
+			DatabaseCleaner cleaner, EventBus eventBus,
+			ShutdownManager shutdown) {
+		return new DatabaseComponentImpl<Connection>(db, cleaner, eventBus,
+				shutdown);
 	}
 
 	@Provides @Singleton @DatabaseExecutor
diff --git a/briar-core/src/org/briarproject/event/EventBusImpl.java b/briar-core/src/org/briarproject/event/EventBusImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..9db72310b663679a0c40f96e0c61ab2e1e5e25ae
--- /dev/null
+++ b/briar-core/src/org/briarproject/event/EventBusImpl.java
@@ -0,0 +1,26 @@
+package org.briarproject.event;
+
+import java.util.Collection;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import org.briarproject.api.event.Event;
+import org.briarproject.api.event.EventBus;
+import org.briarproject.api.event.EventListener;
+
+class EventBusImpl implements EventBus {
+
+	private final Collection<EventListener> listeners =
+			new CopyOnWriteArrayList<EventListener>();
+
+	public void addListener(EventListener l) {
+		listeners.add(l);
+	}
+
+	public void removeListener(EventListener l) {
+		listeners.remove(l);
+	}
+
+	public void broadcast(Event e) {
+		for(EventListener l : listeners) l.eventOccurred(e);
+	}
+}
diff --git a/briar-core/src/org/briarproject/event/EventModule.java b/briar-core/src/org/briarproject/event/EventModule.java
new file mode 100644
index 0000000000000000000000000000000000000000..34aa23eb66d4469a246648ac361557cb202232af
--- /dev/null
+++ b/briar-core/src/org/briarproject/event/EventModule.java
@@ -0,0 +1,14 @@
+package org.briarproject.event;
+
+import org.briarproject.api.event.EventBus;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.Singleton;
+
+public class EventModule extends AbstractModule {
+
+	@Override
+	protected void configure() {
+		bind(EventBus.class).to(EventBusImpl.class).in(Singleton.class);
+	}
+}
diff --git a/briar-core/src/org/briarproject/messaging/duplex/DuplexConnection.java b/briar-core/src/org/briarproject/messaging/duplex/DuplexConnection.java
index de6d569a5ecdc96fa803289fc1e928a9e68711b2..88c7fbbe82db3ea198c7786935b18e9da2bf4345 100644
--- a/briar-core/src/org/briarproject/messaging/duplex/DuplexConnection.java
+++ b/briar-core/src/org/briarproject/messaging/duplex/DuplexConnection.java
@@ -22,6 +22,7 @@ import org.briarproject.api.db.DatabaseComponent;
 import org.briarproject.api.db.DbException;
 import org.briarproject.api.event.ContactRemovedEvent;
 import org.briarproject.api.event.Event;
+import org.briarproject.api.event.EventBus;
 import org.briarproject.api.event.EventListener;
 import org.briarproject.api.event.LocalSubscriptionsUpdatedEvent;
 import org.briarproject.api.event.LocalTransportsUpdatedEvent;
@@ -72,6 +73,7 @@ abstract class DuplexConnection implements EventListener {
 	};
 
 	protected final DatabaseComponent db;
+	protected final EventBus eventBus;
 	protected final ConnectionRegistry connRegistry;
 	protected final ConnectionReaderFactory connReaderFactory;
 	protected final ConnectionWriterFactory connWriterFactory;
@@ -92,7 +94,7 @@ abstract class DuplexConnection implements EventListener {
 
 	DuplexConnection(Executor dbExecutor, Executor cryptoExecutor,
 			MessageVerifier messageVerifier, DatabaseComponent db,
-			ConnectionRegistry connRegistry,
+			EventBus eventBus, ConnectionRegistry connRegistry,
 			ConnectionReaderFactory connReaderFactory,
 			ConnectionWriterFactory connWriterFactory,
 			PacketReaderFactory packetReaderFactory,
@@ -102,6 +104,7 @@ abstract class DuplexConnection implements EventListener {
 		this.cryptoExecutor = cryptoExecutor;
 		this.messageVerifier = messageVerifier;
 		this.db = db;
+		this.eventBus = eventBus;
 		this.connRegistry = connRegistry;
 		this.connReaderFactory = connReaderFactory;
 		this.connWriterFactory = connWriterFactory;
@@ -218,7 +221,7 @@ abstract class DuplexConnection implements EventListener {
 
 	void write() {
 		connRegistry.registerConnection(contactId, transportId);
-		db.addListener(this);
+		eventBus.addListener(this);
 		try {
 			OutputStream out = createConnectionWriter().getOutputStream();
 			writer = packetWriterFactory.createPacketWriter(out, true);
@@ -260,7 +263,7 @@ abstract class DuplexConnection implements EventListener {
 			if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
 			dispose(true, true);
 		}
-		db.removeListener(this);
+		eventBus.removeListener(this);
 		connRegistry.unregisterConnection(contactId, transportId);
 	}
 
diff --git a/briar-core/src/org/briarproject/messaging/duplex/DuplexConnectionFactoryImpl.java b/briar-core/src/org/briarproject/messaging/duplex/DuplexConnectionFactoryImpl.java
index 94d14c0215cf4c0cb01cdc6363f495fa0da55d70..dfb71541dc0a838cbc1d99e2f4c303936796bbe5 100644
--- a/briar-core/src/org/briarproject/messaging/duplex/DuplexConnectionFactoryImpl.java
+++ b/briar-core/src/org/briarproject/messaging/duplex/DuplexConnectionFactoryImpl.java
@@ -11,6 +11,7 @@ import org.briarproject.api.crypto.CryptoExecutor;
 import org.briarproject.api.crypto.KeyManager;
 import org.briarproject.api.db.DatabaseComponent;
 import org.briarproject.api.db.DatabaseExecutor;
+import org.briarproject.api.event.EventBus;
 import org.briarproject.api.messaging.MessageVerifier;
 import org.briarproject.api.messaging.PacketReaderFactory;
 import org.briarproject.api.messaging.PacketWriterFactory;
@@ -29,6 +30,7 @@ class DuplexConnectionFactoryImpl implements DuplexConnectionFactory {
 	private final Executor dbExecutor, cryptoExecutor;
 	private final MessageVerifier messageVerifier;
 	private final DatabaseComponent db;
+	private final EventBus eventBus;
 	private final KeyManager keyManager;
 	private final ConnectionRegistry connRegistry;
 	private final ConnectionReaderFactory connReaderFactory;
@@ -40,7 +42,8 @@ class DuplexConnectionFactoryImpl implements DuplexConnectionFactory {
 	DuplexConnectionFactoryImpl(@DatabaseExecutor Executor dbExecutor,
 			@CryptoExecutor Executor cryptoExecutor,
 			MessageVerifier messageVerifier, DatabaseComponent db,
-			KeyManager keyManager, ConnectionRegistry connRegistry,
+			EventBus eventBus, KeyManager keyManager,
+			ConnectionRegistry connRegistry,
 			ConnectionReaderFactory connReaderFactory,
 			ConnectionWriterFactory connWriterFactory,
 			PacketReaderFactory packetReaderFactory,
@@ -49,6 +52,7 @@ class DuplexConnectionFactoryImpl implements DuplexConnectionFactory {
 		this.cryptoExecutor = cryptoExecutor;
 		this.messageVerifier = messageVerifier;
 		this.db = db;
+		this.eventBus = eventBus;
 		this.keyManager = keyManager;
 		this.connRegistry = connRegistry;
 		this.connReaderFactory = connReaderFactory;
@@ -60,7 +64,7 @@ class DuplexConnectionFactoryImpl implements DuplexConnectionFactory {
 	public void createIncomingConnection(ConnectionContext ctx,
 			DuplexTransportConnection transport) {
 		final DuplexConnection conn = new IncomingDuplexConnection(dbExecutor,
-				cryptoExecutor, messageVerifier, db, connRegistry,
+				cryptoExecutor, messageVerifier, db, eventBus, connRegistry,
 				connReaderFactory, connWriterFactory, packetReaderFactory,
 				packetWriterFactory, ctx, transport);
 		Runnable write = new Runnable() {
@@ -85,7 +89,7 @@ class DuplexConnectionFactoryImpl implements DuplexConnectionFactory {
 			return;
 		}
 		final DuplexConnection conn = new OutgoingDuplexConnection(dbExecutor,
-				cryptoExecutor, messageVerifier, db, connRegistry,
+				cryptoExecutor, messageVerifier, db, eventBus, connRegistry,
 				connReaderFactory, connWriterFactory, packetReaderFactory,
 				packetWriterFactory, ctx, transport);
 		Runnable write = new Runnable() {
diff --git a/briar-core/src/org/briarproject/messaging/duplex/IncomingDuplexConnection.java b/briar-core/src/org/briarproject/messaging/duplex/IncomingDuplexConnection.java
index 513c1821d2f270e44334cc3d3fbee1284730df77..ed63fe015f214d973036332960ded5b152ee60f0 100644
--- a/briar-core/src/org/briarproject/messaging/duplex/IncomingDuplexConnection.java
+++ b/briar-core/src/org/briarproject/messaging/duplex/IncomingDuplexConnection.java
@@ -6,6 +6,7 @@ import java.io.OutputStream;
 import java.util.concurrent.Executor;
 
 import org.briarproject.api.db.DatabaseComponent;
+import org.briarproject.api.event.EventBus;
 import org.briarproject.api.messaging.MessageVerifier;
 import org.briarproject.api.messaging.PacketReaderFactory;
 import org.briarproject.api.messaging.PacketWriterFactory;
@@ -21,15 +22,15 @@ class IncomingDuplexConnection extends DuplexConnection {
 
 	IncomingDuplexConnection(Executor dbExecutor, Executor cryptoExecutor,
 			MessageVerifier messageVerifier, DatabaseComponent db,
-			ConnectionRegistry connRegistry,
+			EventBus eventBus, ConnectionRegistry connRegistry,
 			ConnectionReaderFactory connReaderFactory,
 			ConnectionWriterFactory connWriterFactory,
 			PacketReaderFactory packetReaderFactory,
 			PacketWriterFactory packetWriterFactory,
 			ConnectionContext ctx, DuplexTransportConnection transport) {
-		super(dbExecutor, cryptoExecutor, messageVerifier, db, connRegistry,
-				connReaderFactory, connWriterFactory, packetReaderFactory,
-				packetWriterFactory, ctx, transport);
+		super(dbExecutor, cryptoExecutor, messageVerifier, db, eventBus,
+				connRegistry, connReaderFactory, connWriterFactory,
+				packetReaderFactory, packetWriterFactory, ctx, transport);
 	}
 
 	@Override
diff --git a/briar-core/src/org/briarproject/messaging/duplex/OutgoingDuplexConnection.java b/briar-core/src/org/briarproject/messaging/duplex/OutgoingDuplexConnection.java
index 8a3fadb0e885979ac0d94ec07334d8dc6a1ece9e..4305674c1645aed2addcd46adc7eb9497a4091bb 100644
--- a/briar-core/src/org/briarproject/messaging/duplex/OutgoingDuplexConnection.java
+++ b/briar-core/src/org/briarproject/messaging/duplex/OutgoingDuplexConnection.java
@@ -6,6 +6,7 @@ import java.io.OutputStream;
 import java.util.concurrent.Executor;
 
 import org.briarproject.api.db.DatabaseComponent;
+import org.briarproject.api.event.EventBus;
 import org.briarproject.api.messaging.MessageVerifier;
 import org.briarproject.api.messaging.PacketReaderFactory;
 import org.briarproject.api.messaging.PacketWriterFactory;
@@ -21,15 +22,15 @@ class OutgoingDuplexConnection extends DuplexConnection {
 
 	OutgoingDuplexConnection(Executor dbExecutor, Executor cryptoExecutor,
 			MessageVerifier messageVerifier, DatabaseComponent db,
-			ConnectionRegistry connRegistry,
+			EventBus eventBus, ConnectionRegistry connRegistry,
 			ConnectionReaderFactory connReaderFactory,
 			ConnectionWriterFactory connWriterFactory,
 			PacketReaderFactory packetReaderFactory,
 			PacketWriterFactory packetWriterFactory, ConnectionContext ctx,
 			DuplexTransportConnection transport) {
-		super(dbExecutor, cryptoExecutor, messageVerifier, db, connRegistry,
-				connReaderFactory, connWriterFactory, packetReaderFactory,
-				packetWriterFactory, ctx, transport);
+		super(dbExecutor, cryptoExecutor, messageVerifier, db, eventBus,
+				connRegistry, connReaderFactory, connWriterFactory,
+				packetReaderFactory, packetWriterFactory, ctx, transport);
 	}
 
 	@Override
diff --git a/briar-core/src/org/briarproject/transport/KeyManagerImpl.java b/briar-core/src/org/briarproject/transport/KeyManagerImpl.java
index 6bffe0f6275d2ba99df792ce0cf468fc4b65ef21..3b0ff81c89e6278aca91479254b68cd013f4245f 100644
--- a/briar-core/src/org/briarproject/transport/KeyManagerImpl.java
+++ b/briar-core/src/org/briarproject/transport/KeyManagerImpl.java
@@ -23,6 +23,7 @@ import org.briarproject.api.db.DatabaseComponent;
 import org.briarproject.api.db.DbException;
 import org.briarproject.api.event.ContactRemovedEvent;
 import org.briarproject.api.event.Event;
+import org.briarproject.api.event.EventBus;
 import org.briarproject.api.event.EventListener;
 import org.briarproject.api.event.TransportAddedEvent;
 import org.briarproject.api.event.TransportRemovedEvent;
@@ -44,6 +45,7 @@ class KeyManagerImpl extends TimerTask implements KeyManager, EventListener {
 
 	private final CryptoComponent crypto;
 	private final DatabaseComponent db;
+	private final EventBus eventBus;
 	private final ConnectionRecogniser connectionRecogniser;
 	private final Clock clock;
 	private final Timer timer;
@@ -56,10 +58,11 @@ class KeyManagerImpl extends TimerTask implements KeyManager, EventListener {
 
 	@Inject
 	KeyManagerImpl(CryptoComponent crypto, DatabaseComponent db,
-			ConnectionRecogniser connectionRecogniser, Clock clock,
-			Timer timer) {
+			EventBus eventBus, ConnectionRecogniser connectionRecogniser,
+			Clock clock, Timer timer) {
 		this.crypto = crypto;
 		this.db = db;
+		this.eventBus = eventBus;
 		this.connectionRecogniser = connectionRecogniser;
 		this.clock = clock;
 		this.timer = timer;
@@ -70,7 +73,7 @@ class KeyManagerImpl extends TimerTask implements KeyManager, EventListener {
 	}
 
 	public synchronized boolean start() {
-		db.addListener(this);
+		eventBus.addListener(this);
 		// Load the temporary secrets and transport latencies from the database
 		Collection<TemporarySecret> secrets;
 		try {
@@ -213,7 +216,7 @@ class KeyManagerImpl extends TimerTask implements KeyManager, EventListener {
 	}
 
 	public synchronized boolean stop() {
-		db.removeListener(this);
+		eventBus.removeListener(this);
 		timer.cancel();
 		connectionRecogniser.removeSecrets();
 		maxLatencies.clear();
@@ -290,6 +293,7 @@ class KeyManagerImpl extends TimerTask implements KeyManager, EventListener {
 		connectionRecogniser.addSecret(s3);
 	}
 
+	@Override
 	public synchronized void run() {
 		// Rebuild the maps because we may be running a whole period late
 		Collection<TemporarySecret> secrets = new ArrayList<TemporarySecret>();
@@ -399,6 +403,7 @@ class KeyManagerImpl extends TimerTask implements KeyManager, EventListener {
 			this.event = event;
 		}
 
+		@Override
 		public void run() {
 			ContactId c = event.getContactId();
 			connectionRecogniser.removeSecrets(c);
@@ -418,6 +423,7 @@ class KeyManagerImpl extends TimerTask implements KeyManager, EventListener {
 			this.event = event;
 		}
 
+		@Override
 		public void run() {
 			synchronized(KeyManagerImpl.this) {
 				maxLatencies.put(event.getTransportId(), event.getMaxLatency());
@@ -433,6 +439,7 @@ class KeyManagerImpl extends TimerTask implements KeyManager, EventListener {
 			this.event = event;
 		}
 
+		@Override
 		public void run() {
 			TransportId t = event.getTransportId();
 			connectionRecogniser.removeSecrets(t);
diff --git a/briar-tests/src/org/briarproject/ProtocolIntegrationTest.java b/briar-tests/src/org/briarproject/ProtocolIntegrationTest.java
index a32c463a89af1eb78901b8e88e562e7fdd6864ae..6441b83e41da2d833e688a4110eeb5c637ec9412 100644
--- a/briar-tests/src/org/briarproject/ProtocolIntegrationTest.java
+++ b/briar-tests/src/org/briarproject/ProtocolIntegrationTest.java
@@ -43,6 +43,7 @@ import org.briarproject.api.transport.ConnectionWriter;
 import org.briarproject.api.transport.ConnectionWriterFactory;
 import org.briarproject.crypto.CryptoModule;
 import org.briarproject.db.DatabaseModule;
+import org.briarproject.event.EventModule;
 import org.briarproject.messaging.MessagingModule;
 import org.briarproject.messaging.duplex.DuplexMessagingModule;
 import org.briarproject.messaging.simplex.SimplexMessagingModule;
@@ -79,9 +80,10 @@ public class ProtocolIntegrationTest extends BriarTestCase {
 		Injector i = Guice.createInjector(new TestDatabaseModule(),
 				new TestLifecycleModule(), new TestSystemModule(),
 				new TestUiModule(), new CryptoModule(), new DatabaseModule(),
-				new MessagingModule(), new DuplexMessagingModule(),
-				new SimplexMessagingModule(), new ReliabilityModule(),
-				new SerialModule(), new TransportModule());
+				new EventModule(), new MessagingModule(),
+				new DuplexMessagingModule(), new SimplexMessagingModule(),
+				new ReliabilityModule(), new SerialModule(),
+				new TransportModule());
 		connectionReaderFactory = i.getInstance(ConnectionReaderFactory.class);
 		connectionWriterFactory = i.getInstance(ConnectionWriterFactory.class);
 		packetReaderFactory = i.getInstance(PacketReaderFactory.class);
diff --git a/briar-tests/src/org/briarproject/db/DatabaseComponentImplTest.java b/briar-tests/src/org/briarproject/db/DatabaseComponentImplTest.java
index f1906dcdf547cab6dd479c17a128040dcaaebc8e..f694e90c7cdfd21f573780a957fa957eb90603c6 100644
--- a/briar-tests/src/org/briarproject/db/DatabaseComponentImplTest.java
+++ b/briar-tests/src/org/briarproject/db/DatabaseComponentImplTest.java
@@ -7,6 +7,7 @@ import java.util.Collections;
 
 import org.briarproject.api.db.DatabaseComponent;
 import org.briarproject.api.db.DbException;
+import org.briarproject.api.event.EventBus;
 import org.briarproject.api.lifecycle.ShutdownManager;
 import org.briarproject.db.DatabaseCleaner.Callback;
 import org.jmock.Expectations;
@@ -26,11 +27,13 @@ public class DatabaseComponentImplTest extends DatabaseComponentTest {
 		final Database<Object> database = context.mock(Database.class);
 		final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
 		final ShutdownManager shutdown = context.mock(ShutdownManager.class);
+		final EventBus eventBus = context.mock(EventBus.class);
 		context.checking(new Expectations() {{
 			oneOf(database).getFreeSpace();
 			will(returnValue(MIN_FREE_SPACE));
 		}});
-		Callback db = createDatabaseComponentImpl(database, cleaner, shutdown);
+		Callback db = createDatabaseComponentImpl(database, cleaner, eventBus,
+				shutdown);
 
 		db.checkFreeSpaceAndClean();
 
@@ -44,6 +47,7 @@ public class DatabaseComponentImplTest extends DatabaseComponentTest {
 		final Database<Object> database = context.mock(Database.class);
 		final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
 		final ShutdownManager shutdown = context.mock(ShutdownManager.class);
+		final EventBus eventBus = context.mock(EventBus.class);
 		context.checking(new Expectations() {{
 			oneOf(database).getFreeSpace();
 			will(returnValue(MIN_FREE_SPACE - 1));
@@ -56,7 +60,8 @@ public class DatabaseComponentImplTest extends DatabaseComponentTest {
 			oneOf(database).getFreeSpace();
 			will(returnValue(MIN_FREE_SPACE));
 		}});
-		Callback db = createDatabaseComponentImpl(database, cleaner, shutdown);
+		Callback db = createDatabaseComponentImpl(database, cleaner, eventBus,
+				shutdown);
 
 		db.checkFreeSpaceAndClean();
 
@@ -65,14 +70,16 @@ public class DatabaseComponentImplTest extends DatabaseComponentTest {
 
 	@Override
 	protected <T> DatabaseComponent createDatabaseComponent(
-			Database<T> database, DatabaseCleaner cleaner,
+			Database<T> database, DatabaseCleaner cleaner, EventBus eventBus,
 			ShutdownManager shutdown) {
-		return createDatabaseComponentImpl(database, cleaner, shutdown);
+		return createDatabaseComponentImpl(database, cleaner, eventBus,
+				shutdown);
 	}
 
 	private <T> DatabaseComponentImpl<T> createDatabaseComponentImpl(
-			Database<T> database, DatabaseCleaner cleaner,
+			Database<T> database, DatabaseCleaner cleaner, EventBus eventBus,
 			ShutdownManager shutdown) {
-		return new DatabaseComponentImpl<T>(database, cleaner, shutdown);
+		return new DatabaseComponentImpl<T>(database, cleaner, eventBus,
+				shutdown);
 	}
 }
diff --git a/briar-tests/src/org/briarproject/db/DatabaseComponentTest.java b/briar-tests/src/org/briarproject/db/DatabaseComponentTest.java
index 98d2b8f97a52637f5a9a53dd469b38fd14a52e49..7bb0c12ce7a8951eca492738bb79b0e26d48175e 100644
--- a/briar-tests/src/org/briarproject/db/DatabaseComponentTest.java
+++ b/briar-tests/src/org/briarproject/db/DatabaseComponentTest.java
@@ -26,14 +26,16 @@ import org.briarproject.api.db.NoSuchSubscriptionException;
 import org.briarproject.api.db.NoSuchTransportException;
 import org.briarproject.api.event.ContactAddedEvent;
 import org.briarproject.api.event.ContactRemovedEvent;
-import org.briarproject.api.event.EventListener;
+import org.briarproject.api.event.EventBus;
 import org.briarproject.api.event.LocalAuthorAddedEvent;
 import org.briarproject.api.event.LocalAuthorRemovedEvent;
 import org.briarproject.api.event.LocalSubscriptionsUpdatedEvent;
 import org.briarproject.api.event.LocalTransportsUpdatedEvent;
 import org.briarproject.api.event.MessageAddedEvent;
+import org.briarproject.api.event.MessageRequestedEvent;
 import org.briarproject.api.event.MessageToAckEvent;
 import org.briarproject.api.event.MessageToRequestEvent;
+import org.briarproject.api.event.MessagesAckedEvent;
 import org.briarproject.api.event.SubscriptionAddedEvent;
 import org.briarproject.api.event.SubscriptionRemovedEvent;
 import org.briarproject.api.lifecycle.ShutdownManager;
@@ -52,7 +54,6 @@ import org.briarproject.api.messaging.TransportAck;
 import org.briarproject.api.messaging.TransportUpdate;
 import org.briarproject.api.transport.Endpoint;
 import org.briarproject.api.transport.TemporarySecret;
-
 import org.jmock.Expectations;
 import org.jmock.Mockery;
 import org.junit.Test;
@@ -109,7 +110,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 	}
 
 	protected abstract <T> DatabaseComponent createDatabaseComponent(
-			Database<T> database, DatabaseCleaner cleaner,
+			Database<T> database, DatabaseCleaner cleaner, EventBus eventBus,
 			ShutdownManager shutdown);
 
 	@Test
@@ -120,7 +121,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 		final Database<Object> database = context.mock(Database.class);
 		final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
 		final ShutdownManager shutdown = context.mock(ShutdownManager.class);
-		final EventListener listener = context.mock(EventListener.class);
+		final EventBus eventBus = context.mock(EventBus.class);
 		context.checking(new Expectations() {{
 			exactly(11).of(database).startTransaction();
 			will(returnValue(txn));
@@ -137,8 +138,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 			oneOf(database).containsLocalAuthor(txn, localAuthorId);
 			will(returnValue(false));
 			oneOf(database).addLocalAuthor(txn, localAuthor);
-			oneOf(listener).eventOccurred(with(any(
-					LocalAuthorAddedEvent.class)));
+			oneOf(eventBus).broadcast(with(any(LocalAuthorAddedEvent.class)));
 			// addContact()
 			oneOf(database).containsContact(txn, authorId);
 			will(returnValue(false));
@@ -146,7 +146,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 			will(returnValue(true));
 			oneOf(database).addContact(txn, author, localAuthorId);
 			will(returnValue(contactId));
-			oneOf(listener).eventOccurred(with(any(ContactAddedEvent.class)));
+			oneOf(eventBus).broadcast(with(any(ContactAddedEvent.class)));
 			// getContacts()
 			oneOf(database).getContacts(txn);
 			will(returnValue(Arrays.asList(contact)));
@@ -158,8 +158,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 			will(returnValue(false));
 			oneOf(database).addGroup(txn, group);
 			will(returnValue(true));
-			oneOf(listener).eventOccurred(with(any(
-					SubscriptionAddedEvent.class)));
+			oneOf(eventBus).broadcast(with(any(SubscriptionAddedEvent.class)));
 			// addGroup() again
 			oneOf(database).containsGroup(txn, groupId);
 			will(returnValue(true));
@@ -177,9 +176,9 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 			oneOf(database).getVisibility(txn, groupId);
 			will(returnValue(Collections.emptyList()));
 			oneOf(database).removeGroup(txn, groupId);
-			oneOf(listener).eventOccurred(with(any(
+			oneOf(eventBus).broadcast(with(any(
 					SubscriptionRemovedEvent.class)));
-			oneOf(listener).eventOccurred(with(any(
+			oneOf(eventBus).broadcast(with(any(
 					LocalSubscriptionsUpdatedEvent.class)));
 			// removeContact()
 			oneOf(database).containsContact(txn, contactId);
@@ -187,25 +186,23 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 			oneOf(database).getInboxGroupId(txn, contactId);
 			will(returnValue(null));
 			oneOf(database).removeContact(txn, contactId);
-			oneOf(listener).eventOccurred(with(any(ContactRemovedEvent.class)));
+			oneOf(eventBus).broadcast(with(any(ContactRemovedEvent.class)));
 			// removeLocalAuthor()
 			oneOf(database).containsLocalAuthor(txn, localAuthorId);
 			will(returnValue(true));
 			oneOf(database).getContacts(txn, localAuthorId);
 			will(returnValue(Collections.emptyList()));
 			oneOf(database).removeLocalAuthor(txn, localAuthorId);
-			oneOf(listener).eventOccurred(with(any(
-					LocalAuthorRemovedEvent.class)));
+			oneOf(eventBus).broadcast(with(any(LocalAuthorRemovedEvent.class)));
 			// close()
 			oneOf(shutdown).removeShutdownHook(shutdownHandle);
 			oneOf(cleaner).stopCleaning();
 			oneOf(database).close();
 		}});
 		DatabaseComponent db = createDatabaseComponent(database, cleaner,
-				shutdown);
+				eventBus, shutdown);
 
 		assertFalse(db.open());
-		db.addListener(listener);
 		db.addLocalAuthor(localAuthor);
 		assertEquals(contactId, db.addContact(author, localAuthorId));
 		assertEquals(Arrays.asList(contact), db.getContacts());
@@ -218,7 +215,6 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 		db.removeGroup(group);
 		db.removeContact(contactId);
 		db.removeLocalAuthor(localAuthorId);
-		db.removeListener(listener);
 		db.close();
 
 		context.assertIsSatisfied();
@@ -231,15 +227,18 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 		final Database<Object> database = context.mock(Database.class);
 		final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
 		final ShutdownManager shutdown = context.mock(ShutdownManager.class);
+		final EventBus eventBus = context.mock(EventBus.class);
 		context.checking(new Expectations() {{
 			oneOf(database).startTransaction();
 			will(returnValue(txn));
 			oneOf(database).containsMessage(txn, messageId);
 			will(returnValue(true));
+			oneOf(database).containsGroup(txn, groupId);
+			will(returnValue(true));
 			oneOf(database).commitTransaction(txn);
 		}});
 		DatabaseComponent db = createDatabaseComponent(database, cleaner,
-				shutdown);
+				eventBus, shutdown);
 
 		db.addLocalMessage(message);
 
@@ -254,6 +253,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 		final Database<Object> database = context.mock(Database.class);
 		final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
 		final ShutdownManager shutdown = context.mock(ShutdownManager.class);
+		final EventBus eventBus = context.mock(EventBus.class);
 		context.checking(new Expectations() {{
 			oneOf(database).startTransaction();
 			will(returnValue(txn));
@@ -264,7 +264,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 			oneOf(database).commitTransaction(txn);
 		}});
 		DatabaseComponent db = createDatabaseComponent(database, cleaner,
-				shutdown);
+				eventBus, shutdown);
 
 		db.addLocalMessage(message);
 
@@ -278,7 +278,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 		final Database<Object> database = context.mock(Database.class);
 		final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
 		final ShutdownManager shutdown = context.mock(ShutdownManager.class);
-		final EventListener listener = context.mock(EventListener.class);
+		final EventBus eventBus = context.mock(EventBus.class);
 		context.checking(new Expectations() {{
 			oneOf(database).startTransaction();
 			will(returnValue(txn));
@@ -297,13 +297,11 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 			oneOf(database).addStatus(txn, contactId, messageId, false, false);
 			oneOf(database).commitTransaction(txn);
 			// The message was added, so the listener should be called
-			oneOf(listener).eventOccurred(with(any(
-					MessageAddedEvent.class)));
+			oneOf(eventBus).broadcast(with(any(MessageAddedEvent.class)));
 		}});
 		DatabaseComponent db = createDatabaseComponent(database, cleaner,
-				shutdown);
+				eventBus, shutdown);
 
-		db.addListener(listener);
 		db.addLocalMessage(message);
 
 		context.assertIsSatisfied();
@@ -317,6 +315,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 		final Database<Object> database = context.mock(Database.class);
 		final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
 		final ShutdownManager shutdown = context.mock(ShutdownManager.class);
+		final EventBus eventBus = context.mock(EventBus.class);
 		context.checking(new Expectations() {{
 			// Check whether the contact is in the DB (which it's not)
 			exactly(25).of(database).startTransaction();
@@ -326,7 +325,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 			exactly(25).of(database).abortTransaction(txn);
 		}});
 		DatabaseComponent db = createDatabaseComponent(database, cleaner,
-				shutdown);
+				eventBus, shutdown);
 
 		try {
 			db.addEndpoint(endpoint);
@@ -474,6 +473,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 		final Database<Object> database = context.mock(Database.class);
 		final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
 		final ShutdownManager shutdown = context.mock(ShutdownManager.class);
+		final EventBus eventBus = context.mock(EventBus.class);
 		context.checking(new Expectations() {{
 			// Check whether the pseudonym is in the DB (which it's not)
 			exactly(3).of(database).startTransaction();
@@ -486,7 +486,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 			will(returnValue(false));
 		}});
 		DatabaseComponent db = createDatabaseComponent(database, cleaner,
-				shutdown);
+				eventBus, shutdown);
 
 		try {
 			db.addContact(author, localAuthorId);
@@ -514,6 +514,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 		final Database<Object> database = context.mock(Database.class);
 		final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
 		final ShutdownManager shutdown = context.mock(ShutdownManager.class);
+		final EventBus eventBus = context.mock(EventBus.class);
 		context.checking(new Expectations() {{
 			// Check whether the subscription is in the DB (which it's not)
 			exactly(5).of(database).startTransaction();
@@ -523,7 +524,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 			exactly(5).of(database).abortTransaction(txn);
 		}});
 		DatabaseComponent db = createDatabaseComponent(database, cleaner,
-				shutdown);
+				eventBus, shutdown);
 
 		try {
 			db.getGroup(groupId);
@@ -561,6 +562,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 		final Database<Object> database = context.mock(Database.class);
 		final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
 		final ShutdownManager shutdown = context.mock(ShutdownManager.class);
+		final EventBus eventBus = context.mock(EventBus.class);
 		context.checking(new Expectations() {{
 			// addLocalAuthor()
 			oneOf(database).startTransaction();
@@ -569,6 +571,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 			will(returnValue(false));
 			oneOf(database).addLocalAuthor(txn, localAuthor);
 			oneOf(database).commitTransaction(txn);
+			oneOf(eventBus).broadcast(with(any(LocalAuthorAddedEvent.class)));
 			// addContact()
 			oneOf(database).startTransaction();
 			will(returnValue(txn));
@@ -579,6 +582,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 			oneOf(database).addContact(txn, author, localAuthorId);
 			will(returnValue(contactId));
 			oneOf(database).commitTransaction(txn);
+			oneOf(eventBus).broadcast(with(any(ContactAddedEvent.class)));
 			// Check whether the transport is in the DB (which it's not)
 			exactly(8).of(database).startTransaction();
 			will(returnValue(txn));
@@ -589,7 +593,8 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 			exactly(8).of(database).abortTransaction(txn);
 		}});
 		DatabaseComponent db = createDatabaseComponent(database, cleaner,
-				shutdown);
+				eventBus, shutdown);
+
 		db.addLocalAuthor(localAuthor);
 		assertEquals(contactId, db.addContact(author, localAuthorId));
 
@@ -645,6 +650,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 		final Database<Object> database = context.mock(Database.class);
 		final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
 		final ShutdownManager shutdown = context.mock(ShutdownManager.class);
+		final EventBus eventBus = context.mock(EventBus.class);
 		context.checking(new Expectations() {{
 			oneOf(database).startTransaction();
 			will(returnValue(txn));
@@ -656,7 +662,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 			oneOf(database).commitTransaction(txn);
 		}});
 		DatabaseComponent db = createDatabaseComponent(database, cleaner,
-				shutdown);
+				eventBus, shutdown);
 
 		Ack a = db.generateAck(contactId, 123);
 		assertEquals(messagesToAck, a.getMessageIds());
@@ -674,6 +680,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 		final Database<Object> database = context.mock(Database.class);
 		final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
 		final ShutdownManager shutdown = context.mock(ShutdownManager.class);
+		final EventBus eventBus = context.mock(EventBus.class);
 		context.checking(new Expectations() {{
 			oneOf(database).startTransaction();
 			will(returnValue(txn));
@@ -693,7 +700,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 			oneOf(database).commitTransaction(txn);
 		}});
 		DatabaseComponent db = createDatabaseComponent(database, cleaner,
-				shutdown);
+				eventBus, shutdown);
 
 		assertEquals(messages, db.generateBatch(contactId, size * 2,
 				Long.MAX_VALUE));
@@ -710,6 +717,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 		final Database<Object> database = context.mock(Database.class);
 		final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
 		final ShutdownManager shutdown = context.mock(ShutdownManager.class);
+		final EventBus eventBus = context.mock(EventBus.class);
 		context.checking(new Expectations() {{
 			oneOf(database).startTransaction();
 			will(returnValue(txn));
@@ -724,7 +732,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 			oneOf(database).commitTransaction(txn);
 		}});
 		DatabaseComponent db = createDatabaseComponent(database, cleaner,
-				shutdown);
+				eventBus, shutdown);
 
 		Offer o = db.generateOffer(contactId, 123, Long.MAX_VALUE);
 		assertEquals(ids, o.getMessageIds());
@@ -741,6 +749,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 		final Database<Object> database = context.mock(Database.class);
 		final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
 		final ShutdownManager shutdown = context.mock(ShutdownManager.class);
+		final EventBus eventBus = context.mock(EventBus.class);
 		context.checking(new Expectations() {{
 			oneOf(database).startTransaction();
 			will(returnValue(txn));
@@ -752,7 +761,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 			oneOf(database).commitTransaction(txn);
 		}});
 		DatabaseComponent db = createDatabaseComponent(database, cleaner,
-				shutdown);
+				eventBus, shutdown);
 
 		Request r = db.generateRequest(contactId, 123);
 		assertEquals(ids, r.getMessageIds());
@@ -770,6 +779,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 		final Database<Object> database = context.mock(Database.class);
 		final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
 		final ShutdownManager shutdown = context.mock(ShutdownManager.class);
+		final EventBus eventBus = context.mock(EventBus.class);
 		context.checking(new Expectations() {{
 			oneOf(database).startTransaction();
 			will(returnValue(txn));
@@ -790,7 +800,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 			oneOf(database).commitTransaction(txn);
 		}});
 		DatabaseComponent db = createDatabaseComponent(database, cleaner,
-				shutdown);
+				eventBus, shutdown);
 
 		assertEquals(messages, db.generateRequestedBatch(contactId, size * 2,
 				Long.MAX_VALUE));
@@ -805,6 +815,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 		final Database<Object> database = context.mock(Database.class);
 		final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
 		final ShutdownManager shutdown = context.mock(ShutdownManager.class);
+		final EventBus eventBus = context.mock(EventBus.class);
 		context.checking(new Expectations() {{
 			oneOf(database).startTransaction();
 			will(returnValue(txn));
@@ -815,7 +826,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 			oneOf(database).commitTransaction(txn);
 		}});
 		DatabaseComponent db = createDatabaseComponent(database, cleaner,
-				shutdown);
+				eventBus, shutdown);
 
 		assertNull(db.generateRetentionUpdate(contactId, Long.MAX_VALUE));
 
@@ -829,6 +840,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 		final Database<Object> database = context.mock(Database.class);
 		final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
 		final ShutdownManager shutdown = context.mock(ShutdownManager.class);
+		final EventBus eventBus = context.mock(EventBus.class);
 		context.checking(new Expectations() {{
 			oneOf(database).startTransaction();
 			will(returnValue(txn));
@@ -839,7 +851,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 			oneOf(database).commitTransaction(txn);
 		}});
 		DatabaseComponent db = createDatabaseComponent(database, cleaner,
-				shutdown);
+				eventBus, shutdown);
 
 		RetentionUpdate u = db.generateRetentionUpdate(contactId,
 				Long.MAX_VALUE);
@@ -856,6 +868,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 		final Database<Object> database = context.mock(Database.class);
 		final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
 		final ShutdownManager shutdown = context.mock(ShutdownManager.class);
+		final EventBus eventBus = context.mock(EventBus.class);
 		context.checking(new Expectations() {{
 			oneOf(database).startTransaction();
 			will(returnValue(txn));
@@ -867,7 +880,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 			oneOf(database).commitTransaction(txn);
 		}});
 		DatabaseComponent db = createDatabaseComponent(database, cleaner,
-				shutdown);
+				eventBus, shutdown);
 
 		assertNull(db.generateSubscriptionUpdate(contactId, Long.MAX_VALUE));
 
@@ -881,6 +894,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 		final Database<Object> database = context.mock(Database.class);
 		final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
 		final ShutdownManager shutdown = context.mock(ShutdownManager.class);
+		final EventBus eventBus = context.mock(EventBus.class);
 		context.checking(new Expectations() {{
 			oneOf(database).startTransaction();
 			will(returnValue(txn));
@@ -892,7 +906,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 			oneOf(database).commitTransaction(txn);
 		}});
 		DatabaseComponent db = createDatabaseComponent(database, cleaner,
-				shutdown);
+				eventBus, shutdown);
 
 		SubscriptionUpdate u = db.generateSubscriptionUpdate(contactId,
 				Long.MAX_VALUE);
@@ -909,6 +923,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 		final Database<Object> database = context.mock(Database.class);
 		final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
 		final ShutdownManager shutdown = context.mock(ShutdownManager.class);
+		final EventBus eventBus = context.mock(EventBus.class);
 		context.checking(new Expectations() {{
 			oneOf(database).startTransaction();
 			will(returnValue(txn));
@@ -919,7 +934,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 			oneOf(database).commitTransaction(txn);
 		}});
 		DatabaseComponent db = createDatabaseComponent(database, cleaner,
-				shutdown);
+				eventBus, shutdown);
 
 		assertNull(db.generateTransportUpdates(contactId, Long.MAX_VALUE));
 
@@ -933,6 +948,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 		final Database<Object> database = context.mock(Database.class);
 		final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
 		final ShutdownManager shutdown = context.mock(ShutdownManager.class);
+		final EventBus eventBus = context.mock(EventBus.class);
 		context.checking(new Expectations() {{
 			oneOf(database).startTransaction();
 			will(returnValue(txn));
@@ -944,7 +960,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 			oneOf(database).commitTransaction(txn);
 		}});
 		DatabaseComponent db = createDatabaseComponent(database, cleaner,
-				shutdown);
+				eventBus, shutdown);
 
 		Collection<TransportUpdate> updates =
 				db.generateTransportUpdates(contactId, Long.MAX_VALUE);
@@ -965,6 +981,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 		final Database<Object> database = context.mock(Database.class);
 		final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
 		final ShutdownManager shutdown = context.mock(ShutdownManager.class);
+		final EventBus eventBus = context.mock(EventBus.class);
 		context.checking(new Expectations() {{
 			oneOf(database).startTransaction();
 			will(returnValue(txn));
@@ -974,9 +991,10 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 			will(returnValue(true));
 			oneOf(database).raiseSeenFlag(txn, contactId, messageId);
 			oneOf(database).commitTransaction(txn);
+			oneOf(eventBus).broadcast(with(any(MessagesAckedEvent.class)));
 		}});
 		DatabaseComponent db = createDatabaseComponent(database, cleaner,
-				shutdown);
+				eventBus, shutdown);
 
 		db.receiveAck(contactId, new Ack(Arrays.asList(messageId)));
 
@@ -990,7 +1008,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 		final Database<Object> database = context.mock(Database.class);
 		final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
 		final ShutdownManager shutdown = context.mock(ShutdownManager.class);
-		final EventListener listener = context.mock(EventListener.class);
+		final EventBus eventBus = context.mock(EventBus.class);
 		context.checking(new Expectations() {{
 			oneOf(database).startTransaction();
 			will(returnValue(txn));
@@ -1011,14 +1029,12 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 			oneOf(database).raiseAckFlag(txn, contactId, messageId);
 			oneOf(database).commitTransaction(txn);
 			// The message was received and added
-			oneOf(listener).eventOccurred(with(any(
-					MessageToAckEvent.class)));
-			oneOf(listener).eventOccurred(with(any(MessageAddedEvent.class)));
+			oneOf(eventBus).broadcast(with(any(MessageToAckEvent.class)));
+			oneOf(eventBus).broadcast(with(any(MessageAddedEvent.class)));
 		}});
 		DatabaseComponent db = createDatabaseComponent(database, cleaner,
-				shutdown);
+				eventBus, shutdown);
 
-		db.addListener(listener);
 		db.receiveMessage(contactId, message);
 
 		context.assertIsSatisfied();
@@ -1031,7 +1047,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 		final Database<Object> database = context.mock(Database.class);
 		final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
 		final ShutdownManager shutdown = context.mock(ShutdownManager.class);
-		final EventListener listener = context.mock(EventListener.class);
+		final EventBus eventBus = context.mock(EventBus.class);
 		context.checking(new Expectations() {{
 			oneOf(database).startTransaction();
 			will(returnValue(txn));
@@ -1045,13 +1061,11 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 			oneOf(database).raiseAckFlag(txn, contactId, messageId);
 			oneOf(database).commitTransaction(txn);
 			// The message was received but not added
-			oneOf(listener).eventOccurred(with(any(
-					MessageToAckEvent.class)));
+			oneOf(eventBus).broadcast(with(any(MessageToAckEvent.class)));
 		}});
 		DatabaseComponent db = createDatabaseComponent(database, cleaner,
-				shutdown);
+				eventBus, shutdown);
 
-		db.addListener(listener);
 		db.receiveMessage(contactId, message);
 
 		context.assertIsSatisfied();
@@ -1064,6 +1078,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 		final Database<Object> database = context.mock(Database.class);
 		final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
 		final ShutdownManager shutdown = context.mock(ShutdownManager.class);
+		final EventBus eventBus = context.mock(EventBus.class);
 		context.checking(new Expectations() {{
 			oneOf(database).startTransaction();
 			will(returnValue(txn));
@@ -1076,7 +1091,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 			oneOf(database).commitTransaction(txn);
 		}});
 		DatabaseComponent db = createDatabaseComponent(database, cleaner,
-				shutdown);
+				eventBus, shutdown);
 
 		db.receiveMessage(contactId, message);
 
@@ -1093,7 +1108,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 		final Database<Object> database = context.mock(Database.class);
 		final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
 		final ShutdownManager shutdown = context.mock(ShutdownManager.class);
-		final EventListener listener = context.mock(EventListener.class);
+		final EventBus eventBus = context.mock(EventBus.class);
 		context.checking(new Expectations() {{
 			oneOf(database).startTransaction();
 			will(returnValue(txn));
@@ -1119,14 +1134,12 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 			oneOf(database).containsVisibleMessage(txn, contactId, messageId3);
 			will(returnValue(false));
 			oneOf(database).commitTransaction(txn);
-			oneOf(listener).eventOccurred(with(any(MessageToAckEvent.class)));
-			oneOf(listener).eventOccurred(with(any(
-					MessageToRequestEvent.class)));
+			oneOf(eventBus).broadcast(with(any(MessageToAckEvent.class)));
+			oneOf(eventBus).broadcast(with(any(MessageToRequestEvent.class)));
 		}});
 		DatabaseComponent db = createDatabaseComponent(database, cleaner,
-				shutdown);
+				eventBus, shutdown);
 
-		db.addListener(listener);
 		Offer o = new Offer(Arrays.asList(messageId, messageId1, messageId2,
 				messageId3));
 		db.receiveOffer(contactId, o);
@@ -1140,6 +1153,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 		final Database<Object> database = context.mock(Database.class);
 		final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
 		final ShutdownManager shutdown = context.mock(ShutdownManager.class);
+		final EventBus eventBus = context.mock(EventBus.class);
 		context.checking(new Expectations() {{
 			oneOf(database).startTransaction();
 			will(returnValue(txn));
@@ -1150,9 +1164,10 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 			oneOf(database).raiseRequestedFlag(txn, contactId, messageId);
 			oneOf(database).resetExpiryTime(txn, contactId, messageId);
 			oneOf(database).commitTransaction(txn);
+			oneOf(eventBus).broadcast(with(any(MessageRequestedEvent.class)));
 		}});
 		DatabaseComponent db = createDatabaseComponent(database, cleaner,
-				shutdown);
+				eventBus, shutdown);
 
 		db.receiveRequest(contactId, new Request(Arrays.asList(messageId)));
 
@@ -1166,6 +1181,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 		final Database<Object> database = context.mock(Database.class);
 		final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
 		final ShutdownManager shutdown = context.mock(ShutdownManager.class);
+		final EventBus eventBus = context.mock(EventBus.class);
 		context.checking(new Expectations() {{
 			oneOf(database).startTransaction();
 			will(returnValue(txn));
@@ -1175,7 +1191,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 			oneOf(database).commitTransaction(txn);
 		}});
 		DatabaseComponent db = createDatabaseComponent(database, cleaner,
-				shutdown);
+				eventBus, shutdown);
 
 		RetentionAck a = new RetentionAck(1);
 		db.receiveRetentionAck(contactId, a);
@@ -1190,6 +1206,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 		final Database<Object> database = context.mock(Database.class);
 		final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
 		final ShutdownManager shutdown = context.mock(ShutdownManager.class);
+		final EventBus eventBus = context.mock(EventBus.class);
 		context.checking(new Expectations() {{
 			oneOf(database).startTransaction();
 			will(returnValue(txn));
@@ -1199,7 +1216,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 			oneOf(database).commitTransaction(txn);
 		}});
 		DatabaseComponent db = createDatabaseComponent(database, cleaner,
-				shutdown);
+				eventBus, shutdown);
 
 		SubscriptionAck a = new SubscriptionAck(1);
 		db.receiveSubscriptionAck(contactId, a);
@@ -1214,6 +1231,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 		final Database<Object> database = context.mock(Database.class);
 		final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
 		final ShutdownManager shutdown = context.mock(ShutdownManager.class);
+		final EventBus eventBus = context.mock(EventBus.class);
 		context.checking(new Expectations() {{
 			oneOf(database).startTransaction();
 			will(returnValue(txn));
@@ -1223,7 +1241,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 			oneOf(database).commitTransaction(txn);
 		}});
 		DatabaseComponent db = createDatabaseComponent(database, cleaner,
-				shutdown);
+				eventBus, shutdown);
 
 		SubscriptionUpdate u = new SubscriptionUpdate(Arrays.asList(group), 1);
 		db.receiveSubscriptionUpdate(contactId, u);
@@ -1238,6 +1256,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 		final Database<Object> database = context.mock(Database.class);
 		final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
 		final ShutdownManager shutdown = context.mock(ShutdownManager.class);
+		final EventBus eventBus = context.mock(EventBus.class);
 		context.checking(new Expectations() {{
 			oneOf(database).startTransaction();
 			will(returnValue(txn));
@@ -1250,7 +1269,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 			oneOf(database).commitTransaction(txn);
 		}});
 		DatabaseComponent db = createDatabaseComponent(database, cleaner,
-				shutdown);
+				eventBus, shutdown);
 
 		TransportAck a = new TransportAck(transportId, 1);
 		db.receiveTransportAck(contactId, a);
@@ -1265,6 +1284,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 		final Database<Object> database = context.mock(Database.class);
 		final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
 		final ShutdownManager shutdown = context.mock(ShutdownManager.class);
+		final EventBus eventBus = context.mock(EventBus.class);
 		context.checking(new Expectations() {{
 			oneOf(database).startTransaction();
 			will(returnValue(txn));
@@ -1275,7 +1295,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 			oneOf(database).commitTransaction(txn);
 		}});
 		DatabaseComponent db = createDatabaseComponent(database, cleaner,
-				shutdown);
+				eventBus, shutdown);
 
 		TransportUpdate u = new TransportUpdate(transportId,
 				transportProperties, 1);
@@ -1294,7 +1314,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 		final Database<Object> database = context.mock(Database.class);
 		final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
 		final ShutdownManager shutdown = context.mock(ShutdownManager.class);
-		final EventListener listener = context.mock(EventListener.class);
+		final EventBus eventBus = context.mock(EventBus.class);
 		context.checking(new Expectations() {{
 			oneOf(database).startTransaction();
 			will(returnValue(txn));
@@ -1304,13 +1324,12 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 			will(returnValue(new TransportProperties()));
 			oneOf(database).mergeLocalProperties(txn, transportId, properties);
 			oneOf(database).commitTransaction(txn);
-			oneOf(listener).eventOccurred(with(any(
+			oneOf(eventBus).broadcast(with(any(
 					LocalTransportsUpdatedEvent.class)));
 		}});
 		DatabaseComponent db = createDatabaseComponent(database, cleaner,
-				shutdown);
+				eventBus, shutdown);
 
-		db.addListener(listener);
 		db.mergeLocalProperties(transportId, properties);
 
 		context.assertIsSatisfied();
@@ -1326,7 +1345,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 		final Database<Object> database = context.mock(Database.class);
 		final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
 		final ShutdownManager shutdown = context.mock(ShutdownManager.class);
-		final EventListener listener = context.mock(EventListener.class);
+		final EventBus eventBus = context.mock(EventBus.class);
 		context.checking(new Expectations() {{
 			oneOf(database).startTransaction();
 			will(returnValue(txn));
@@ -1337,9 +1356,8 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 			oneOf(database).commitTransaction(txn);
 		}});
 		DatabaseComponent db = createDatabaseComponent(database, cleaner,
-				shutdown);
+				eventBus, shutdown);
 
-		db.addListener(listener);
 		db.mergeLocalProperties(transportId, properties);
 
 		context.assertIsSatisfied();
@@ -1354,7 +1372,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 		final Database<Object> database = context.mock(Database.class);
 		final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
 		final ShutdownManager shutdown = context.mock(ShutdownManager.class);
-		final EventListener listener = context.mock(EventListener.class);
+		final EventBus eventBus = context.mock(EventBus.class);
 		context.checking(new Expectations() {{
 			oneOf(database).startTransaction();
 			will(returnValue(txn));
@@ -1367,13 +1385,12 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 			oneOf(database).removeVisibility(txn, contactId1, groupId);
 			oneOf(database).setVisibleToAll(txn, groupId, false);
 			oneOf(database).commitTransaction(txn);
-			oneOf(listener).eventOccurred(with(any(
+			oneOf(eventBus).broadcast(with(any(
 					LocalSubscriptionsUpdatedEvent.class)));
 		}});
 		DatabaseComponent db = createDatabaseComponent(database, cleaner,
-				shutdown);
+				eventBus, shutdown);
 
-		db.addListener(listener);
 		db.setVisibility(groupId, Arrays.asList(contactId));
 
 		context.assertIsSatisfied();
@@ -1389,7 +1406,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 		final Database<Object> database = context.mock(Database.class);
 		final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
 		final ShutdownManager shutdown = context.mock(ShutdownManager.class);
-		final EventListener listener = context.mock(EventListener.class);
+		final EventBus eventBus = context.mock(EventBus.class);
 		context.checking(new Expectations() {{
 			oneOf(database).startTransaction();
 			will(returnValue(txn));
@@ -1403,9 +1420,8 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 			oneOf(database).commitTransaction(txn);
 		}});
 		DatabaseComponent db = createDatabaseComponent(database, cleaner,
-				shutdown);
+				eventBus, shutdown);
 
-		db.addListener(listener);
 		db.setVisibility(groupId, both);
 
 		context.assertIsSatisfied();
@@ -1421,7 +1437,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 		final Database<Object> database = context.mock(Database.class);
 		final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
 		final ShutdownManager shutdown = context.mock(ShutdownManager.class);
-		final EventListener listener = context.mock(EventListener.class);
+		final EventBus eventBus = context.mock(EventBus.class);
 		context.checking(new Expectations() {{
 			// setVisibility()
 			oneOf(database).startTransaction();
@@ -1435,7 +1451,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 			oneOf(database).addVisibility(txn, contactId, groupId);
 			oneOf(database).setVisibleToAll(txn, groupId, false);
 			oneOf(database).commitTransaction(txn);
-			oneOf(listener).eventOccurred(with(any(
+			oneOf(eventBus).broadcast(with(any(
 					LocalSubscriptionsUpdatedEvent.class)));
 			// setVisibleToAll()
 			oneOf(database).startTransaction();
@@ -1449,13 +1465,12 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 			will(returnValue(both));
 			oneOf(database).addVisibility(txn, contactId1, groupId);
 			oneOf(database).commitTransaction(txn);
-			oneOf(listener).eventOccurred(with(any(
+			oneOf(eventBus).broadcast(with(any(
 					LocalSubscriptionsUpdatedEvent.class)));
 		}});
 		DatabaseComponent db = createDatabaseComponent(database, cleaner,
-				shutdown);
+				eventBus, shutdown);
 
-		db.addListener(listener);
 		db.setVisibility(groupId, Arrays.asList(contactId));
 		db.setVisibleToAll(groupId, true);
 
@@ -1469,6 +1484,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 		final Database<Object> database = context.mock(Database.class);
 		final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
 		final ShutdownManager shutdown = context.mock(ShutdownManager.class);
+		final EventBus eventBus = context.mock(EventBus.class);
 		context.checking(new Expectations() {{
 			// addSecrets()
 			oneOf(database).startTransaction();
@@ -1487,7 +1503,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 			oneOf(database).commitTransaction(txn);
 		}});
 		DatabaseComponent db = createDatabaseComponent(database, cleaner,
-				shutdown);
+				eventBus, shutdown);
 
 		db.addSecrets(Arrays.asList(temporarySecret));
 		assertEquals(Arrays.asList(temporarySecret), db.getSecrets());
diff --git a/briar-tests/src/org/briarproject/messaging/ConstantsTest.java b/briar-tests/src/org/briarproject/messaging/ConstantsTest.java
index b94059f01dcfed848dc71396d4513774efb6424f..75e36a80fd08134fcc3000a5eb7d583911488591 100644
--- a/briar-tests/src/org/briarproject/messaging/ConstantsTest.java
+++ b/briar-tests/src/org/briarproject/messaging/ConstantsTest.java
@@ -45,6 +45,7 @@ import org.briarproject.api.messaging.SubscriptionUpdate;
 import org.briarproject.api.messaging.TransportUpdate;
 import org.briarproject.crypto.CryptoModule;
 import org.briarproject.db.DatabaseModule;
+import org.briarproject.event.EventModule;
 import org.briarproject.messaging.duplex.DuplexMessagingModule;
 import org.briarproject.messaging.simplex.SimplexMessagingModule;
 import org.briarproject.serial.SerialModule;
@@ -65,9 +66,10 @@ public class ConstantsTest extends BriarTestCase {
 	public ConstantsTest() throws Exception {
 		Injector i = Guice.createInjector(new TestDatabaseModule(),
 				new TestLifecycleModule(), new TestSystemModule(),
-				new CryptoModule(), new DatabaseModule(), new MessagingModule(),
-				new DuplexMessagingModule(), new SimplexMessagingModule(),
-				new SerialModule(), new TransportModule());
+				new CryptoModule(), new DatabaseModule(), new EventModule(),
+				new MessagingModule(), new DuplexMessagingModule(),
+				new SimplexMessagingModule(), new SerialModule(),
+				new TransportModule());
 		crypto = i.getInstance(CryptoComponent.class);
 		groupFactory = i.getInstance(GroupFactory.class);
 		authorFactory = i.getInstance(AuthorFactory.class);
diff --git a/briar-tests/src/org/briarproject/messaging/simplex/OutgoingSimplexConnectionTest.java b/briar-tests/src/org/briarproject/messaging/simplex/OutgoingSimplexConnectionTest.java
index 34184b59dbb0d739ccbcb31c54813816e92c0b36..4f421c6127362d30aa57ad3d4901216a8a00be32 100644
--- a/briar-tests/src/org/briarproject/messaging/simplex/OutgoingSimplexConnectionTest.java
+++ b/briar-tests/src/org/briarproject/messaging/simplex/OutgoingSimplexConnectionTest.java
@@ -28,6 +28,7 @@ import org.briarproject.api.transport.ConnectionContext;
 import org.briarproject.api.transport.ConnectionRegistry;
 import org.briarproject.api.transport.ConnectionWriterFactory;
 import org.briarproject.crypto.CryptoModule;
+import org.briarproject.event.EventModule;
 import org.briarproject.messaging.MessagingModule;
 import org.briarproject.messaging.duplex.DuplexMessagingModule;
 import org.briarproject.serial.SerialModule;
@@ -59,6 +60,7 @@ public class OutgoingSimplexConnectionTest extends BriarTestCase {
 		context = new Mockery();
 		db = context.mock(DatabaseComponent.class);
 		Module testModule = new AbstractModule() {
+			@Override
 			public void configure() {
 				bind(DatabaseComponent.class).toInstance(db);
 				bind(Executor.class).annotatedWith(
@@ -68,7 +70,7 @@ public class OutgoingSimplexConnectionTest extends BriarTestCase {
 		};
 		Injector i = Guice.createInjector(testModule,
 				new TestLifecycleModule(), new TestSystemModule(),
-				new CryptoModule(), new MessagingModule(),
+				new CryptoModule(), new EventModule(), new MessagingModule(),
 				new DuplexMessagingModule(), new SimplexMessagingModule(),
 				new SerialModule(), new TransportModule());
 		connRegistry = i.getInstance(ConnectionRegistry.class);
diff --git a/briar-tests/src/org/briarproject/messaging/simplex/SimplexMessagingIntegrationTest.java b/briar-tests/src/org/briarproject/messaging/simplex/SimplexMessagingIntegrationTest.java
index 0856bbb38bfe4679bad7bc39eee170768543299d..659d4d8412d826818abcdaeba5339050a0950e83 100644
--- a/briar-tests/src/org/briarproject/messaging/simplex/SimplexMessagingIntegrationTest.java
+++ b/briar-tests/src/org/briarproject/messaging/simplex/SimplexMessagingIntegrationTest.java
@@ -22,6 +22,7 @@ import org.briarproject.api.TransportId;
 import org.briarproject.api.crypto.KeyManager;
 import org.briarproject.api.db.DatabaseComponent;
 import org.briarproject.api.event.Event;
+import org.briarproject.api.event.EventBus;
 import org.briarproject.api.event.EventListener;
 import org.briarproject.api.event.MessageAddedEvent;
 import org.briarproject.api.messaging.Group;
@@ -39,6 +40,7 @@ import org.briarproject.api.transport.ConnectionWriterFactory;
 import org.briarproject.api.transport.Endpoint;
 import org.briarproject.crypto.CryptoModule;
 import org.briarproject.db.DatabaseModule;
+import org.briarproject.event.EventModule;
 import org.briarproject.messaging.MessagingModule;
 import org.briarproject.messaging.duplex.DuplexMessagingModule;
 import org.briarproject.plugins.ImmediateExecutor;
@@ -85,9 +87,10 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase {
 	private Injector createInjector(File dir) {
 		return Guice.createInjector(new TestDatabaseModule(dir),
 				new TestLifecycleModule(), new TestSystemModule(),
-				new CryptoModule(), new DatabaseModule(), new MessagingModule(),
-				new DuplexMessagingModule(), new SimplexMessagingModule(),
-				new SerialModule(), new TransportModule());
+				new CryptoModule(), new DatabaseModule(), new EventModule(),
+				new MessagingModule(), new DuplexMessagingModule(),
+				new SimplexMessagingModule(), new SerialModule(),
+				new TransportModule());
 	}
 
 	@Test
@@ -190,9 +193,9 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase {
 		Endpoint ep = new Endpoint(contactId, transportId, epoch, false);
 		db.addEndpoint(ep);
 		km.endpointAdded(ep, LATENCY, initialSecret.clone());
-		// Set up a database listener
+		// Set up an event listener
 		MessageListener listener = new MessageListener();
-		db.addListener(listener);
+		bob.getInstance(EventBus.class).addListener(listener);
 		// Create a connection recogniser and recognise the connection
 		ByteArrayInputStream in = new ByteArrayInputStream(b);
 		ConnectionRecogniser rec = bob.getInstance(ConnectionRecogniser.class);
diff --git a/briar-tests/src/org/briarproject/transport/KeyManagerImplTest.java b/briar-tests/src/org/briarproject/transport/KeyManagerImplTest.java
index 29e9114fc55b25dd8aa290a1d983d45a16862494..08b05a38e07eb54c6805bafada631d2afa5ccd06 100644
--- a/briar-tests/src/org/briarproject/transport/KeyManagerImplTest.java
+++ b/briar-tests/src/org/briarproject/transport/KeyManagerImplTest.java
@@ -11,6 +11,7 @@ import org.briarproject.api.ContactId;
 import org.briarproject.api.TransportId;
 import org.briarproject.api.crypto.CryptoComponent;
 import org.briarproject.api.db.DatabaseComponent;
+import org.briarproject.api.event.EventBus;
 import org.briarproject.api.event.EventListener;
 import org.briarproject.api.system.Clock;
 import org.briarproject.api.system.Timer;
@@ -56,17 +57,18 @@ public class KeyManagerImplTest extends BriarTestCase {
 		Mockery context = new Mockery();
 		final CryptoComponent crypto = context.mock(CryptoComponent.class);
 		final DatabaseComponent db = context.mock(DatabaseComponent.class);
+		final EventBus eventBus = context.mock(EventBus.class);
 		final ConnectionRecogniser connectionRecogniser =
 				context.mock(ConnectionRecogniser.class);
 		final Clock clock = context.mock(Clock.class);
 		final Timer timer = context.mock(Timer.class);
 
 		final KeyManagerImpl keyManager = new KeyManagerImpl(crypto, db,
-				connectionRecogniser, clock, timer);
+				eventBus, connectionRecogniser, clock, timer);
 
 		context.checking(new Expectations() {{
 			// start()
-			oneOf(db).addListener(with(any(EventListener.class)));
+			oneOf(eventBus).addListener(with(any(EventListener.class)));
 			oneOf(db).getSecrets();
 			will(returnValue(Collections.emptyList()));
 			oneOf(db).getTransportLatencies();
@@ -76,7 +78,7 @@ public class KeyManagerImplTest extends BriarTestCase {
 			oneOf(timer).scheduleAtFixedRate(with(keyManager),
 					with(any(long.class)), with(any(long.class)));
 			// stop()
-			oneOf(db).removeListener(with(any(EventListener.class)));
+			oneOf(eventBus).removeListener(with(any(EventListener.class)));
 			oneOf(timer).cancel();
 			oneOf(connectionRecogniser).removeSecrets();
 		}});
@@ -92,13 +94,14 @@ public class KeyManagerImplTest extends BriarTestCase {
 		Mockery context = new Mockery();
 		final CryptoComponent crypto = context.mock(CryptoComponent.class);
 		final DatabaseComponent db = context.mock(DatabaseComponent.class);
+		final EventBus eventBus = context.mock(EventBus.class);
 		final ConnectionRecogniser connectionRecogniser =
 				context.mock(ConnectionRecogniser.class);
 		final Clock clock = context.mock(Clock.class);
 		final Timer timer = context.mock(Timer.class);
 
 		final KeyManagerImpl keyManager = new KeyManagerImpl(crypto, db,
-				connectionRecogniser, clock, timer);
+				eventBus, connectionRecogniser, clock, timer);
 
 		// The secrets for periods 0 - 2 should be derived
 		Endpoint ep = new Endpoint(contactId, transportId, EPOCH, true);
@@ -108,7 +111,7 @@ public class KeyManagerImplTest extends BriarTestCase {
 
 		context.checking(new Expectations() {{
 			// start()
-			oneOf(db).addListener(with(any(EventListener.class)));
+			oneOf(eventBus).addListener(with(any(EventListener.class)));
 			oneOf(db).getSecrets();
 			will(returnValue(Collections.emptyList()));
 			oneOf(db).getTransportLatencies();
@@ -133,7 +136,7 @@ public class KeyManagerImplTest extends BriarTestCase {
 			oneOf(connectionRecogniser).addSecret(s1);
 			oneOf(connectionRecogniser).addSecret(s2);
 			// stop()
-			oneOf(db).removeListener(with(any(EventListener.class)));
+			oneOf(eventBus).removeListener(with(any(EventListener.class)));
 			oneOf(timer).cancel();
 			oneOf(connectionRecogniser).removeSecrets();
 		}});
@@ -150,13 +153,14 @@ public class KeyManagerImplTest extends BriarTestCase {
 		Mockery context = new Mockery();
 		final CryptoComponent crypto = context.mock(CryptoComponent.class);
 		final DatabaseComponent db = context.mock(DatabaseComponent.class);
+		final EventBus eventBus = context.mock(EventBus.class);
 		final ConnectionRecogniser connectionRecogniser =
 				context.mock(ConnectionRecogniser.class);
 		final Clock clock = context.mock(Clock.class);
 		final Timer timer = context.mock(Timer.class);
 
 		final KeyManagerImpl keyManager = new KeyManagerImpl(crypto, db,
-				connectionRecogniser, clock, timer);
+				eventBus, connectionRecogniser, clock, timer);
 
 		// The secrets for periods 0 - 2 should be derived
 		Endpoint ep = new Endpoint(contactId, transportId, EPOCH, true);
@@ -166,7 +170,7 @@ public class KeyManagerImplTest extends BriarTestCase {
 
 		context.checking(new Expectations() {{
 			// start()
-			oneOf(db).addListener(with(any(EventListener.class)));
+			oneOf(eventBus).addListener(with(any(EventListener.class)));
 			oneOf(db).getSecrets();
 			will(returnValue(Collections.emptyList()));
 			oneOf(db).getTransportLatencies();
@@ -194,7 +198,7 @@ public class KeyManagerImplTest extends BriarTestCase {
 			oneOf(db).incrementConnectionCounter(contactId, transportId, 1);
 			will(returnValue(0L));
 			// stop()
-			oneOf(db).removeListener(with(any(EventListener.class)));
+			oneOf(eventBus).removeListener(with(any(EventListener.class)));
 			oneOf(timer).cancel();
 			oneOf(connectionRecogniser).removeSecrets();
 		}});
@@ -219,13 +223,14 @@ public class KeyManagerImplTest extends BriarTestCase {
 		Mockery context = new Mockery();
 		final CryptoComponent crypto = context.mock(CryptoComponent.class);
 		final DatabaseComponent db = context.mock(DatabaseComponent.class);
+		final EventBus eventBus = context.mock(EventBus.class);
 		final ConnectionRecogniser connectionRecogniser =
 				context.mock(ConnectionRecogniser.class);
 		final Clock clock = context.mock(Clock.class);
 		final Timer timer = context.mock(Timer.class);
 
 		final KeyManagerImpl keyManager = new KeyManagerImpl(crypto, db,
-				connectionRecogniser, clock, timer);
+				eventBus, connectionRecogniser, clock, timer);
 
 		// The DB contains the secrets for periods 0 - 2
 		Endpoint ep = new Endpoint(contactId, transportId, EPOCH, true);
@@ -235,7 +240,7 @@ public class KeyManagerImplTest extends BriarTestCase {
 
 		context.checking(new Expectations() {{
 			// start()
-			oneOf(db).addListener(with(any(EventListener.class)));
+			oneOf(eventBus).addListener(with(any(EventListener.class)));
 			oneOf(db).getSecrets();
 			will(returnValue(Arrays.asList(s0, s1, s2)));
 			oneOf(db).getTransportLatencies();
@@ -251,7 +256,7 @@ public class KeyManagerImplTest extends BriarTestCase {
 			oneOf(timer).scheduleAtFixedRate(with(keyManager),
 					with(any(long.class)), with(any(long.class)));
 			// stop()
-			oneOf(db).removeListener(with(any(EventListener.class)));
+			oneOf(eventBus).removeListener(with(any(EventListener.class)));
 			oneOf(timer).cancel();
 			oneOf(connectionRecogniser).removeSecrets();
 		}});
@@ -267,13 +272,14 @@ public class KeyManagerImplTest extends BriarTestCase {
 		Mockery context = new Mockery();
 		final CryptoComponent crypto = context.mock(CryptoComponent.class);
 		final DatabaseComponent db = context.mock(DatabaseComponent.class);
+		final EventBus eventBus = context.mock(EventBus.class);
 		final ConnectionRecogniser connectionRecogniser =
 				context.mock(ConnectionRecogniser.class);
 		final Clock clock = context.mock(Clock.class);
 		final Timer timer = context.mock(Timer.class);
 
 		final KeyManagerImpl keyManager = new KeyManagerImpl(crypto, db,
-				connectionRecogniser, clock, timer);
+				eventBus, connectionRecogniser, clock, timer);
 
 		// The DB contains the secrets for periods 0 - 2
 		Endpoint ep = new Endpoint(contactId, transportId, EPOCH, true);
@@ -285,7 +291,7 @@ public class KeyManagerImplTest extends BriarTestCase {
 
 		context.checking(new Expectations() {{
 			// start()
-			oneOf(db).addListener(with(any(EventListener.class)));
+			oneOf(eventBus).addListener(with(any(EventListener.class)));
 			oneOf(db).getSecrets();
 			will(returnValue(Arrays.asList(s0, s1, s2)));
 			oneOf(db).getTransportLatencies();
@@ -309,7 +315,7 @@ public class KeyManagerImplTest extends BriarTestCase {
 			oneOf(timer).scheduleAtFixedRate(with(keyManager),
 					with(any(long.class)), with(any(long.class)));
 			// stop()
-			oneOf(db).removeListener(with(any(EventListener.class)));
+			oneOf(eventBus).removeListener(with(any(EventListener.class)));
 			oneOf(timer).cancel();
 			oneOf(connectionRecogniser).removeSecrets();
 		}});
@@ -325,13 +331,14 @@ public class KeyManagerImplTest extends BriarTestCase {
 		Mockery context = new Mockery();
 		final CryptoComponent crypto = context.mock(CryptoComponent.class);
 		final DatabaseComponent db = context.mock(DatabaseComponent.class);
+		final EventBus eventBus = context.mock(EventBus.class);
 		final ConnectionRecogniser connectionRecogniser =
 				context.mock(ConnectionRecogniser.class);
 		final Clock clock = context.mock(Clock.class);
 		final Timer timer = context.mock(Timer.class);
 
 		final KeyManagerImpl keyManager = new KeyManagerImpl(crypto, db,
-				connectionRecogniser, clock, timer);
+				eventBus, connectionRecogniser, clock, timer);
 
 		// The DB contains the secrets for periods 0 - 2
 		Endpoint ep = new Endpoint(contactId, transportId, EPOCH, true);
@@ -344,7 +351,7 @@ public class KeyManagerImplTest extends BriarTestCase {
 
 		context.checking(new Expectations() {{
 			// start()
-			oneOf(db).addListener(with(any(EventListener.class)));
+			oneOf(eventBus).addListener(with(any(EventListener.class)));
 			oneOf(db).getSecrets();
 			will(returnValue(Arrays.asList(s0, s1, s2)));
 			oneOf(db).getTransportLatencies();
@@ -369,7 +376,7 @@ public class KeyManagerImplTest extends BriarTestCase {
 			oneOf(timer).scheduleAtFixedRate(with(keyManager),
 					with(any(long.class)), with(any(long.class)));
 			// stop()
-			oneOf(db).removeListener(with(any(EventListener.class)));
+			oneOf(eventBus).removeListener(with(any(EventListener.class)));
 			oneOf(timer).cancel();
 			oneOf(connectionRecogniser).removeSecrets();
 		}});
@@ -385,13 +392,14 @@ public class KeyManagerImplTest extends BriarTestCase {
 		Mockery context = new Mockery();
 		final CryptoComponent crypto = context.mock(CryptoComponent.class);
 		final DatabaseComponent db = context.mock(DatabaseComponent.class);
+		final EventBus eventBus = context.mock(EventBus.class);
 		final ConnectionRecogniser connectionRecogniser =
 				context.mock(ConnectionRecogniser.class);
 		final Clock clock = context.mock(Clock.class);
 		final Timer timer = context.mock(Timer.class);
 
 		final KeyManagerImpl keyManager = new KeyManagerImpl(crypto, db,
-				connectionRecogniser, clock, timer);
+				eventBus, connectionRecogniser, clock, timer);
 
 		// The DB contains the secrets for periods 0 - 2
 		Endpoint ep = new Endpoint(contactId, transportId, EPOCH, true);
@@ -401,7 +409,7 @@ public class KeyManagerImplTest extends BriarTestCase {
 
 		context.checking(new Expectations() {{
 			// start()
-			oneOf(db).addListener(with(any(EventListener.class)));
+			oneOf(eventBus).addListener(with(any(EventListener.class)));
 			oneOf(db).getSecrets();
 			will(returnValue(Arrays.asList(s0, s1, s2)));
 			oneOf(db).getTransportLatencies();
@@ -423,7 +431,7 @@ public class KeyManagerImplTest extends BriarTestCase {
 			oneOf(db).incrementConnectionCounter(contactId, transportId, 1);
 			will(returnValue(0L));
 			// stop()
-			oneOf(db).removeListener(with(any(EventListener.class)));
+			oneOf(eventBus).removeListener(with(any(EventListener.class)));
 			oneOf(timer).cancel();
 			oneOf(connectionRecogniser).removeSecrets();
 		}});
@@ -448,13 +456,14 @@ public class KeyManagerImplTest extends BriarTestCase {
 		Mockery context = new Mockery();
 		final CryptoComponent crypto = context.mock(CryptoComponent.class);
 		final DatabaseComponent db = context.mock(DatabaseComponent.class);
+		final EventBus eventBus = context.mock(EventBus.class);
 		final ConnectionRecogniser connectionRecogniser =
 				context.mock(ConnectionRecogniser.class);
 		final Clock clock = context.mock(Clock.class);
 		final Timer timer = context.mock(Timer.class);
 
 		final KeyManagerImpl keyManager = new KeyManagerImpl(crypto, db,
-				connectionRecogniser, clock, timer);
+				eventBus, connectionRecogniser, clock, timer);
 
 		// The DB contains the secrets for periods 0 - 2
 		Endpoint ep = new Endpoint(contactId, transportId, EPOCH, true);
@@ -466,7 +475,7 @@ public class KeyManagerImplTest extends BriarTestCase {
 
 		context.checking(new Expectations() {{
 			// start()
-			oneOf(db).addListener(with(any(EventListener.class)));
+			oneOf(eventBus).addListener(with(any(EventListener.class)));
 			oneOf(db).getSecrets();
 			will(returnValue(Arrays.asList(s0, s1, s2)));
 			oneOf(db).getTransportLatencies();
@@ -497,7 +506,7 @@ public class KeyManagerImplTest extends BriarTestCase {
 			oneOf(db).incrementConnectionCounter(contactId, transportId, 2);
 			will(returnValue(0L));
 			// stop()
-			oneOf(db).removeListener(with(any(EventListener.class)));
+			oneOf(eventBus).removeListener(with(any(EventListener.class)));
 			oneOf(timer).cancel();
 			oneOf(connectionRecogniser).removeSecrets();
 		}});
@@ -522,13 +531,14 @@ public class KeyManagerImplTest extends BriarTestCase {
 		Mockery context = new Mockery();
 		final CryptoComponent crypto = context.mock(CryptoComponent.class);
 		final DatabaseComponent db = context.mock(DatabaseComponent.class);
+		final EventBus eventBus = context.mock(EventBus.class);
 		final ConnectionRecogniser connectionRecogniser =
 				context.mock(ConnectionRecogniser.class);
 		final Clock clock = context.mock(Clock.class);
 		final Timer timer = context.mock(Timer.class);
 
 		final KeyManagerImpl keyManager = new KeyManagerImpl(crypto, db,
-				connectionRecogniser, clock, timer);
+				eventBus, connectionRecogniser, clock, timer);
 
 		// The DB contains the secrets for periods 0 - 2
 		Endpoint ep = new Endpoint(contactId, transportId, EPOCH, true);
@@ -541,7 +551,7 @@ public class KeyManagerImplTest extends BriarTestCase {
 
 		context.checking(new Expectations() {{
 			// start()
-			oneOf(db).addListener(with(any(EventListener.class)));
+			oneOf(eventBus).addListener(with(any(EventListener.class)));
 			oneOf(db).getSecrets();
 			will(returnValue(Arrays.asList(s0, s1, s2)));
 			oneOf(db).getTransportLatencies();
@@ -574,7 +584,7 @@ public class KeyManagerImplTest extends BriarTestCase {
 			oneOf(db).incrementConnectionCounter(contactId, transportId, 3);
 			will(returnValue(0L));
 			// stop()
-			oneOf(db).removeListener(with(any(EventListener.class)));
+			oneOf(eventBus).removeListener(with(any(EventListener.class)));
 			oneOf(timer).cancel();
 			oneOf(connectionRecogniser).removeSecrets();
 		}});
diff --git a/briar-tests/src/org/briarproject/transport/KeyRotationIntegrationTest.java b/briar-tests/src/org/briarproject/transport/KeyRotationIntegrationTest.java
index 9f2dad9337ca4235a79688b2df38bb6329078f32..6969f67df5398af49a50c2995b464d0515872ac7 100644
--- a/briar-tests/src/org/briarproject/transport/KeyRotationIntegrationTest.java
+++ b/briar-tests/src/org/briarproject/transport/KeyRotationIntegrationTest.java
@@ -13,6 +13,7 @@ import org.briarproject.api.TransportId;
 import org.briarproject.api.crypto.CryptoComponent;
 import org.briarproject.api.crypto.SecretKey;
 import org.briarproject.api.db.DatabaseComponent;
+import org.briarproject.api.event.EventBus;
 import org.briarproject.api.event.EventListener;
 import org.briarproject.api.system.Clock;
 import org.briarproject.api.system.Timer;
@@ -73,17 +74,18 @@ public class KeyRotationIntegrationTest extends BriarTestCase {
 		Mockery context = new Mockery();
 		final CryptoComponent crypto = context.mock(CryptoComponent.class);
 		final DatabaseComponent db = context.mock(DatabaseComponent.class);
+		final EventBus eventBus = context.mock(EventBus.class);
 		final Clock clock = context.mock(Clock.class);
 		final Timer timer = context.mock(Timer.class);
 
 		final ConnectionRecogniser connectionRecogniser =
 				new ConnectionRecogniserImpl(crypto, db);
 		final KeyManagerImpl keyManager = new KeyManagerImpl(crypto, db,
-				connectionRecogniser, clock, timer);
+				eventBus, connectionRecogniser, clock, timer);
 
 		context.checking(new Expectations() {{
 			// start()
-			oneOf(db).addListener(with(any(EventListener.class)));
+			oneOf(eventBus).addListener(with(any(EventListener.class)));
 			oneOf(db).getSecrets();
 			will(returnValue(Collections.emptyList()));
 			oneOf(db).getTransportLatencies();
@@ -93,7 +95,7 @@ public class KeyRotationIntegrationTest extends BriarTestCase {
 			oneOf(timer).scheduleAtFixedRate(with(keyManager),
 					with(any(long.class)), with(any(long.class)));
 			// stop()
-			oneOf(db).removeListener(with(any(EventListener.class)));
+			oneOf(eventBus).removeListener(with(any(EventListener.class)));
 			oneOf(timer).cancel();
 		}});
 
@@ -108,6 +110,7 @@ public class KeyRotationIntegrationTest extends BriarTestCase {
 		Mockery context = new Mockery();
 		final CryptoComponent crypto = context.mock(CryptoComponent.class);
 		final DatabaseComponent db = context.mock(DatabaseComponent.class);
+		final EventBus eventBus = context.mock(EventBus.class);
 		final Clock clock = context.mock(Clock.class);
 		final Timer timer = context.mock(Timer.class);
 		final SecretKey k0 = context.mock(SecretKey.class, "k0");
@@ -117,7 +120,7 @@ public class KeyRotationIntegrationTest extends BriarTestCase {
 		final ConnectionRecogniser connectionRecogniser =
 				new ConnectionRecogniserImpl(crypto, db);
 		final KeyManagerImpl keyManager = new KeyManagerImpl(crypto, db,
-				connectionRecogniser, clock, timer);
+				eventBus, connectionRecogniser, clock, timer);
 
 		// The secrets for periods 0 - 2 should be derived
 		Endpoint ep = new Endpoint(contactId, transportId, EPOCH, true);
@@ -127,7 +130,7 @@ public class KeyRotationIntegrationTest extends BriarTestCase {
 
 		context.checking(new Expectations() {{
 			// start()
-			oneOf(db).addListener(with(any(EventListener.class)));
+			oneOf(eventBus).addListener(with(any(EventListener.class)));
 			oneOf(db).getSecrets();
 			will(returnValue(Collections.emptyList()));
 			oneOf(db).getTransportLatencies();
@@ -215,7 +218,7 @@ public class KeyRotationIntegrationTest extends BriarTestCase {
 			}
 			oneOf(k2).erase();
 			// Remove the listener and stop the timer
-			oneOf(db).removeListener(with(any(EventListener.class)));
+			oneOf(eventBus).removeListener(with(any(EventListener.class)));
 			oneOf(timer).cancel();
 		}});
 
@@ -231,6 +234,7 @@ public class KeyRotationIntegrationTest extends BriarTestCase {
 		Mockery context = new Mockery();
 		final CryptoComponent crypto = context.mock(CryptoComponent.class);
 		final DatabaseComponent db = context.mock(DatabaseComponent.class);
+		final EventBus eventBus = context.mock(EventBus.class);
 		final Clock clock = context.mock(Clock.class);
 		final Timer timer = context.mock(Timer.class);
 		final SecretKey k0 = context.mock(SecretKey.class, "k0");
@@ -240,7 +244,7 @@ public class KeyRotationIntegrationTest extends BriarTestCase {
 		final ConnectionRecogniser connectionRecogniser =
 				new ConnectionRecogniserImpl(crypto, db);
 		final KeyManagerImpl keyManager = new KeyManagerImpl(crypto, db,
-				connectionRecogniser, clock, timer);
+				eventBus, connectionRecogniser, clock, timer);
 
 		// The secrets for periods 0 - 2 should be derived
 		Endpoint ep = new Endpoint(contactId, transportId, EPOCH, true);
@@ -250,7 +254,7 @@ public class KeyRotationIntegrationTest extends BriarTestCase {
 
 		context.checking(new Expectations() {{
 			// start()
-			oneOf(db).addListener(with(any(EventListener.class)));
+			oneOf(eventBus).addListener(with(any(EventListener.class)));
 			oneOf(db).getSecrets();
 			will(returnValue(Collections.emptyList()));
 			oneOf(db).getTransportLatencies();
@@ -341,7 +345,7 @@ public class KeyRotationIntegrationTest extends BriarTestCase {
 			}
 			oneOf(k2).erase();
 			// Remove the listener and stop the timer
-			oneOf(db).removeListener(with(any(EventListener.class)));
+			oneOf(eventBus).removeListener(with(any(EventListener.class)));
 			oneOf(timer).cancel();
 		}});
 
@@ -365,6 +369,7 @@ public class KeyRotationIntegrationTest extends BriarTestCase {
 		Mockery context = new Mockery();
 		final CryptoComponent crypto = context.mock(CryptoComponent.class);
 		final DatabaseComponent db = context.mock(DatabaseComponent.class);
+		final EventBus eventBus = context.mock(EventBus.class);
 		final Clock clock = context.mock(Clock.class);
 		final Timer timer = context.mock(Timer.class);
 		final SecretKey k0 = context.mock(SecretKey.class, "k0");
@@ -374,7 +379,7 @@ public class KeyRotationIntegrationTest extends BriarTestCase {
 		final ConnectionRecogniser connectionRecogniser =
 				new ConnectionRecogniserImpl(crypto, db);
 		final KeyManagerImpl keyManager = new KeyManagerImpl(crypto, db,
-				connectionRecogniser, clock, timer);
+				eventBus, connectionRecogniser, clock, timer);
 
 		// The secrets for periods 0 - 2 should be derived
 		Endpoint ep = new Endpoint(contactId, transportId, EPOCH, true);
@@ -384,7 +389,7 @@ public class KeyRotationIntegrationTest extends BriarTestCase {
 
 		context.checking(new Expectations() {{
 			// start()
-			oneOf(db).addListener(with(any(EventListener.class)));
+			oneOf(eventBus).addListener(with(any(EventListener.class)));
 			oneOf(db).getSecrets();
 			will(returnValue(Collections.emptyList()));
 			oneOf(db).getTransportLatencies();
@@ -483,7 +488,7 @@ public class KeyRotationIntegrationTest extends BriarTestCase {
 			}
 			oneOf(k2).erase();
 			// Remove the listener and stop the timer
-			oneOf(db).removeListener(with(any(EventListener.class)));
+			oneOf(eventBus).removeListener(with(any(EventListener.class)));
 			oneOf(timer).cancel();
 		}});
 
@@ -510,6 +515,7 @@ public class KeyRotationIntegrationTest extends BriarTestCase {
 		Mockery context = new Mockery();
 		final CryptoComponent crypto = context.mock(CryptoComponent.class);
 		final DatabaseComponent db = context.mock(DatabaseComponent.class);
+		final EventBus eventBus = context.mock(EventBus.class);
 		final Clock clock = context.mock(Clock.class);
 		final Timer timer = context.mock(Timer.class);
 		final SecretKey k0 = context.mock(SecretKey.class, "k0");
@@ -519,7 +525,7 @@ public class KeyRotationIntegrationTest extends BriarTestCase {
 		final ConnectionRecogniser connectionRecogniser =
 				new ConnectionRecogniserImpl(crypto, db);
 		final KeyManagerImpl keyManager = new KeyManagerImpl(crypto, db,
-				connectionRecogniser, clock, timer);
+				eventBus, connectionRecogniser, clock, timer);
 
 		// The DB contains the secrets for periods 0 - 2
 		Endpoint ep = new Endpoint(contactId, transportId, EPOCH, true);
@@ -529,7 +535,7 @@ public class KeyRotationIntegrationTest extends BriarTestCase {
 
 		context.checking(new Expectations() {{
 			// start()
-			oneOf(db).addListener(with(any(EventListener.class)));
+			oneOf(eventBus).addListener(with(any(EventListener.class)));
 			oneOf(db).getSecrets();
 			will(returnValue(Arrays.asList(s0, s1, s2)));
 			oneOf(db).getTransportLatencies();
@@ -609,7 +615,7 @@ public class KeyRotationIntegrationTest extends BriarTestCase {
 			}
 			oneOf(k2).erase();
 			// Remove the listener and stop the timer
-			oneOf(db).removeListener(with(any(EventListener.class)));
+			oneOf(eventBus).removeListener(with(any(EventListener.class)));
 			oneOf(timer).cancel();
 		}});
 
@@ -624,6 +630,7 @@ public class KeyRotationIntegrationTest extends BriarTestCase {
 		Mockery context = new Mockery();
 		final CryptoComponent crypto = context.mock(CryptoComponent.class);
 		final DatabaseComponent db = context.mock(DatabaseComponent.class);
+		final EventBus eventBus = context.mock(EventBus.class);
 		final Clock clock = context.mock(Clock.class);
 		final Timer timer = context.mock(Timer.class);
 		final SecretKey k1 = context.mock(SecretKey.class, "k1");
@@ -633,7 +640,7 @@ public class KeyRotationIntegrationTest extends BriarTestCase {
 		final ConnectionRecogniser connectionRecogniser =
 				new ConnectionRecogniserImpl(crypto, db);
 		final KeyManagerImpl keyManager = new KeyManagerImpl(crypto, db,
-				connectionRecogniser, clock, timer);
+				eventBus, connectionRecogniser, clock, timer);
 
 		// The DB contains the secrets for periods 0 - 2
 		Endpoint ep = new Endpoint(contactId, transportId, EPOCH, true);
@@ -645,7 +652,7 @@ public class KeyRotationIntegrationTest extends BriarTestCase {
 
 		context.checking(new Expectations() {{
 			// start()
-			oneOf(db).addListener(with(any(EventListener.class)));
+			oneOf(eventBus).addListener(with(any(EventListener.class)));
 			oneOf(db).getSecrets();
 			will(returnValue(Arrays.asList(s0, s1, s2)));
 			oneOf(db).getTransportLatencies();
@@ -733,7 +740,7 @@ public class KeyRotationIntegrationTest extends BriarTestCase {
 			}
 			oneOf(k3).erase();
 			// Remove the listener and stop the timer
-			oneOf(db).removeListener(with(any(EventListener.class)));
+			oneOf(eventBus).removeListener(with(any(EventListener.class)));
 			oneOf(timer).cancel();
 		}});
 
@@ -748,6 +755,7 @@ public class KeyRotationIntegrationTest extends BriarTestCase {
 		Mockery context = new Mockery();
 		final CryptoComponent crypto = context.mock(CryptoComponent.class);
 		final DatabaseComponent db = context.mock(DatabaseComponent.class);
+		final EventBus eventBus = context.mock(EventBus.class);
 		final Clock clock = context.mock(Clock.class);
 		final Timer timer = context.mock(Timer.class);
 		final SecretKey k2 = context.mock(SecretKey.class, "k2");
@@ -757,7 +765,7 @@ public class KeyRotationIntegrationTest extends BriarTestCase {
 		final ConnectionRecogniser connectionRecogniser =
 				new ConnectionRecogniserImpl(crypto, db);
 		final KeyManagerImpl keyManager = new KeyManagerImpl(crypto, db,
-				connectionRecogniser, clock, timer);
+				eventBus, connectionRecogniser, clock, timer);
 
 		// The DB contains the secrets for periods 0 - 2
 		Endpoint ep = new Endpoint(contactId, transportId, EPOCH, true);
@@ -770,7 +778,7 @@ public class KeyRotationIntegrationTest extends BriarTestCase {
 
 		context.checking(new Expectations() {{
 			// start()
-			oneOf(db).addListener(with(any(EventListener.class)));
+			oneOf(eventBus).addListener(with(any(EventListener.class)));
 			oneOf(db).getSecrets();
 			will(returnValue(Arrays.asList(s0, s1, s2)));
 			oneOf(db).getTransportLatencies();
@@ -859,7 +867,7 @@ public class KeyRotationIntegrationTest extends BriarTestCase {
 			}
 			oneOf(k4).erase();
 			// Remove the listener and stop the timer
-			oneOf(db).removeListener(with(any(EventListener.class)));
+			oneOf(eventBus).removeListener(with(any(EventListener.class)));
 			oneOf(timer).cancel();
 		}});