diff --git a/briar-api/src/net/sf/briar/api/transport/ConnectionListener.java b/briar-api/src/net/sf/briar/api/transport/ConnectionListener.java
new file mode 100644
index 0000000000000000000000000000000000000000..0d9a027c8bc6f0a7fc312f89ecc6f2854c1abdf6
--- /dev/null
+++ b/briar-api/src/net/sf/briar/api/transport/ConnectionListener.java
@@ -0,0 +1,13 @@
+package net.sf.briar.api.transport;
+
+import net.sf.briar.api.ContactId;
+
+/** An interface for listening for connection and disconnection events. */
+public interface ConnectionListener {
+
+	/** Called when a contact connects and has no existing connections. */
+	void contactConnected(ContactId c);
+
+	/** Called when a contact disconnects and has no remaining connections. */
+	void contactDisconnected(ContactId c);
+}
diff --git a/briar-api/src/net/sf/briar/api/transport/ConnectionRegistry.java b/briar-api/src/net/sf/briar/api/transport/ConnectionRegistry.java
index 2983fff39cc0dc50fd82dcefc9b01b123167956b..fd3759613a59aa1b5d1888da01bd7c7d79cd2992 100644
--- a/briar-api/src/net/sf/briar/api/transport/ConnectionRegistry.java
+++ b/briar-api/src/net/sf/briar/api/transport/ConnectionRegistry.java
@@ -10,9 +10,15 @@ import net.sf.briar.api.messaging.TransportId;
  */
 public interface ConnectionRegistry {
 
+	void addListener(ConnectionListener c);
+
+	void removeListener(ConnectionListener c);
+
 	void registerConnection(ContactId c, TransportId t);
 
 	void unregisterConnection(ContactId c, TransportId t);
 
 	Collection<ContactId> getConnectedContacts(TransportId t);
+
+	boolean isConnected(ContactId c);
 }
diff --git a/briar-core/src/net/sf/briar/transport/ConnectionRegistryImpl.java b/briar-core/src/net/sf/briar/transport/ConnectionRegistryImpl.java
index ffa0c2c97bf549931314cb5daf7729061b30760c..d1a5b48a64126977fcb5207fe89abd3221a99844 100644
--- a/briar-core/src/net/sf/briar/transport/ConnectionRegistryImpl.java
+++ b/briar-core/src/net/sf/briar/transport/ConnectionRegistryImpl.java
@@ -6,41 +6,81 @@ import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.CopyOnWriteArrayList;
 
 import net.sf.briar.api.ContactId;
 import net.sf.briar.api.messaging.TransportId;
+import net.sf.briar.api.transport.ConnectionListener;
 import net.sf.briar.api.transport.ConnectionRegistry;
 
 class ConnectionRegistryImpl implements ConnectionRegistry {
 
 	// Locking: this
 	private final Map<TransportId, Map<ContactId, Integer>> connections;
+	// Locking: this
+	private final Map<ContactId, Integer> contactCounts;
+	private final List<ConnectionListener> listeners;
 
 	ConnectionRegistryImpl() {
 		connections = new HashMap<TransportId, Map<ContactId, Integer>>();
+		contactCounts = new HashMap<ContactId, Integer>();
+		listeners = new CopyOnWriteArrayList<ConnectionListener>();
 	}
 
-	public synchronized void registerConnection(ContactId c, TransportId t) {
-		Map<ContactId, Integer> m = connections.get(t);
-		if(m == null) {
-			m = new HashMap<ContactId, Integer>();
-			connections.put(t, m);
+	public void addListener(ConnectionListener c) {
+		listeners.add(c);
+	}
+
+	public void removeListener(ConnectionListener c) {
+		listeners.remove(c);
+	}
+
+	public void registerConnection(ContactId c, TransportId t) {
+		boolean firstConnection = false;
+		synchronized(this) {
+			Map<ContactId, Integer> m = connections.get(t);
+			if(m == null) {
+				m = new HashMap<ContactId, Integer>();
+				connections.put(t, m);
+			}
+			Integer count = m.get(c);
+			if(count == null) m.put(c, 1);
+			else m.put(c, count + 1);
+			count = contactCounts.get(c);
+			if(count == null) {
+				firstConnection = true;
+				contactCounts.put(c, 1);
+			} else {
+				contactCounts.put(c, count + 1);
+			}
 		}
-		Integer count = m.get(c);
-		if(count == null) m.put(c, 1);
-		else m.put(c, count + 1);
+		if(firstConnection)
+			for(ConnectionListener l : listeners) l.contactConnected(c);
 	}
 
-	public synchronized void unregisterConnection(ContactId c, TransportId t) {
-		Map<ContactId, Integer> m = connections.get(t);
-		if(m == null) throw new IllegalArgumentException();
-		Integer count = m.remove(c);
-		if(count == null) throw new IllegalArgumentException();
-		if(count == 1) {
-			if(m.isEmpty()) connections.remove(t);
-		} else {
-			m.put(c, count - 1);
+	public void unregisterConnection(ContactId c, TransportId t) {
+		boolean lastConnection = false;
+		synchronized(this) {
+			Map<ContactId, Integer> m = connections.get(t);
+			if(m == null) throw new IllegalArgumentException();
+			Integer count = m.remove(c);
+			if(count == null) throw new IllegalArgumentException();
+			if(count == 1) {
+				if(m.isEmpty()) connections.remove(t);
+			} else {
+				m.put(c, count - 1);
+			}
+			count = contactCounts.get(c);
+			if(count == null) throw new IllegalArgumentException();
+			if(count == 1) {
+				lastConnection = true;
+				contactCounts.remove(c);
+			} else {
+				contactCounts.put(c, count - 1);
+			}
 		}
+		if(lastConnection)
+			for(ConnectionListener l : listeners) l.contactDisconnected(c);
 	}
 
 	public synchronized Collection<ContactId> getConnectedContacts(
@@ -50,4 +90,8 @@ class ConnectionRegistryImpl implements ConnectionRegistry {
 		List<ContactId> keys = new ArrayList<ContactId>(m.keySet());
 		return Collections.unmodifiableList(keys);
 	}
+
+	public synchronized boolean isConnected(ContactId c) {
+		return contactCounts.containsKey(c);
+	}
 }