diff --git a/briar-api/src/net/sf/briar/api/db/DatabaseComponent.java b/briar-api/src/net/sf/briar/api/db/DatabaseComponent.java
index 4f26123167ae3643d7cadf26c3f48388488dd84c..67883406cc03e26aedf83ed1bf654008c4db3136 100644
--- a/briar-api/src/net/sf/briar/api/db/DatabaseComponent.java
+++ b/briar-api/src/net/sf/briar/api/db/DatabaseComponent.java
@@ -344,11 +344,18 @@ public interface DatabaseComponent {
 
 	/**
 	 * Makes the given group visible to the given set of contacts and invisible
-	 * to any other contacts.
+	 * to any other current or future contacts.
 	 */
 	void setVisibility(GroupId g, Collection<ContactId> visible)
 			throws DbException;
 
+	/**
+	 * Makes the given group visible or invisible to future contacts by default.
+	 * If <tt>visible</tt> is true, the group is also made visible to all
+	 * current contacts.
+	 */
+	void setVisibleToAll(GroupId g, boolean visible) throws DbException;
+
 	/**
 	 * Subscribes to the given group, or returns false if the user already has
 	 * the maximum number of subscriptions.
diff --git a/briar-core/src/net/sf/briar/db/Database.java b/briar-core/src/net/sf/briar/db/Database.java
index 9fdeae416b6748bc1c98be24aa36dd79cc90d414..05dce999c77cba3b27b7ff14910ca1ee02dec1ad 100644
--- a/briar-core/src/net/sf/briar/db/Database.java
+++ b/briar-core/src/net/sf/briar/db/Database.java
@@ -791,6 +791,13 @@ interface Database<T> {
 	void setTransportUpdateAcked(T txn, ContactId c, TransportId t,
 			long version) throws DbException;
 
+	/**
+	 * Makes the given group visible or invisible to future contacts by default.
+	 * <p>
+	 * Locking: subscription write.
+	 */
+	void setVisibleToAll(T txn, GroupId g, boolean visible) throws DbException;
+
 	/**
 	 * Updates the expiry times of the given messages with respect to the given
 	 * contact, using the given transmission counts and the latency of the
diff --git a/briar-core/src/net/sf/briar/db/DatabaseComponentImpl.java b/briar-core/src/net/sf/briar/db/DatabaseComponentImpl.java
index 4736d6cc8535fc30938d9e3a6138b7a772e4d0c5..f830fe233b8f68b861ff6f934d5b5270f6c83c3c 100644
--- a/briar-core/src/net/sf/briar/db/DatabaseComponentImpl.java
+++ b/briar-core/src/net/sf/briar/db/DatabaseComponentImpl.java
@@ -1982,22 +1982,61 @@ DatabaseCleaner.Callback {
 					if(!db.containsSubscription(txn, g))
 						throw new NoSuchSubscriptionException();
 					// Use HashSets for O(1) lookups, O(n) overall running time
-					HashSet<ContactId> newVisible =
-							new HashSet<ContactId>(visible);
-					HashSet<ContactId> oldVisible =
-							new HashSet<ContactId>(db.getVisibility(txn, g));
+					HashSet<ContactId> now = new HashSet<ContactId>(visible);
+					Collection<ContactId> before = db.getVisibility(txn, g);
+					before = new HashSet<ContactId>(before);
 					// Set the group's visibility for each current contact
 					for(ContactId c : db.getContactIds(txn)) {
-						boolean then = oldVisible.contains(c);
-						boolean now = newVisible.contains(c);
-						if(!then && now) {
+						boolean wasBefore = before.contains(c);
+						boolean isNow = now.contains(c);
+						if(!wasBefore && isNow) {
 							db.addVisibility(txn, c, g);
 							affected.add(c);
-						} else if(then && !now) {
+						} else if(wasBefore && !isNow) {
 							db.removeVisibility(txn, c, g);
 							affected.add(c);
 						}
 					}
+					// Make the group invisible to future contacts
+					db.setVisibleToAll(txn, g, false);
+					db.commitTransaction(txn);
+				} catch(DbException e) {
+					db.abortTransaction(txn);
+					throw e;
+				}
+			} finally {
+				subscriptionLock.writeLock().unlock();
+			}
+		} finally {
+			contactLock.readLock().unlock();
+		}
+		if(!affected.isEmpty())
+			callListeners(new LocalSubscriptionsUpdatedEvent(affected));
+	}
+
+	public void setVisibleToAll(GroupId g, boolean visible) throws DbException {
+		Collection<ContactId> affected = new ArrayList<ContactId>();
+		contactLock.readLock().lock();
+		try {
+			subscriptionLock.writeLock().lock();
+			try {
+				T txn = db.startTransaction();
+				try {
+					if(!db.containsSubscription(txn, g))
+						throw new NoSuchSubscriptionException();
+					// Make the group visible or invisible to future contacts
+					db.setVisibleToAll(txn, g, visible);
+					if(visible) {
+						// Make the group visible to all current contacts
+						Collection<ContactId> before = db.getVisibility(txn, g);
+						before = new HashSet<ContactId>(before);
+						for(ContactId c : db.getContactIds(txn)) {
+							if(!before.contains(c)) {
+								db.addVisibility(txn, c, g);
+								affected.add(c);
+							}
+						}
+					}
 					db.commitTransaction(txn);
 				} catch(DbException e) {
 					db.abortTransaction(txn);
diff --git a/briar-core/src/net/sf/briar/db/JdbcDatabase.java b/briar-core/src/net/sf/briar/db/JdbcDatabase.java
index 580d4e8a37ba396304aa23092b352fbed8f96d99..8e3033e88c61a3daaf5dc7799399a8a4baa27591 100644
--- a/briar-core/src/net/sf/briar/db/JdbcDatabase.java
+++ b/briar-core/src/net/sf/briar/db/JdbcDatabase.java
@@ -103,6 +103,7 @@ abstract class JdbcDatabase implements Database<Connection> {
 					+ " (groupId HASH NOT NULL,"
 					+ " name VARCHAR NOT NULL,"
 					+ " publicKey BINARY," // Null for unrestricted groups
+					+ " visibleToAll BOOLEAN NOT NULL,"
 					+ " PRIMARY KEY (groupId))";
 
 	// Locking: subscription
@@ -573,6 +574,27 @@ abstract class JdbcDatabase implements Database<Connection> {
 			if(rs.next()) throw new DbStateException();
 			rs.close();
 			ps.close();
+			// Make groups that are visible to everyone visible to this contact
+			sql = "SELECT groupId FROM groups WHERE visibleToAll = TRUE";
+			ps = txn.prepareStatement(sql);
+			rs = ps.executeQuery();
+			Collection<byte[]> ids = new ArrayList<byte[]>();
+			while(rs.next()) ids.add(rs.getBytes(1));
+			rs.close();
+			ps.close();
+			if(!ids.isEmpty()) {
+				sql = "INSERT INTO groupVisibilities (contactId, groupId)"
+						+ " VALUES (?, ?)";
+				ps = txn.prepareStatement(sql);
+				ps.setInt(1, c.getInt());
+				for(byte[] id : ids) {
+					ps.setBytes(2, id);
+					ps.addBatch();
+				}
+				affected = ps.executeUpdate();
+				if(affected != ids.size()) throw new DbStateException();
+				ps.close();
+			}
 			// Create a connection time row
 			sql = "INSERT INTO connectionTimes (contactId, lastConnected)"
 					+ " VALUES (?, ?)";
@@ -893,8 +915,8 @@ abstract class JdbcDatabase implements Database<Connection> {
 			ps.close();
 			if(count > MAX_SUBSCRIPTIONS) throw new DbStateException();
 			if(count == MAX_SUBSCRIPTIONS) return false;
-			sql = "INSERT INTO groups (groupId, name, publicKey)"
-					+ " VALUES (?, ?, ?)";
+			sql = "INSERT INTO groups (groupId, name, publicKey, visibleToAll)"
+					+ " VALUES (?, ?, ?, FALSE)";
 			ps = txn.prepareStatement(sql);
 			ps.setBytes(1, g.getId().getBytes());
 			ps.setString(2, g.getName());
@@ -3376,6 +3398,23 @@ abstract class JdbcDatabase implements Database<Connection> {
 		}
 	}
 
+	public void setVisibleToAll(Connection txn, GroupId g, boolean visible)
+			throws DbException {
+		PreparedStatement ps = null;
+		try {
+			String sql = "UPDATE groups SET visibleToAll = ? WHERE groupId = ?";
+			ps = txn.prepareStatement(sql);
+			ps.setBoolean(1, visible);
+			ps.setBytes(2, g.getBytes());
+			int affected = ps.executeUpdate();
+			if(affected > 1) throw new DbStateException();
+			ps.close();
+		} catch(SQLException e) {
+			tryToClose(ps);
+			throw new DbException(e);
+		}
+	}
+
 	public void updateExpiryTimes(Connection txn, ContactId c,
 			Map<MessageId, Integer> sent, long maxLatency) throws DbException {
 		long now = clock.currentTimeMillis();
diff --git a/briar-tests/src/net/sf/briar/db/DatabaseComponentTest.java b/briar-tests/src/net/sf/briar/db/DatabaseComponentTest.java
index 349e48bdf8f193e79dbd1d0927a49f32e0839851..84d3a10d6f5eaa125a57f404cf2a68366f4fc4e6 100644
--- a/briar-tests/src/net/sf/briar/db/DatabaseComponentTest.java
+++ b/briar-tests/src/net/sf/briar/db/DatabaseComponentTest.java
@@ -1729,6 +1729,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 			oneOf(database).getContactIds(txn);
 			will(returnValue(both));
 			oneOf(database).removeVisibility(txn, contactId1, groupId);
+			oneOf(database).setVisibleToAll(txn, groupId, false);
 			oneOf(database).commitTransaction(txn);
 			oneOf(listener).eventOccurred(with(any(
 					LocalSubscriptionsUpdatedEvent.class)));
@@ -1745,7 +1746,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 	@Test
 	public void testNotChangingVisibilityDoesNotCallListeners()
 			throws Exception {
-		final ContactId contactId1 = new ContactId(234);
+		final ContactId contactId1 = new ContactId(123);
 		final Collection<ContactId> both = Arrays.asList(contactId, contactId1);
 		Mockery context = new Mockery();
 		@SuppressWarnings("unchecked")
@@ -1762,6 +1763,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 			will(returnValue(both));
 			oneOf(database).getContactIds(txn);
 			will(returnValue(both));
+			oneOf(database).setVisibleToAll(txn, groupId, false);
 			oneOf(database).commitTransaction(txn);
 		}});
 		DatabaseComponent db = createDatabaseComponent(database, cleaner,
@@ -1773,6 +1775,57 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 		context.assertIsSatisfied();
 	}
 
+	@Test
+	public void testSettingVisibleToAllTrueAffectsCurrentContacts()
+			throws Exception {
+		final ContactId contactId1 = new ContactId(123);
+		final Collection<ContactId> both = Arrays.asList(contactId, contactId1);
+		Mockery context = new Mockery();
+		@SuppressWarnings("unchecked")
+		final Database<Object> database = context.mock(Database.class);
+		final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
+		final ShutdownManager shutdown = context.mock(ShutdownManager.class);
+		final DatabaseListener listener = context.mock(DatabaseListener.class);
+		context.checking(new Expectations() {{
+			// setVisibility()
+			oneOf(database).startTransaction();
+			will(returnValue(txn));
+			oneOf(database).containsSubscription(txn, groupId);
+			will(returnValue(true));
+			oneOf(database).getVisibility(txn, groupId);
+			will(returnValue(Collections.emptyList()));
+			oneOf(database).getContactIds(txn);
+			will(returnValue(both));
+			oneOf(database).addVisibility(txn, contactId, groupId);
+			oneOf(database).setVisibleToAll(txn, groupId, false);
+			oneOf(database).commitTransaction(txn);
+			oneOf(listener).eventOccurred(with(any(
+					LocalSubscriptionsUpdatedEvent.class)));
+			// setVisibleToAll()
+			oneOf(database).startTransaction();
+			will(returnValue(txn));
+			oneOf(database).containsSubscription(txn, groupId);
+			will(returnValue(true));
+			oneOf(database).setVisibleToAll(txn, groupId, true);
+			oneOf(database).getVisibility(txn, groupId);
+			will(returnValue(Arrays.asList(contactId)));
+			oneOf(database).getContactIds(txn);
+			will(returnValue(both));
+			oneOf(database).addVisibility(txn, contactId1, groupId);
+			oneOf(database).commitTransaction(txn);
+			oneOf(listener).eventOccurred(with(any(
+					LocalSubscriptionsUpdatedEvent.class)));
+		}});
+		DatabaseComponent db = createDatabaseComponent(database, cleaner,
+				shutdown);
+
+		db.addListener(listener);
+		db.setVisibility(groupId, Arrays.asList(contactId));
+		db.setVisibleToAll(groupId, true);
+
+		context.assertIsSatisfied();
+	}
+
 	@Test
 	public void testTemporarySecrets() throws Exception {
 		Mockery context = new Mockery();
diff --git a/briar-tests/src/net/sf/briar/db/H2DatabaseTest.java b/briar-tests/src/net/sf/briar/db/H2DatabaseTest.java
index 87ec46004a0be0ea1a3aa400be0910ef44b6af91..b461ad1e3a9869607a26bcbe9b0d2e9891377c3d 100644
--- a/briar-tests/src/net/sf/briar/db/H2DatabaseTest.java
+++ b/briar-tests/src/net/sf/briar/db/H2DatabaseTest.java
@@ -873,6 +873,7 @@ public class H2DatabaseTest extends BriarTestCase {
 		db.addLocalAuthor(txn, localAuthor);
 		assertEquals(contactId, db.addContact(txn, author, localAuthorId));
 		db.addSubscription(txn, group);
+		db.addVisibility(txn, contactId, groupId);
 		db.setSubscriptions(txn, contactId, Arrays.asList(group), 1);
 
 		// The message is not in the database
@@ -891,6 +892,7 @@ public class H2DatabaseTest extends BriarTestCase {
 		db.addLocalAuthor(txn, localAuthor);
 		assertEquals(contactId, db.addContact(txn, author, localAuthorId));
 		db.addSubscription(txn, group);
+		db.addVisibility(txn, contactId, groupId);
 		db.setSubscriptions(txn, contactId, Arrays.asList(group), 1);
 		db.addGroupMessage(txn, message);
 
@@ -915,6 +917,7 @@ public class H2DatabaseTest extends BriarTestCase {
 		db.addLocalAuthor(txn, localAuthor);
 		assertEquals(contactId, db.addContact(txn, author, localAuthorId));
 		db.addSubscription(txn, group);
+		db.addVisibility(txn, contactId, groupId);
 		db.setSubscriptions(txn, contactId, Arrays.asList(group), 1);
 		db.addGroupMessage(txn, message);
 
@@ -1028,6 +1031,7 @@ public class H2DatabaseTest extends BriarTestCase {
 		db.addLocalAuthor(txn, localAuthor);
 		assertEquals(contactId, db.addContact(txn, author, localAuthorId));
 		db.addSubscription(txn, group);
+		db.addVisibility(txn, contactId, groupId);
 		db.addGroupMessage(txn, message);
 		db.addStatus(txn, contactId, messageId, false);
 
@@ -1048,8 +1052,8 @@ public class H2DatabaseTest extends BriarTestCase {
 		db.addLocalAuthor(txn, localAuthor);
 		assertEquals(contactId, db.addContact(txn, author, localAuthorId));
 		db.addSubscription(txn, group);
-		db.addGroupMessage(txn, message);
 		db.setSubscriptions(txn, contactId, Arrays.asList(group), 1);
+		db.addGroupMessage(txn, message);
 		db.addStatus(txn, contactId, messageId, false);
 
 		// The subscription is not visible