diff --git a/briar-api/src/net/sf/briar/api/db/event/LocalRetentionTimeUpdatedEvent.java b/briar-api/src/net/sf/briar/api/db/event/LocalRetentionTimeUpdatedEvent.java
new file mode 100644
index 0000000000000000000000000000000000000000..c28ce107552f56a9d2bebac1e5adbeff6b351d9e
--- /dev/null
+++ b/briar-api/src/net/sf/briar/api/db/event/LocalRetentionTimeUpdatedEvent.java
@@ -0,0 +1,9 @@
+package net.sf.briar.api.db.event;
+
+/**
+ * An event that is broadcast when the retention time of the local database
+ * changes.
+ */
+public class LocalRetentionTimeUpdatedEvent extends DatabaseEvent {
+
+}
diff --git a/briar-api/src/net/sf/briar/api/db/event/LocalSubscriptionsUpdatedEvent.java b/briar-api/src/net/sf/briar/api/db/event/LocalSubscriptionsUpdatedEvent.java
index a0114cee024118ea410c294a84259b1e913177ca..fcd2ddeb6677ebf739096d7671385a0a3fdea546 100644
--- a/briar-api/src/net/sf/briar/api/db/event/LocalSubscriptionsUpdatedEvent.java
+++ b/briar-api/src/net/sf/briar/api/db/event/LocalSubscriptionsUpdatedEvent.java
@@ -1,7 +1,6 @@
 package net.sf.briar.api.db.event;
 
 import java.util.Collection;
-import java.util.Collections;
 
 import net.sf.briar.api.ContactId;
 
@@ -13,10 +12,6 @@ public class LocalSubscriptionsUpdatedEvent extends DatabaseEvent {
 
 	private final Collection<ContactId> affected;
 
-	public LocalSubscriptionsUpdatedEvent() {
-		affected = Collections.emptyList();
-	}
-
 	public LocalSubscriptionsUpdatedEvent(Collection<ContactId> affected) {
 		this.affected = affected;
 	}
diff --git a/briar-api/src/net/sf/briar/api/db/event/RemoteRetentionTimeUpdatedEvent.java b/briar-api/src/net/sf/briar/api/db/event/RemoteRetentionTimeUpdatedEvent.java
new file mode 100644
index 0000000000000000000000000000000000000000..35beb8e4d128ba9e081b67c2d20e5ac73d1af2fd
--- /dev/null
+++ b/briar-api/src/net/sf/briar/api/db/event/RemoteRetentionTimeUpdatedEvent.java
@@ -0,0 +1,20 @@
+package net.sf.briar.api.db.event;
+
+import net.sf.briar.api.ContactId;
+
+/**
+ * An event that is broadcast when the retention time of a contact's database
+ * changes.
+ */
+public class RemoteRetentionTimeUpdatedEvent extends DatabaseEvent {
+
+	private final ContactId contactId;
+
+	public RemoteRetentionTimeUpdatedEvent(ContactId contactId) {
+		this.contactId = contactId;
+	}
+
+	public ContactId getContactId() {
+		return contactId;
+	}
+}
diff --git a/briar-core/src/net/sf/briar/db/DatabaseComponentImpl.java b/briar-core/src/net/sf/briar/db/DatabaseComponentImpl.java
index 515bbf06027b380a28451118ae6d59f8fb00fb83..21da037a759e756ba0f39f498b0410a327e62648 100644
--- a/briar-core/src/net/sf/briar/db/DatabaseComponentImpl.java
+++ b/briar-core/src/net/sf/briar/db/DatabaseComponentImpl.java
@@ -34,11 +34,13 @@ import net.sf.briar.api.db.event.ContactAddedEvent;
 import net.sf.briar.api.db.event.ContactRemovedEvent;
 import net.sf.briar.api.db.event.DatabaseEvent;
 import net.sf.briar.api.db.event.DatabaseListener;
+import net.sf.briar.api.db.event.LocalRetentionTimeUpdatedEvent;
 import net.sf.briar.api.db.event.LocalSubscriptionsUpdatedEvent;
 import net.sf.briar.api.db.event.LocalTransportsUpdatedEvent;
 import net.sf.briar.api.db.event.MessageAddedEvent;
 import net.sf.briar.api.db.event.MessageReceivedEvent;
 import net.sf.briar.api.db.event.RatingChangedEvent;
+import net.sf.briar.api.db.event.RemoteRetentionTimeUpdatedEvent;
 import net.sf.briar.api.db.event.RemoteSubscriptionsUpdatedEvent;
 import net.sf.briar.api.db.event.RemoteTransportsUpdatedEvent;
 import net.sf.briar.api.db.event.TransportAddedEvent;
@@ -46,14 +48,14 @@ import net.sf.briar.api.db.event.TransportRemovedEvent;
 import net.sf.briar.api.lifecycle.ShutdownManager;
 import net.sf.briar.api.protocol.Ack;
 import net.sf.briar.api.protocol.AuthorId;
-import net.sf.briar.api.protocol.RetentionAck;
-import net.sf.briar.api.protocol.RetentionUpdate;
 import net.sf.briar.api.protocol.Group;
 import net.sf.briar.api.protocol.GroupId;
 import net.sf.briar.api.protocol.Message;
 import net.sf.briar.api.protocol.MessageId;
 import net.sf.briar.api.protocol.Offer;
 import net.sf.briar.api.protocol.Request;
+import net.sf.briar.api.protocol.RetentionAck;
+import net.sf.briar.api.protocol.RetentionUpdate;
 import net.sf.briar.api.protocol.SubscriptionAck;
 import net.sf.briar.api.protocol.SubscriptionUpdate;
 import net.sf.briar.api.protocol.TransportAck;
@@ -185,7 +187,6 @@ DatabaseCleaner.Callback {
 		} finally {
 			contactLock.writeLock().unlock();
 		}
-		// Call the listeners outside the lock
 		callListeners(new ContactAddedEvent(c));
 		return c;
 	}
