From 51b3a10be213b99103911396ee42ef06e96449f1 Mon Sep 17 00:00:00 2001
From: akwizgran <akwizgran@users.sourceforge.net>
Date: Fri, 20 Dec 2013 13:32:36 +0000
Subject: [PATCH] Removed public/private groups from the wire protocol.

The distinction between inbox groups and other groups can be maintained
internally, there's no need to represent it on the wire.
---
 .../groups/ConfigureGroupActivity.java        |   6 +-
 .../android/groups/CreateGroupActivity.java   |   6 +-
 .../android/groups/GroupListActivity.java     |  22 ++--
 .../android/groups/ManageGroupsActivity.java  |   6 +-
 .../groups/WriteGroupPostActivity.java        |   5 +-
 .../sf/briar/api/db/DatabaseComponent.java    |  21 ++--
 .../src/net/sf/briar/api/messaging/Group.java |   9 +-
 .../sf/briar/api/messaging/GroupFactory.java  |   4 +-
 briar-core/src/net/sf/briar/db/Database.java  |  28 ++---
 .../sf/briar/db/DatabaseComponentImpl.java    |  17 +--
 .../src/net/sf/briar/db/JdbcDatabase.java     | 109 ++++++------------
 .../net/sf/briar/invitation/Connector.java    |   2 +-
 .../sf/briar/messaging/GroupFactoryImpl.java  |   9 +-
 .../net/sf/briar/messaging/GroupReader.java   |   3 +-
 .../briar/messaging/MessageFactoryImpl.java   |   1 -
 .../sf/briar/messaging/PacketWriterImpl.java  |   1 -
 .../net/sf/briar/ProtocolIntegrationTest.java |   2 +-
 .../sf/briar/db/DatabaseComponentTest.java    |  30 +++--
 .../src/net/sf/briar/db/H2DatabaseTest.java   |  51 ++++----
 .../net/sf/briar/messaging/ConstantsTest.java |   4 +-
 .../SimplexMessagingIntegrationTest.java      |   2 +-
 21 files changed, 139 insertions(+), 199 deletions(-)