@@ -251,7 +252,6 @@ DatabaseCleaner.Callback {
 		} finally {
 			contactLock.readLock().unlock();
 		}
-		// Call the listeners outside the lock
 		if(added) callListeners(new MessageAddedEvent());
 	}
 
@@ -364,7 +364,6 @@ DatabaseCleaner.Callback {
 		} finally {
 			contactLock.readLock().unlock();
 		}
-		// Call the listeners outside the lock
 		if(added) callListeners(new MessageAddedEvent());
 	}
 
@@ -417,7 +416,6 @@ DatabaseCleaner.Callback {
 		} finally {
 			transportLock.writeLock().unlock();
 		}
-		// Call the listeners outside the lock
 		callListeners(new TransportAddedEvent(t));
 	}
 
@@ -1064,7 +1062,6 @@ DatabaseCleaner.Callback {
 		} finally {
 			transportLock.writeLock().unlock();
 		}
-		// Call the listeners outside the lock
 		if(changed) callListeners(new LocalTransportsUpdatedEvent());
 	}
 
@@ -1119,7 +1116,6 @@ DatabaseCleaner.Callback {
 		} finally {
 			contactLock.readLock().unlock();
 		}
-		// Call the listeners outside the lock
 		callListeners(new MessageReceivedEvent());
 		if(added) callListeners(new MessageAddedEvent());
 	}
@@ -1226,6 +1222,7 @@ DatabaseCleaner.Callback {
 		} finally {
 			contactLock.readLock().unlock();
 		}
+		callListeners(new RemoteRetentionTimeUpdatedEvent(c));
 	}
 
 	public void receiveSubscriptionAck(ContactId c, SubscriptionAck a)
@@ -1274,7 +1271,6 @@ DatabaseCleaner.Callback {
 		} finally {
 			contactLock.readLock().unlock();
 		}
-		// Call the listeners outside the lock
 		callListeners(new RemoteSubscriptionsUpdatedEvent(c));
 	}
 
@@ -1325,7 +1321,6 @@ DatabaseCleaner.Callback {
 		} finally {
 			contactLock.readLock().unlock();
 		}
-		// Call the listeners outside the lock
 		callListeners(new RemoteTransportsUpdatedEvent(c, u.getId()));
 	}
 
@@ -1365,7 +1360,6 @@ DatabaseCleaner.Callback {
 		} finally {
 			contactLock.writeLock().unlock();
 		}
-		// Call the listeners outside the lock
 		callListeners(new ContactRemovedEvent(c));
 	}
 
@@ -1383,7 +1377,6 @@ DatabaseCleaner.Callback {
 		} finally {
 			transportLock.writeLock().unlock();
 		}
-		// Call the listeners outside the lock
 		callListeners(new TransportRemovedEvent(t));
 	}
 
@@ -1443,7 +1436,6 @@ DatabaseCleaner.Callback {
 		} finally {
 			messageLock.writeLock().unlock();
 		}
-		// Call the listeners outside the lock
 		if(changed) callListeners(new RatingChangedEvent(a, r));
 	}
 
@@ -1505,6 +1497,7 @@ DatabaseCleaner.Callback {
 
 	public void setVisibility(GroupId g, Collection<ContactId> visible)
 			throws DbException {
+		Collection<ContactId> affected = new ArrayList<ContactId>();
 		contactLock.writeLock().lock();
 		try {
 			subscriptionLock.writeLock().lock();
@@ -1512,15 +1505,21 @@ DatabaseCleaner.Callback {
 				T txn = db.startTransaction();
 				try {
 					// Use HashSets for O(1) lookups, O(n) overall running time
-					visible = new HashSet<ContactId>(visible);
+					HashSet<ContactId> newVisible =
+							new HashSet<ContactId>(visible);
 					HashSet<ContactId> oldVisible =
 							new HashSet<ContactId>(db.getVisibility(txn, g));
 					// Set the group's visibility for each current contact
 					for(ContactId c : db.getContacts(txn)) {
 						boolean then = oldVisible.contains(c);
-						boolean now = visible.contains(c);
-						if(!then && now) db.addVisibility(txn, c, g);
-						else if(then && !now) db.removeVisibility(txn, c, g);
+						boolean now = newVisible.contains(c);
+						if(!then && now) {
+							db.addVisibility(txn, c, g);
+							affected.add(c);
+						} else if(then && !now) {
+							db.removeVisibility(txn, c, g);
+							affected.add(c);
+						}
 					}
 					db.commitTransaction(txn);
 				} catch(DbException e) {
@@ -1533,6 +1532,7 @@ DatabaseCleaner.Callback {
 		} finally {
 			contactLock.writeLock().unlock();
 		}
+		callListeners(new LocalSubscriptionsUpdatedEvent(affected));
 	}
 
 	public void subscribe(Group g) throws DbException {
@@ -1581,7 +1581,6 @@ DatabaseCleaner.Callback {
 		} finally {
 			contactLock.writeLock().unlock();
 		}
-		// Call the listeners outside the lock
 		callListeners(new LocalSubscriptionsUpdatedEvent(affected));
 	}
 
@@ -1635,6 +1634,7 @@ DatabaseCleaner.Callback {
 		} finally {
 			contactLock.readLock().unlock();
 		}
+		if(removed) callListeners(new LocalRetentionTimeUpdatedEvent());
 		return removed;
 	}