diff --git a/briar-android/src/net/sf/briar/android/groups/ConfigureGroupActivity.java b/briar-android/src/net/sf/briar/android/groups/ConfigureGroupActivity.java
index 31960b2b12..59a34d373e 100644
--- a/briar-android/src/net/sf/briar/android/groups/ConfigureGroupActivity.java
+++ b/briar-android/src/net/sf/briar/android/groups/ConfigureGroupActivity.java
@@ -77,7 +77,7 @@ SelectContactsDialog.Listener {
 		setTitle(name);
 		b = i.getByteArrayExtra("net.sf.briar.GROUP_SALT");
 		if(b == null) throw new IllegalStateException();
-		group = new Group(id, name, b, false);
+		group = new Group(id, name, b);
 		subscribed = i.getBooleanExtra("net.sf.briar.SUBSCRIBED", false);
 		boolean all = i.getBooleanExtra("net.sf.briar.VISIBLE_TO_ALL", false);
 
@@ -208,8 +208,8 @@ SelectContactsDialog.Listener {
 					long now = System.currentTimeMillis();
 					if(subscribe) {
 						if(!wasSubscribed) db.addGroup(group);
-						db.setVisibleToAll(group, all);
-						if(!all) db.setVisibility(group, visible);
+						db.setVisibleToAll(group.getId(), all);
+						if(!all) db.setVisibility(group.getId(), visible);
 					} else if(wasSubscribed) {
 						db.removeGroup(group);
 					}
diff --git a/briar-android/src/net/sf/briar/android/groups/CreateGroupActivity.java b/briar-android/src/net/sf/briar/android/groups/CreateGroupActivity.java
index 72021d5dc2..5ea8409f61 100644
--- a/briar-android/src/net/sf/briar/android/groups/CreateGroupActivity.java
+++ b/briar-android/src/net/sf/briar/android/groups/CreateGroupActivity.java
@@ -173,11 +173,11 @@ SelectContactsDialog.Listener {
 				public void run() {
 					try {
 						lifecycleManager.waitForDatabase();
-						Group g = groupFactory.createGroup(name, false);
+						Group g = groupFactory.createGroup(name);
 						long now = System.currentTimeMillis();
 						db.addGroup(g);
-						if(all) db.setVisibleToAll(g, true);
-						else db.setVisibility(g, visible);
+						if(all) db.setVisibleToAll(g.getId(), true);
+						else db.setVisibility(g.getId(), visible);
 						long duration = System.currentTimeMillis() - now;
 						if(LOG.isLoggable(INFO))
 							LOG.info("Storing group took " + duration + " ms");
diff --git a/briar-android/src/net/sf/briar/android/groups/GroupListActivity.java b/briar-android/src/net/sf/briar/android/groups/GroupListActivity.java
index 485685715d..894e67c687 100644
--- a/briar-android/src/net/sf/briar/android/groups/GroupListActivity.java
+++ b/briar-android/src/net/sf/briar/android/groups/GroupListActivity.java
@@ -15,6 +15,8 @@ import static net.sf.briar.android.util.CommonLayoutParams.MATCH_WRAP_1;
 
 import java.util.Collection;
 import java.util.Comparator;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.Executor;
 import java.util.logging.Logger;
 
@@ -58,6 +60,9 @@ OnItemClickListener {
 	private static final Logger LOG =
 			Logger.getLogger(GroupListActivity.class.getName());
 
+	private final Map<GroupId,GroupId> groups =
+			new ConcurrentHashMap<GroupId,GroupId>();
+
 	private GroupListAdapter adapter = null;
 	private ListView list = null;
 	private ListLoadingProgressBar loading = null;
@@ -140,7 +145,6 @@ OnItemClickListener {
 					long now = System.currentTimeMillis();
 					for(GroupStatus s : db.getAvailableGroups()) {
 						Group g = s.getGroup();
-						if(g.isPrivate()) continue;
 						if(s.isSubscribed()) {
 							try {
 								Collection<MessageHeader> headers =
@@ -173,6 +177,7 @@ OnItemClickListener {
 	private void clearHeaders() {
 		runOnUiThread(new Runnable() {
 			public void run() {
+				groups.clear();
 				list.setVisibility(GONE);
 				loading.setVisibility(VISIBLE);
 				adapter.clear();
@@ -185,10 +190,12 @@ OnItemClickListener {
 			final Collection<MessageHeader> headers) {
 		runOnUiThread(new Runnable() {
 			public void run() {
+				GroupId id = g.getId();
+				groups.put(id, id);
 				list.setVisibility(VISIBLE);
 				loading.setVisibility(GONE);
 				// Remove the old item, if any
-				GroupListItem item = findGroup(g.getId());
+				GroupListItem item = findGroup(id);
 				if(item != null) adapter.remove(item);
 				// Add a new item
 				adapter.add(new GroupListItem(g, headers));
@@ -243,7 +250,7 @@ OnItemClickListener {
 	public void eventOccurred(DatabaseEvent e) {
 		if(e instanceof MessageAddedEvent) {
 			Group g = ((MessageAddedEvent) e).getGroup();
-			if(!g.isPrivate()) {
+			if(groups.containsKey(g.getId())) {
 				if(LOG.isLoggable(INFO)) LOG.info("Message added, reloading");
 				loadHeaders(g);
 			}
@@ -259,7 +266,7 @@ OnItemClickListener {
 			loadHeaders();
 		} else if(e instanceof SubscriptionRemovedEvent) {
 			Group g = ((SubscriptionRemovedEvent) e).getGroup();
-			if(!g.isPrivate()) {
+			if(groups.containsKey(g.getId())) {
 				// Reload the group, expecting NoSuchSubscriptionException
 				if(LOG.isLoggable(INFO)) LOG.info("Group removed, reloading");
 				loadHeaders(g);
@@ -299,6 +306,7 @@ OnItemClickListener {
 			public void run() {
 				GroupListItem item = findGroup(g);
 				if(item != null) {
+					groups.remove(g);
 					adapter.remove(item);
 					adapter.notifyDataSetChanged();
 					selectFirstUnread();
@@ -314,10 +322,8 @@ OnItemClickListener {
 					lifecycleManager.waitForDatabase();
 					int available = 0;
 					long now = System.currentTimeMillis();
-					for(GroupStatus s : db.getAvailableGroups()) {
-						if(!s.isSubscribed() && !s.getGroup().isPrivate())
-							available++;
-					}
+					for(GroupStatus s : db.getAvailableGroups())
+						if(!s.isSubscribed()) available++;
 					long duration = System.currentTimeMillis() - now;
 					if(LOG.isLoggable(INFO))
 						LOG.info("Loading available took " + duration + " ms");
diff --git a/briar-android/src/net/sf/briar/android/groups/ManageGroupsActivity.java b/briar-android/src/net/sf/briar/android/groups/ManageGroupsActivity.java
index a9be14f8fd..282fa6d29f 100644
--- a/briar-android/src/net/sf/briar/android/groups/ManageGroupsActivity.java
+++ b/briar-android/src/net/sf/briar/android/groups/ManageGroupsActivity.java
@@ -5,7 +5,6 @@ import static java.util.logging.Level.WARNING;
 import static net.sf.briar.android.groups.ManageGroupsItem.NONE;
 import static net.sf.briar.android.util.CommonLayoutParams.MATCH_MATCH;
 
-import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Comparator;
 import java.util.concurrent.Executor;
@@ -76,10 +75,7 @@ implements DatabaseListener, OnItemClickListener {
 				try {
 					lifecycleManager.waitForDatabase();
 					long now = System.currentTimeMillis();
-					Collection<GroupStatus> available =
-							new ArrayList<GroupStatus>();
-					for(GroupStatus s : db.getAvailableGroups())
-						if(!s.getGroup().isPrivate()) available.add(s);
+					Collection<GroupStatus> available = db.getAvailableGroups();
 					long duration = System.currentTimeMillis() - now;
 					if(LOG.isLoggable(INFO))
 						LOG.info("Load took " + duration + " ms");
diff --git a/briar-android/src/net/sf/briar/android/groups/WriteGroupPostActivity.java b/briar-android/src/net/sf/briar/android/groups/WriteGroupPostActivity.java
index fc79780222..14f4f7f72f 100644
--- a/briar-android/src/net/sf/briar/android/groups/WriteGroupPostActivity.java
+++ b/briar-android/src/net/sf/briar/android/groups/WriteGroupPostActivity.java
@@ -35,6 +35,7 @@ import net.sf.briar.api.db.DbException;
 import net.sf.briar.api.lifecycle.LifecycleManager;
 import net.sf.briar.api.messaging.Group;
 import net.sf.briar.api.messaging.GroupId;
+import net.sf.briar.api.messaging.GroupStatus;
 import net.sf.briar.api.messaging.Message;
 import net.sf.briar.api.messaging.MessageFactory;
 import net.sf.briar.api.messaging.MessageId;
@@ -215,8 +216,8 @@ implements OnItemSelectedListener, OnClickListener {
 					lifecycleManager.waitForDatabase();
 					long now = System.currentTimeMillis();
 					Collection<Group> groups = new ArrayList<Group>();
-					for(Group g : db.getGroups())
-						if(!g.isPrivate()) groups.add(g);
+					for(GroupStatus s : db.getAvailableGroups())
+						if(s.isSubscribed()) groups.add(s.getGroup());
 					long duration = System.currentTimeMillis() - now;
 					if(LOG.isLoggable(INFO))
 						LOG.info("Loading groups took " + duration + " ms");
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 d5ff901db4..f7ba57da9d 100644
--- a/briar-api/src/net/sf/briar/api/db/DatabaseComponent.java
+++ b/briar-api/src/net/sf/briar/api/db/DatabaseComponent.java
@@ -160,7 +160,10 @@ public interface DatabaseComponent {
 	Collection<TransportUpdate> generateTransportUpdates(ContactId c,
 			long maxLatency) throws DbException;
 
-	/** Returns the status of all groups to which the user can subscribe. */
+	/**
+	 * Returns the status of all groups to which the user subscribes or can
+	 * subscribe, excluding inbox groups.
+	 */
 	Collection<GroupStatus> getAvailableGroups() throws DbException;
 
 	/** Returns the configuration for the given transport. */
@@ -326,8 +329,8 @@ public interface DatabaseComponent {
 			long centre, byte[] bitmap) throws DbException;
 
 	/**
-	 * Makes a private group visible to the given contact, adds it to the
-	 * contact's subscriptions, and sets it as the inbox group for the contact.
+	 * Makes a group visible to the given contact, adds it to the contact's
+	 * subscriptions, and sets it as the inbox group for the contact.
 	 */
 	public void setInboxGroup(ContactId c, Group g) throws DbException;
 
@@ -348,15 +351,15 @@ public interface DatabaseComponent {
 	void setSeen(ContactId c, Collection<MessageId> seen) throws DbException;
 
 	/**
-	 * Makes a public group visible to the given set of contacts and invisible
-	 * to any other current or future contacts.
+	 * Makes a group visible to the given set of contacts and invisible to any
+	 * other current or future contacts.
 	 */
-	void setVisibility(Group g, Collection<ContactId> visible)
+	void setVisibility(GroupId g, Collection<ContactId> visible)
 			throws DbException;
 
 	/**
-	 * Makes a public group visible to all current and future contacts, or
-	 * invisible to future contacts.
+	 * Makes a group visible to all current and future contacts, or invisible
+	 * to future contacts.
 	 */
-	void setVisibleToAll(Group g, boolean all) throws DbException;
+	void setVisibleToAll(GroupId g, boolean all) throws DbException;
 }
diff --git a/briar-api/src/net/sf/briar/api/messaging/Group.java b/briar-api/src/net/sf/briar/api/messaging/Group.java
index 37b1d376c1..a778b24dff 100644
--- a/briar-api/src/net/sf/briar/api/messaging/Group.java
+++ b/briar-api/src/net/sf/briar/api/messaging/Group.java
@@ -6,13 +6,11 @@ public class Group {
 	private final GroupId id;
 	private final String name;
 	private final byte[] salt;
-	private final boolean isPrivate;
 
-	public Group(GroupId id, String name, byte[] salt, boolean isPrivate) {
+	public Group(GroupId id, String name, byte[] salt) {
 		this.id = id;
 		this.name = name;
 		this.salt = salt;
-		this.isPrivate = isPrivate;
 	}
 
 	/** Returns the group's unique identifier. */
@@ -33,11 +31,6 @@ public class Group {
 		return salt;
 	}
 
-	/** Returns true if the group is private. */
-	public boolean isPrivate() {
-		return isPrivate;
-	}
-
 	@Override
 	public int hashCode() {
 		return id.hashCode();
diff --git a/briar-api/src/net/sf/briar/api/messaging/GroupFactory.java b/briar-api/src/net/sf/briar/api/messaging/GroupFactory.java
index ea3d905342..a7aea03576 100644
--- a/briar-api/src/net/sf/briar/api/messaging/GroupFactory.java
+++ b/briar-api/src/net/sf/briar/api/messaging/GroupFactory.java
@@ -3,8 +3,8 @@ package net.sf.briar.api.messaging;
 public interface GroupFactory {
 
 	/** Creates a group with the given name and a random salt. */
-	Group createGroup(String name, boolean isPrivate);
+	Group createGroup(String name);
 
 	/** Creates a group with the given name and salt. */
-	Group createGroup(String name, byte[] salt, boolean isPrivate);
+	Group createGroup(String name, byte[] salt);
 }
diff --git a/briar-core/src/net/sf/briar/db/Database.java b/briar-core/src/net/sf/briar/db/Database.java
index 888f46cfde..2d47642b25 100644
--- a/briar-core/src/net/sf/briar/db/Database.java
+++ b/briar-core/src/net/sf/briar/db/Database.java
@@ -152,11 +152,11 @@ interface Database<T> {
 			throws DbException;
 
 	/**
-	 * Makes a public group visible to the given contact.
+	 * Makes a group visible to the given contact.
 	 * <p>
 	 * Locking: subscription write.
 	 */
-	void addVisibility(T txn, ContactId c, Group g) throws DbException;
+	void addVisibility(T txn, ContactId c, GroupId g) throws DbException;
 
 	/**
 	 * Returns true if the database contains the given contact.
@@ -217,7 +217,8 @@ interface Database<T> {
 			throws DbException;
 
 	/**
-	 * Returns the status of all groups to which the user can subscribe.
+	 * Returns the status of all groups to which the user subscribes or can
+	 * subscribe, excluding inbox groups.
 	 * <p>
 	 * Locking: subscription read.
 	 */
@@ -522,15 +523,6 @@ interface Database<T> {
 	 */
 	Collection<ContactId> getVisibility(T txn, GroupId g) throws DbException;
 
-	/**
-	 * Returns the IDs of all private groups that are visible to the given
-	 * contact.
-	 * <p>
-	 * Locking: subscription read.
-	 */
-	Collection<GroupId> getVisiblePrivateGroups(T txn, ContactId c)
-			throws DbException;
-
 	/**
 	 * Increments the outgoing connection counter for the given endpoint
 	 * in the given rotation period and returns the old value, or -1 if the
@@ -616,11 +608,11 @@ interface Database<T> {
 	void removeTransport(T txn, TransportId t) throws DbException;
 
 	/**
-	 * Makes a public group invisible to the given contact.
+	 * Makes a group invisible to the given contact.
 	 * <p>
 	 * Locking: subscription write.
 	 */
-	void removeVisibility(T txn, ContactId c, Group g) throws DbException;
+	void removeVisibility(T txn, ContactId c, GroupId g) throws DbException;
 
 	/**
 	 * Sets the connection reordering window for the given endpoint in the
@@ -642,8 +634,8 @@ interface Database<T> {
 			long version) throws DbException;
 
 	/**
-	 * Makes a private group visible to the given contact, adds it to the
-	 * contact's subscriptions, and sets it as the inbox group for the contact.
+	 * Makes a group visible to the given contact, adds it to the contact's
+	 * subscriptions, and sets it as the inbox group for the contact.
 	 * <p>
 	 * Locking: contact read, message write, subscription write.
 	 */
@@ -732,11 +724,11 @@ interface Database<T> {
 			long version) throws DbException;
 
 	/**
-	 * Makes a public group visible or invisible to future contacts by default.
+	 * Makes a group visible or invisible to future contacts by default.
 	 * <p>
 	 * Locking: subscription write.
 	 */
-	void setVisibleToAll(T txn, Group g, boolean all) throws DbException;
+	void setVisibleToAll(T txn, GroupId g, boolean all) throws DbException;
 
 	/**
 	 * Updates the expiry times of the given messages with respect to the given
diff --git a/briar-core/src/net/sf/briar/db/DatabaseComponentImpl.java b/briar-core/src/net/sf/briar/db/DatabaseComponentImpl.java
index a5df7e447b..f49b984fff 100644
--- a/briar-core/src/net/sf/briar/db/DatabaseComponentImpl.java
+++ b/briar-core/src/net/sf/briar/db/DatabaseComponentImpl.java
@@ -1770,7 +1770,6 @@ DatabaseCleaner.Callback {
 	}
 
 	public void setInboxGroup(ContactId c, Group g) throws DbException {
-		if(!g.isPrivate()) throw new IllegalArgumentException();
 		contactLock.readLock().lock();
 		try {
 			messageLock.writeLock().lock();
@@ -1871,9 +1870,8 @@ DatabaseCleaner.Callback {
 		}
 	}
 
-	public void setVisibility(Group g, Collection<ContactId> visible)
+	public void setVisibility(GroupId g, Collection<ContactId> visible)
 			throws DbException {
-		if(g.isPrivate()) throw new IllegalArgumentException();
 		Collection<ContactId> affected = new ArrayList<ContactId>();
 		contactLock.readLock().lock();
 		try {
@@ -1881,12 +1879,11 @@ DatabaseCleaner.Callback {
 			try {
 				T txn = db.startTransaction();
 				try {
-					if(!db.containsGroup(txn, g.getId()))
+					if(!db.containsGroup(txn, g))
 						throw new NoSuchSubscriptionException();
 					// Use HashSets for O(1) lookups, O(n) overall running time
 					HashSet<ContactId> now = new HashSet<ContactId>(visible);
-					Collection<ContactId> before =
-							db.getVisibility(txn, g.getId());
+					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)) {
@@ -1917,8 +1914,7 @@ DatabaseCleaner.Callback {
 			callListeners(new LocalSubscriptionsUpdatedEvent(affected));
 	}
 
-	public void setVisibleToAll(Group g, boolean all) throws DbException {
-		if(g.isPrivate()) throw new IllegalArgumentException();
+	public void setVisibleToAll(GroupId g, boolean all) throws DbException {
 		Collection<ContactId> affected = new ArrayList<ContactId>();
 		contactLock.readLock().lock();
 		try {
@@ -1926,14 +1922,13 @@ DatabaseCleaner.Callback {
 			try {
 				T txn = db.startTransaction();
 				try {
-					if(!db.containsGroup(txn, g.getId()))
+					if(!db.containsGroup(txn, g))
 						throw new NoSuchSubscriptionException();
 					// Make the group visible or invisible to future contacts
 					db.setVisibleToAll(txn, g, all);
 					if(all) {
 						// Make the group visible to all current contacts
-						Collection<ContactId> before =
-								db.getVisibility(txn, g.getId());
+						Collection<ContactId> before = db.getVisibility(txn, g);
 						before = new HashSet<ContactId>(before);
 						for(ContactId c : db.getContactIds(txn)) {
 							if(!before.contains(c)) {
diff --git a/briar-core/src/net/sf/briar/db/JdbcDatabase.java b/briar-core/src/net/sf/briar/db/JdbcDatabase.java
index 88b940cfc7..9c07177e34 100644
--- a/briar-core/src/net/sf/briar/db/JdbcDatabase.java
+++ b/briar-core/src/net/sf/briar/db/JdbcDatabase.java
@@ -88,7 +88,6 @@ abstract class JdbcDatabase implements Database<Connection> {
 					+ " (groupId HASH NOT NULL,"
 					+ " name VARCHAR NOT NULL,"
 					+ " salt BINARY NOT NULL,"
-					+ " private BOOLEAN NOT NULL,"
 					+ " visibleToAll BOOLEAN NOT NULL,"
 					+ " PRIMARY KEY (groupId))";
 
@@ -112,7 +111,6 @@ abstract class JdbcDatabase implements Database<Connection> {
 					+ " groupId HASH NOT NULL," // Not a foreign key
 					+ " name VARCHAR NOT NULL,"
 					+ " salt BINARY NOT NULL,"
-					+ " private BOOLEAN NOT NULL,"
 					+ " PRIMARY KEY (contactId, groupId),"
 					+ " FOREIGN KEY (contactId)"
 					+ " REFERENCES contacts (contactId)"
@@ -646,13 +644,12 @@ abstract class JdbcDatabase implements Database<Connection> {
 		PreparedStatement ps = null;
 		try {
 			String sql = "INSERT INTO groups"
-					+ " (groupId, name, salt, private, visibleToAll)"
-					+ " VALUES (?, ?, ?, ?, FALSE)";
+					+ " (groupId, name, salt, visibleToAll)"
+					+ " VALUES (?, ?, ?, FALSE)";
 			ps = txn.prepareStatement(sql);
 			ps.setBytes(1, g.getId().getBytes());
 			ps.setString(2, g.getName());
 			ps.setBytes(3, g.getSalt());
-			ps.setBoolean(4, g.isPrivate());
 			int affected = ps.executeUpdate();
 			if(affected != 1) throw new DbStateException();
 			ps.close();
@@ -900,9 +897,8 @@ abstract class JdbcDatabase implements Database<Connection> {
 		}
 	}
 
-	public void addVisibility(Connection txn, ContactId c, Group g)
+	public void addVisibility(Connection txn, ContactId c, GroupId g)
 			throws DbException {
-		if(g.isPrivate()) throw new IllegalArgumentException();
 		PreparedStatement ps = null;
 		try {
 			String sql = "INSERT INTO groupVisibilities"
@@ -910,7 +906,7 @@ abstract class JdbcDatabase implements Database<Connection> {
 					+ " VALUES (?, ?, FALSE)";
 			ps = txn.prepareStatement(sql);
 			ps.setInt(1, c.getInt());
-			ps.setBytes(2, g.getId().getBytes());
+			ps.setBytes(2, g.getBytes());
 			int affected = ps.executeUpdate();
 			if(affected != 1) throw new DbStateException();
 			ps.close();
@@ -1120,9 +1116,12 @@ abstract class JdbcDatabase implements Database<Connection> {
 		PreparedStatement ps = null;
 		ResultSet rs = null;
 		try {
-			// Add all subscribed groups to the list
-			String sql = "SELECT groupId, name, salt, private, visibleToAll"
-					+ " FROM groups";
+			// Add all subscribed groups to the list, except inbox groups
+			String sql = "SELECT DISTINCT g.groupId, name, salt, visibleToAll"
+					+ " FROM groups AS g"
+					+ " LEFT OUTER JOIN groupVisibilities AS gv"
+					+ " ON g.groupId = gv.groupId"
+					+ " WHERE inbox = FALSE OR inbox IS NULL";
 			ps = txn.prepareStatement(sql);
 			rs = ps.executeQuery();
 			List<GroupStatus> groups = new ArrayList<GroupStatus>();
@@ -1130,15 +1129,14 @@ abstract class JdbcDatabase implements Database<Connection> {
 				GroupId id = new GroupId(rs.getBytes(1));
 				String name = rs.getString(2);
 				byte[] salt = rs.getBytes(3);
-				boolean isPrivate = rs.getBoolean(4);
-				Group group = new Group(id, name, salt, isPrivate);
-				boolean visibleToAll = rs.getBoolean(5);
+				Group group = new Group(id, name, salt);
+				boolean visibleToAll = rs.getBoolean(4);
 				groups.add(new GroupStatus(group, true, visibleToAll));
 			}
 			rs.close();
 			ps.close();
 			// Add all unsubscribed groups to the list
-			sql = "SELECT DISTINCT cg.groupId, cg.name, cg.salt, cg.private"
+			sql = "SELECT DISTINCT cg.groupId, cg.name, cg.salt"
 					+ " FROM contactGroups AS cg"
 					+ " LEFT OUTER JOIN groups AS g"
 					+ " ON cg.groupId = g.groupId"
@@ -1149,8 +1147,7 @@ abstract class JdbcDatabase implements Database<Connection> {
 				GroupId id = new GroupId(rs.getBytes(1));
 				String name = rs.getString(2);
 				byte[] salt = rs.getBytes(3);
-				boolean isPrivate = rs.getBoolean(4);
-				Group group = new Group(id, name, salt, isPrivate);
+				Group group = new Group(id, name, salt);
 				groups.add(new GroupStatus(group, false, false));
 			}
 			rs.close();
@@ -1313,18 +1310,16 @@ abstract class JdbcDatabase implements Database<Connection> {
 		PreparedStatement ps = null;
 		ResultSet rs = null;
 		try {
-			String sql = "SELECT name, salt, private FROM groups"
-					+ " WHERE groupId = ?";
+			String sql = "SELECT name, salt FROM groups WHERE groupId = ?";
 			ps = txn.prepareStatement(sql);
 			ps.setBytes(1, g.getBytes());
 			rs = ps.executeQuery();
 			if(!rs.next()) throw new DbStateException();
 			String name = rs.getString(1);
 			byte[] salt = rs.getBytes(2);
-			boolean isPrivate = rs.getBoolean(3);
 			rs.close();
 			ps.close();
-			return new Group(g, name, salt, isPrivate);
+			return new Group(g, name, salt);
 		} catch(SQLException e) {
 			tryToClose(rs);
 			tryToClose(ps);
@@ -1336,7 +1331,7 @@ abstract class JdbcDatabase implements Database<Connection> {
 		PreparedStatement ps = null;
 		ResultSet rs = null;
 		try {
-			String sql = "SELECT groupId, name, salt, private FROM groups";
+			String sql = "SELECT groupId, name, salt FROM groups";
 			ps = txn.prepareStatement(sql);
 			rs = ps.executeQuery();
 			List<Group> groups = new ArrayList<Group>();
@@ -1344,8 +1339,7 @@ abstract class JdbcDatabase implements Database<Connection> {
 				GroupId id = new GroupId(rs.getBytes(1));
 				String name = rs.getString(2);
 				byte[] salt = rs.getBytes(3);
-				boolean isPrivate = rs.getBoolean(4);
-				groups.add(new Group(id, name, salt, isPrivate));
+				groups.add(new Group(id, name, salt));
 			}
 			rs.close();
 			ps.close();
@@ -2085,14 +2079,13 @@ abstract class JdbcDatabase implements Database<Connection> {
 		PreparedStatement ps = null;
 		ResultSet rs = null;
 		try {
-			String sql = "SELECT g.groupId, name, salt, private,"
-					+ " localVersion, txCount"
+			String sql = "SELECT g.groupId, name, salt, localVersion, txCount"
 					+ " FROM groups AS g"
-					+ " JOIN groupVisibilities AS vis"
-					+ " ON g.groupId = vis.groupId"
-					+ " JOIN groupVersions AS ver"
-					+ " ON vis.contactId = ver.contactId"
-					+ " WHERE vis.contactId = ?"
+					+ " JOIN groupVisibilities AS gvis"
+					+ " ON g.groupId = gvis.groupId"
+					+ " JOIN groupVersions AS gver"
+					+ " ON gvis.contactId = gver.contactId"
+					+ " WHERE gvis.contactId = ?"
 					+ " AND localVersion > localAcked"
 					+ " AND expiry < ?";
 			ps = txn.prepareStatement(sql);
@@ -2106,10 +2099,9 @@ abstract class JdbcDatabase implements Database<Connection> {
 				GroupId id = new GroupId(rs.getBytes(1));
 				String name = rs.getString(2);
 				byte[] salt = rs.getBytes(3);
-				boolean isPrivate = rs.getBoolean(4);
-				groups.add(new Group(id, name, salt, isPrivate));
-				version = rs.getLong(5);
-				txCount = rs.getInt(6);
+				groups.add(new Group(id, name, salt));
+				version = rs.getLong(4);
+				txCount = rs.getInt(5);
 			}
 			rs.close();
 			ps.close();
@@ -2335,32 +2327,6 @@ abstract class JdbcDatabase implements Database<Connection> {
 		}
 	}
 
-	public Collection<GroupId> getVisiblePrivateGroups(Connection txn,
-			ContactId c) throws DbException {
-		PreparedStatement ps = null;
-		ResultSet rs = null;
-		try {
-			String sql = "SELECT g.groupId"
-					+ " FROM groups AS g"
-					+ " JOIN groupVisibilities AS gv"
-					+ " ON g.groupId = gv.groupId"
-					+ " WHERE contactId = ?"
-					+ " AND private = TRUE";
-			ps = txn.prepareStatement(sql);
-			ps.setInt(1, c.getInt());
-			rs = ps.executeQuery();
-			List<GroupId> visible = new ArrayList<GroupId>();
-			while(rs.next()) visible.add(new GroupId(rs.getBytes(1)));
-			rs.close();
-			ps.close();
-			return Collections.unmodifiableList(visible);
-		} catch(SQLException e) {
-			tryToClose(rs);
-			tryToClose(ps);
-			throw new DbException(e);
-		}
-	}
-
 	public long incrementConnectionCounter(Connection txn, ContactId c,
 			TransportId t, long period) throws DbException {
 		PreparedStatement ps = null;
@@ -2619,16 +2585,15 @@ abstract class JdbcDatabase implements Database<Connection> {
 		}
 	}
 
-	public void removeVisibility(Connection txn, ContactId c, Group g)
+	public void removeVisibility(Connection txn, ContactId c, GroupId g)
 			throws DbException {
-		if(g.isPrivate()) throw new IllegalArgumentException();
 		PreparedStatement ps = null;
 		try {
 			String sql = "DELETE FROM groupVisibilities"
 					+ " WHERE contactId = ? AND groupId = ?";
 			ps = txn.prepareStatement(sql);
 			ps.setInt(1, c.getInt());
-			ps.setBytes(2, g.getId().getBytes());
+			ps.setBytes(2, g.getBytes());
 			int affected = ps.executeUpdate();
 			if(affected != 1) throw new DbStateException();
 			ps.close();
@@ -2694,15 +2659,14 @@ abstract class JdbcDatabase implements Database<Connection> {
 			// Store the new subscriptions, if any
 			if(groups.isEmpty()) return true;
 			sql = "INSERT INTO contactGroups"
-					+ " (contactId, groupId, name, salt, private)"
-					+ " VALUES (?, ?, ?, ?, ?)";
+					+ " (contactId, groupId, name, salt)"
+					+ " VALUES (?, ?, ?, ?)";
 			ps = txn.prepareStatement(sql);
 			ps.setInt(1, c.getInt());
 			for(Group g : groups) {
 				ps.setBytes(2, g.getId().getBytes());
 				ps.setString(3, g.getName());
 				ps.setBytes(4, g.getSalt());
-				ps.setBoolean(5, g.isPrivate());
 				ps.addBatch();
 			}
 			int[] batchAffected = ps.executeBatch();
@@ -2740,7 +2704,6 @@ abstract class JdbcDatabase implements Database<Connection> {
 
 	public void setInboxGroup(Connection txn, ContactId c, Group g)
 			throws DbException {
-		if(!g.isPrivate()) throw new IllegalArgumentException();
 		PreparedStatement ps = null;
 		try {
 			// Unset any existing inbox group for the contact
@@ -2765,14 +2728,13 @@ abstract class JdbcDatabase implements Database<Connection> {
 			ps.close();
 			// Add the group to the contact's subscriptions
 			sql = "INSERT INTO contactGroups"
-					+ " (contactId, groupId, name, salt, private)"
-					+ " VALUES (?, ?, ?, ?, ?)";
+					+ " (contactId, groupId, name, salt)"
+					+ " VALUES (?, ?, ?, ?)";
 			ps = txn.prepareStatement(sql);
 			ps.setInt(1, c.getInt());
 			ps.setBytes(2, g.getId().getBytes());
 			ps.setString(3, g.getName());
 			ps.setBytes(4, g.getSalt());
-			ps.setBoolean(5, g.isPrivate());
 			affected = ps.executeUpdate();
 			if(affected != 1) throw new DbStateException();
 			ps.close();
@@ -3061,15 +3023,14 @@ abstract class JdbcDatabase implements Database<Connection> {
 		}
 	}
 
-	public void setVisibleToAll(Connection txn, Group g, boolean all)
+	public void setVisibleToAll(Connection txn, GroupId g, boolean all)
 			throws DbException {
-		if(g.isPrivate()) throw new IllegalArgumentException();
 		PreparedStatement ps = null;
 		try {
 			String sql = "UPDATE groups SET visibleToAll = ? WHERE groupId = ?";
 			ps = txn.prepareStatement(sql);
 			ps.setBoolean(1, all);
-			ps.setBytes(2, g.getId().getBytes());
+			ps.setBytes(2, g.getBytes());
 			int affected = ps.executeUpdate();
 			if(affected > 1) throw new DbStateException();
 			ps.close();
diff --git a/briar-core/src/net/sf/briar/invitation/Connector.java b/briar-core/src/net/sf/briar/invitation/Connector.java
index 5d22468788..720164b38a 100644
--- a/briar-core/src/net/sf/briar/invitation/Connector.java
+++ b/briar-core/src/net/sf/briar/invitation/Connector.java
@@ -274,7 +274,7 @@ abstract class Connector extends Thread {
 		contactId = db.addContact(remoteAuthor, localAuthor.getId());
 		// Create and store the inbox group
 		byte[] salt = crypto.deriveGroupSalt(secret);
-		Group inbox = groupFactory.createGroup("Inbox", salt, true);
+		Group inbox = groupFactory.createGroup("Inbox", salt);
 		db.addGroup(inbox);
 		db.setInboxGroup(contactId, inbox);
 		// Store the remote transport properties
diff --git a/briar-core/src/net/sf/briar/messaging/GroupFactoryImpl.java b/briar-core/src/net/sf/briar/messaging/GroupFactoryImpl.java
index bb08c25580..3a360d1384 100644
--- a/briar-core/src/net/sf/briar/messaging/GroupFactoryImpl.java
+++ b/briar-core/src/net/sf/briar/messaging/GroupFactoryImpl.java
@@ -27,20 +27,19 @@ class GroupFactoryImpl implements GroupFactory {
 		this.writerFactory = writerFactory;
 	}
 
-	public Group createGroup(String name, boolean isPrivate) {
+	public Group createGroup(String name) {
 		byte[] salt = new byte[GROUP_SALT_LENGTH];
 		crypto.getSecureRandom().nextBytes(salt);
-		return createGroup(name, salt, isPrivate);
+		return createGroup(name, salt);
 	}
 
-	public Group createGroup(String name, byte[] salt, boolean isPrivate) {
+	public Group createGroup(String name, byte[] salt) {
 		ByteArrayOutputStream out = new ByteArrayOutputStream();
 		Writer w = writerFactory.createWriter(out);
 		try {
 			w.writeStructStart(GROUP);
 			w.writeString(name);
 			w.writeBytes(salt);
-			w.writeBoolean(isPrivate);
 			w.writeStructEnd();
 		} catch(IOException e) {
 			// Shouldn't happen with ByteArrayOutputStream
@@ -49,6 +48,6 @@ class GroupFactoryImpl implements GroupFactory {
 		MessageDigest messageDigest = crypto.getMessageDigest();
 		messageDigest.update(out.toByteArray());
 		GroupId id = new GroupId(messageDigest.digest());
-		return new Group(id, name, salt, isPrivate);
+		return new Group(id, name, salt);
 	}
 }
diff --git a/briar-core/src/net/sf/briar/messaging/GroupReader.java b/briar-core/src/net/sf/briar/messaging/GroupReader.java
index 4b35b2ec92..20402c5ae2 100644
--- a/briar-core/src/net/sf/briar/messaging/GroupReader.java
+++ b/briar-core/src/net/sf/briar/messaging/GroupReader.java
@@ -31,11 +31,10 @@ class GroupReader implements StructReader<Group> {
 		byte[] publicKey = null;
 		if(r.hasNull()) r.readNull();
 		else publicKey = r.readBytes(MAX_PUBLIC_KEY_LENGTH);
-		boolean isPrivate = r.readBoolean();
 		r.readStructEnd();
 		r.removeConsumer(digesting);
 		// Build and return the group
 		GroupId id = new GroupId(messageDigest.digest());
-		return new Group(id, name, publicKey, isPrivate);
+		return new Group(id, name, publicKey);
 	}
 }
diff --git a/briar-core/src/net/sf/briar/messaging/MessageFactoryImpl.java b/briar-core/src/net/sf/briar/messaging/MessageFactoryImpl.java
index cbb60ff59b..bd9901459e 100644
--- a/briar-core/src/net/sf/briar/messaging/MessageFactoryImpl.java
+++ b/briar-core/src/net/sf/briar/messaging/MessageFactoryImpl.java
@@ -122,7 +122,6 @@ class MessageFactoryImpl implements MessageFactory {
 		w.writeStructStart(GROUP);
 		w.writeString(g.getName());
 		w.writeBytes(g.getSalt());
-		w.writeBoolean(g.isPrivate());
 		w.writeStructEnd();
 	}
 
diff --git a/briar-core/src/net/sf/briar/messaging/PacketWriterImpl.java b/briar-core/src/net/sf/briar/messaging/PacketWriterImpl.java
index ffdf05b248..281d5a36b9 100644
--- a/briar-core/src/net/sf/briar/messaging/PacketWriterImpl.java
+++ b/briar-core/src/net/sf/briar/messaging/PacketWriterImpl.java
@@ -129,7 +129,6 @@ class PacketWriterImpl implements PacketWriter {
 			w.writeStructStart(GROUP);
 			w.writeString(g.getName());
 			w.writeBytes(g.getSalt());
-			w.writeBoolean(g.isPrivate());
 			w.writeStructEnd();
 		}
 		w.writeListEnd();
diff --git a/briar-tests/src/net/sf/briar/ProtocolIntegrationTest.java b/briar-tests/src/net/sf/briar/ProtocolIntegrationTest.java
index fa108875db..1a4b1c6590 100644
--- a/briar-tests/src/net/sf/briar/ProtocolIntegrationTest.java
+++ b/briar-tests/src/net/sf/briar/ProtocolIntegrationTest.java
@@ -95,7 +95,7 @@ public class ProtocolIntegrationTest extends BriarTestCase {
 		new Random().nextBytes(secret);
 		// Create a group
 		GroupFactory groupFactory = i.getInstance(GroupFactory.class);
-		group = groupFactory.createGroup("Group", false);
+		group = groupFactory.createGroup("Group");
 		// Create an author
 		AuthorFactory authorFactory = i.getInstance(AuthorFactory.class);
 		CryptoComponent crypto = i.getInstance(CryptoComponent.class);
diff --git a/briar-tests/src/net/sf/briar/db/DatabaseComponentTest.java b/briar-tests/src/net/sf/briar/db/DatabaseComponentTest.java
index 6049609bc5..9c4956a67f 100644
--- a/briar-tests/src/net/sf/briar/db/DatabaseComponentTest.java
+++ b/briar-tests/src/net/sf/briar/db/DatabaseComponentTest.java
@@ -83,7 +83,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 
 	public DatabaseComponentTest() {
 		groupId = new GroupId(TestUtils.getRandomId());
-		group = new Group(groupId, "Group", new byte[GROUP_SALT_LENGTH], false);
+		group = new Group(groupId, "Group", new byte[GROUP_SALT_LENGTH]);
 		authorId = new AuthorId(TestUtils.getRandomId());
 		author = new Author(authorId, "Alice", new byte[MAX_PUBLIC_KEY_LENGTH]);
 		localAuthorId = new AuthorId(TestUtils.getRandomId());
@@ -465,9 +465,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 		} catch(NoSuchContactException expected) {}
 
 		try {
-			Group privateGroup = new Group(groupId, "Group",
-					new byte[GROUP_SALT_LENGTH], true);
-			db.setInboxGroup(contactId, privateGroup);
+			db.setInboxGroup(contactId, group);
 			fail();
 		} catch(NoSuchContactException expected) {}
 
@@ -559,7 +557,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 		} catch(NoSuchSubscriptionException expected) {}
 
 		try {
-			db.setVisibility(group, Collections.<ContactId>emptyList());
+			db.setVisibility(groupId, Collections.<ContactId>emptyList());
 			fail();
 		} catch(NoSuchSubscriptionException expected) {}
 
@@ -1339,8 +1337,8 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 			will(returnValue(both));
 			oneOf(database).getContactIds(txn);
 			will(returnValue(both));
-			oneOf(database).removeVisibility(txn, contactId1, group);
-			oneOf(database).setVisibleToAll(txn, group, false);
+			oneOf(database).removeVisibility(txn, contactId1, groupId);
+			oneOf(database).setVisibleToAll(txn, groupId, false);
 			oneOf(database).commitTransaction(txn);
 			oneOf(listener).eventOccurred(with(any(
 					LocalSubscriptionsUpdatedEvent.class)));
@@ -1349,7 +1347,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 				shutdown);
 
 		db.addListener(listener);
-		db.setVisibility(group, Arrays.asList(contactId));
+		db.setVisibility(groupId, Arrays.asList(contactId));
 
 		context.assertIsSatisfied();
 	}
@@ -1374,14 +1372,14 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 			will(returnValue(both));
 			oneOf(database).getContactIds(txn);
 			will(returnValue(both));
-			oneOf(database).setVisibleToAll(txn, group, false);
+			oneOf(database).setVisibleToAll(txn, groupId, false);
 			oneOf(database).commitTransaction(txn);
 		}});
 		DatabaseComponent db = createDatabaseComponent(database, cleaner,
 				shutdown);
 
 		db.addListener(listener);
-		db.setVisibility(group, both);
+		db.setVisibility(groupId, both);
 
 		context.assertIsSatisfied();
 	}
@@ -1407,8 +1405,8 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 			will(returnValue(Collections.emptyList()));
 			oneOf(database).getContactIds(txn);
 			will(returnValue(both));
-			oneOf(database).addVisibility(txn, contactId, group);
-			oneOf(database).setVisibleToAll(txn, group, false);
+			oneOf(database).addVisibility(txn, contactId, groupId);
+			oneOf(database).setVisibleToAll(txn, groupId, false);
 			oneOf(database).commitTransaction(txn);
 			oneOf(listener).eventOccurred(with(any(
 					LocalSubscriptionsUpdatedEvent.class)));
@@ -1417,12 +1415,12 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 			will(returnValue(txn));
 			oneOf(database).containsGroup(txn, groupId);
 			will(returnValue(true));
-			oneOf(database).setVisibleToAll(txn, group, 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, group);
+			oneOf(database).addVisibility(txn, contactId1, groupId);
 			oneOf(database).commitTransaction(txn);
 			oneOf(listener).eventOccurred(with(any(
 					LocalSubscriptionsUpdatedEvent.class)));
@@ -1431,8 +1429,8 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 				shutdown);
 
 		db.addListener(listener);
-		db.setVisibility(group, Arrays.asList(contactId));
-		db.setVisibleToAll(group, true);
+		db.setVisibility(groupId, Arrays.asList(contactId));
+		db.setVisibleToAll(groupId, true);
 
 		context.assertIsSatisfied();
 	}
diff --git a/briar-tests/src/net/sf/briar/db/H2DatabaseTest.java b/briar-tests/src/net/sf/briar/db/H2DatabaseTest.java
index 044f1f9ac0..0fc598ffd0 100644
--- a/briar-tests/src/net/sf/briar/db/H2DatabaseTest.java
+++ b/briar-tests/src/net/sf/briar/db/H2DatabaseTest.java
@@ -70,7 +70,7 @@ public class H2DatabaseTest extends BriarTestCase {
 
 	public H2DatabaseTest() throws Exception {
 		groupId = new GroupId(TestUtils.getRandomId());
-		group = new Group(groupId, "Group", new byte[GROUP_SALT_LENGTH], false);
+		group = new Group(groupId, "Group", new byte[GROUP_SALT_LENGTH]);
 		authorId = new AuthorId(TestUtils.getRandomId());
 		author = new Author(authorId, "Alice", new byte[MAX_PUBLIC_KEY_LENGTH]);
 		localAuthorId = new AuthorId(TestUtils.getRandomId());
@@ -166,7 +166,7 @@ public class H2DatabaseTest extends BriarTestCase {
 		db.addLocalAuthor(txn, localAuthor);
 		assertEquals(contactId, db.addContact(txn, author, localAuthorId));
 		db.addGroup(txn, group);
-		db.addVisibility(txn, contactId, group);
+		db.addVisibility(txn, contactId, groupId);
 		db.setGroups(txn, contactId, Arrays.asList(group), 1);
 		db.addMessage(txn, message, false);
 
@@ -203,7 +203,7 @@ public class H2DatabaseTest extends BriarTestCase {
 		db.addLocalAuthor(txn, localAuthor);
 		assertEquals(contactId, db.addContact(txn, author, localAuthorId));
 		db.addGroup(txn, group);
-		db.addVisibility(txn, contactId, group);
+		db.addVisibility(txn, contactId, groupId);
 		db.addMessage(txn, message, false);
 		db.addStatus(txn, contactId, messageId, false);
 
@@ -240,7 +240,7 @@ public class H2DatabaseTest extends BriarTestCase {
 		db.addLocalAuthor(txn, localAuthor);
 		assertEquals(contactId, db.addContact(txn, author, localAuthorId));
 		db.addGroup(txn, group);
-		db.addVisibility(txn, contactId, group);
+		db.addVisibility(txn, contactId, groupId);
 		db.setGroups(txn, contactId, Arrays.asList(group), 1);
 		db.addMessage(txn, message, false);
 		db.addStatus(txn, contactId, messageId, false);
@@ -283,7 +283,7 @@ public class H2DatabaseTest extends BriarTestCase {
 		assertFalse(it.hasNext());
 
 		// Making the subscription visible should make the message sendable
-		db.addVisibility(txn, contactId, group);
+		db.addVisibility(txn, contactId, groupId);
 		assertTrue(db.containsSendableMessages(txn, contactId));
 		it = db.getSendableMessages(txn, contactId, ONE_MEGABYTE).iterator();
 		assertTrue(it.hasNext());
@@ -356,7 +356,7 @@ public class H2DatabaseTest extends BriarTestCase {
 		db.addLocalAuthor(txn, localAuthor);
 		assertEquals(contactId, db.addContact(txn, author, localAuthorId));
 		db.addGroup(txn, group);
-		db.addVisibility(txn, contactId, group);
+		db.addVisibility(txn, contactId, groupId);
 		db.setGroups(txn, contactId, Arrays.asList(group), 1);
 		db.addMessage(txn, message, false);
 		db.addStatus(txn, contactId, messageId, false);
@@ -654,7 +654,7 @@ public class H2DatabaseTest extends BriarTestCase {
 		db.addLocalAuthor(txn, localAuthor);
 		assertEquals(contactId, db.addContact(txn, author, localAuthorId));
 		db.addGroup(txn, group);
-		db.addVisibility(txn, contactId, group);
+		db.addVisibility(txn, contactId, groupId);
 		db.setGroups(txn, contactId, Arrays.asList(group), 1);
 
 		// The message is not in the database
@@ -673,7 +673,7 @@ public class H2DatabaseTest extends BriarTestCase {
 		db.addLocalAuthor(txn, localAuthor);
 		assertEquals(contactId, db.addContact(txn, author, localAuthorId));
 		db.addGroup(txn, group);
-		db.addVisibility(txn, contactId, group);
+		db.addVisibility(txn, contactId, groupId);
 		db.setGroups(txn, contactId, Arrays.asList(group), 1);
 		db.addMessage(txn, message, false);
 
@@ -697,7 +697,7 @@ public class H2DatabaseTest extends BriarTestCase {
 		db.addLocalAuthor(txn, localAuthor);
 		assertEquals(contactId, db.addContact(txn, author, localAuthorId));
 		db.addGroup(txn, group);
-		db.addVisibility(txn, contactId, group);
+		db.addVisibility(txn, contactId, groupId);
 		db.setGroups(txn, contactId, Arrays.asList(group), 1);
 		db.setRetentionTime(txn, contactId, timestamp + 1, 1);
 		db.addMessage(txn, message, false);
@@ -721,7 +721,7 @@ public class H2DatabaseTest extends BriarTestCase {
 		db.addLocalAuthor(txn, localAuthor);
 		assertEquals(contactId, db.addContact(txn, author, localAuthorId));
 		db.addGroup(txn, group);
-		db.addVisibility(txn, contactId, group);
+		db.addVisibility(txn, contactId, groupId);
 		db.setGroups(txn, contactId, Arrays.asList(group), 1);
 		db.addMessage(txn, message, false);
 
@@ -746,7 +746,7 @@ public class H2DatabaseTest extends BriarTestCase {
 		db.addLocalAuthor(txn, localAuthor);
 		assertEquals(contactId, db.addContact(txn, author, localAuthorId));
 		db.addGroup(txn, group);
-		db.addVisibility(txn, contactId, group);
+		db.addVisibility(txn, contactId, groupId);
 		db.setGroups(txn, contactId, Arrays.asList(group), 1);
 
 		// The message is not in the database
@@ -784,7 +784,7 @@ public class H2DatabaseTest extends BriarTestCase {
 		db.addLocalAuthor(txn, localAuthor);
 		assertEquals(contactId, db.addContact(txn, author, localAuthorId));
 		db.addGroup(txn, group);
-		db.addVisibility(txn, contactId, group);
+		db.addVisibility(txn, contactId, groupId);
 		db.addMessage(txn, message, false);
 		db.addStatus(txn, contactId, messageId, false);
 
@@ -826,7 +826,7 @@ public class H2DatabaseTest extends BriarTestCase {
 		db.addLocalAuthor(txn, localAuthor);
 		assertEquals(contactId, db.addContact(txn, author, localAuthorId));
 		db.addGroup(txn, group);
-		db.addVisibility(txn, contactId, group);
+		db.addVisibility(txn, contactId, groupId);
 		db.setGroups(txn, contactId, Arrays.asList(group), 1);
 		db.addMessage(txn, message, false);
 
@@ -849,7 +849,7 @@ public class H2DatabaseTest extends BriarTestCase {
 		db.addLocalAuthor(txn, localAuthor);
 		assertEquals(contactId, db.addContact(txn, author, localAuthorId));
 		db.addGroup(txn, group);
-		db.addVisibility(txn, contactId, group);
+		db.addVisibility(txn, contactId, groupId);
 		db.setGroups(txn, contactId, Arrays.asList(group), 1);
 		db.addMessage(txn, message, false);
 
@@ -876,11 +876,11 @@ public class H2DatabaseTest extends BriarTestCase {
 		assertEquals(Collections.emptyList(), db.getVisibility(txn, groupId));
 
 		// Make the group visible to the contact
-		db.addVisibility(txn, contactId, group);
+		db.addVisibility(txn, contactId, groupId);
 		assertEquals(Arrays.asList(contactId), db.getVisibility(txn, groupId));
 
 		// Make the group invisible again
-		db.removeVisibility(txn, contactId, group);
+		db.removeVisibility(txn, contactId, groupId);
 		assertEquals(Collections.emptyList(), db.getVisibility(txn, groupId));
 
 		db.commitTransaction(txn);
@@ -934,7 +934,7 @@ public class H2DatabaseTest extends BriarTestCase {
 			throws Exception {
 		GroupId groupId1 = new GroupId(TestUtils.getRandomId());
 		Group group1 = new Group(groupId1, "Another group",
-				new byte[GROUP_SALT_LENGTH], false);
+				new byte[GROUP_SALT_LENGTH]);
 		Database<Connection> db = open(false);
 		Connection txn = db.startTransaction();
 
@@ -1134,7 +1134,7 @@ public class H2DatabaseTest extends BriarTestCase {
 		db.addGroup(txn, group);
 		GroupId groupId1 = new GroupId(TestUtils.getRandomId());
 		Group group1 = new Group(groupId1, "Another group",
-				new byte[GROUP_SALT_LENGTH], false);
+				new byte[GROUP_SALT_LENGTH]);
 		db.addGroup(txn, group1);
 
 		// Store two messages in the first group
@@ -1188,7 +1188,7 @@ public class H2DatabaseTest extends BriarTestCase {
 		for(int i = 0; i < 100; i++) {
 			GroupId id = new GroupId(TestUtils.getRandomId());
 			String name = "Group " + i;
-			groups.add(new Group(id, name, new byte[GROUP_SALT_LENGTH], false));
+			groups.add(new Group(id, name, new byte[GROUP_SALT_LENGTH]));
 		}
 
 		Database<Connection> db = open(false);
@@ -1201,12 +1201,13 @@ public class H2DatabaseTest extends BriarTestCase {
 
 		// Make the groups visible to the contact
 		Collections.shuffle(groups);
-		for(Group g : groups) db.addVisibility(txn, contactId, g);
+		for(Group g : groups) db.addVisibility(txn, contactId, g.getId());
 
 		// Make some of the groups invisible to the contact and remove them all
 		Collections.shuffle(groups);
 		for(Group g : groups) {
-			if(Math.random() < 0.5) db.removeVisibility(txn, contactId, g);
+			if(Math.random() < 0.5)
+				db.removeVisibility(txn, contactId, g.getId());
 			db.removeGroup(txn, g.getId());
 		}
 
@@ -1571,7 +1572,7 @@ public class H2DatabaseTest extends BriarTestCase {
 
 		// Make the group visible to all contacts - it should be available,
 		// subscribed, visible to all
-		db.setVisibleToAll(txn, group, true);
+		db.setVisibleToAll(txn, groupId, true);
 		assertEquals(Arrays.asList(group), db.getGroups(txn));
 		it = db.getAvailableGroups(txn).iterator();
 		assertTrue(it.hasNext());
@@ -1636,10 +1637,8 @@ public class H2DatabaseTest extends BriarTestCase {
 		// Add a contact and an inbox group - no headers should be returned
 		db.addLocalAuthor(txn, localAuthor);
 		assertEquals(contactId, db.addContact(txn, author, localAuthorId));
-		Group inbox = new Group(groupId, "Group", new byte[GROUP_SALT_LENGTH],
-				true);
-		db.addGroup(txn, inbox);
-		db.setInboxGroup(txn, contactId, inbox);
+		db.addGroup(txn, group);
+		db.setInboxGroup(txn, contactId, group);
 		assertEquals(Collections.emptyList(),
 				db.getInboxMessageHeaders(txn, contactId));
 
diff --git a/briar-tests/src/net/sf/briar/messaging/ConstantsTest.java b/briar-tests/src/net/sf/briar/messaging/ConstantsTest.java
index b5065a898f..f94873d7f3 100644
--- a/briar-tests/src/net/sf/briar/messaging/ConstantsTest.java
+++ b/briar-tests/src/net/sf/briar/messaging/ConstantsTest.java
@@ -121,7 +121,7 @@ public class ConstantsTest extends BriarTestCase {
 		MessageId parent = new MessageId(TestUtils.getRandomId());
 		// Create a maximum-length group
 		String groupName = TestUtils.createRandomString(MAX_GROUP_NAME_LENGTH);
-		Group group = groupFactory.createGroup(groupName, false);
+		Group group = groupFactory.createGroup(groupName);
 		// Create a maximum-length author
 		String authorName =
 				TestUtils.createRandomString(MAX_AUTHOR_NAME_LENGTH);
@@ -180,7 +180,7 @@ public class ConstantsTest extends BriarTestCase {
 		Collection<Group> groups = new ArrayList<Group>();
 		for(int i = 0; i < MAX_SUBSCRIPTIONS; i++) {
 			String name = TestUtils.createRandomString(MAX_GROUP_NAME_LENGTH);
-			groups.add(groupFactory.createGroup(name, false));
+			groups.add(groupFactory.createGroup(name));
 		}
 		// Create a maximum-length subscription update
 		SubscriptionUpdate u = new SubscriptionUpdate(groups, Long.MAX_VALUE);
diff --git a/briar-tests/src/net/sf/briar/messaging/simplex/SimplexMessagingIntegrationTest.java b/briar-tests/src/net/sf/briar/messaging/simplex/SimplexMessagingIntegrationTest.java
index 2511f6a594..3ddb98eaf1 100644
--- a/briar-tests/src/net/sf/briar/messaging/simplex/SimplexMessagingIntegrationTest.java
+++ b/briar-tests/src/net/sf/briar/messaging/simplex/SimplexMessagingIntegrationTest.java
@@ -69,7 +69,7 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase {
 
 	public SimplexMessagingIntegrationTest() throws Exception {
 		GroupId groupId = new GroupId(TestUtils.getRandomId());
-		group = new Group(groupId, "Group", new byte[GROUP_SALT_LENGTH], true);
+		group = new Group(groupId, "Group", new byte[GROUP_SALT_LENGTH]);
 		transportId = new TransportId(TestUtils.getRandomId());
 		// Create matching secrets for Alice and Bob
 		initialSecret = new byte[32];
-- 
GitLab