diff --git a/briar-android/src/org/briarproject/android/ActivityComponent.java b/briar-android/src/org/briarproject/android/ActivityComponent.java
index fa2e845d5833949e185b6dde6cdadbe3ec7d1bd6..e350683529b7ce312f9a673e36cc15ffcd48b579 100644
--- a/briar-android/src/org/briarproject/android/ActivityComponent.java
+++ b/briar-android/src/org/briarproject/android/ActivityComponent.java
@@ -38,7 +38,7 @@ import org.briarproject.android.privategroup.list.GroupListFragment;
 import org.briarproject.android.privategroup.memberlist.GroupMemberListActivity;
 import org.briarproject.android.sharing.BlogInvitationActivity;
 import org.briarproject.android.sharing.BlogSharingStatusActivity;
-import org.briarproject.android.sharing.ContactSelectorFragment;
+import org.briarproject.android.contactselection.ContactSelectorFragment;
 import org.briarproject.android.sharing.ForumInvitationActivity;
 import org.briarproject.android.sharing.ForumSharingStatusActivity;
 import org.briarproject.android.sharing.ShareBlogActivity;
diff --git a/briar-android/src/org/briarproject/android/ActivityModule.java b/briar-android/src/org/briarproject/android/ActivityModule.java
index 931b54bac16bbcd07a03430949831e2d0c11ef9f..1c2db121765055d06fc75b0bcefd16d76cf1a76e 100644
--- a/briar-android/src/org/briarproject/android/ActivityModule.java
+++ b/briar-android/src/org/briarproject/android/ActivityModule.java
@@ -35,6 +35,10 @@ import org.briarproject.android.sharing.BlogInvitationController;
 import org.briarproject.android.sharing.BlogInvitationControllerImpl;
 import org.briarproject.android.sharing.ForumInvitationController;
 import org.briarproject.android.sharing.ForumInvitationControllerImpl;
+import org.briarproject.android.sharing.ShareBlogController;
+import org.briarproject.android.sharing.ShareBlogControllerImpl;
+import org.briarproject.android.sharing.ShareForumController;
+import org.briarproject.android.sharing.ShareForumControllerImpl;
 
 import dagger.Module;
 import dagger.Provides;
@@ -148,6 +152,13 @@ public class ActivityModule {
 		return forumController;
 	}
 
+	@ActivityScope
+	@Provides
+	ShareForumController provideShareForumController(
+			ShareForumControllerImpl shareForumController) {
+		return shareForumController;
+	}
+
 	@ActivityScope
 	@Provides
 	protected ForumInvitationController provideInvitationForumController(
@@ -171,6 +182,13 @@ public class ActivityModule {
 		return blogController;
 	}
 
+	@ActivityScope
+	@Provides
+	ShareBlogController provideShareBlogController(
+			ShareBlogControllerImpl shareBlogController) {
+		return shareBlogController;
+	}
+
 	@ActivityScope
 	@Provides
 	FeedController provideFeedController(FeedControllerImpl feedController) {
diff --git a/briar-android/src/org/briarproject/android/contact/BaseContactListAdapter.java b/briar-android/src/org/briarproject/android/contact/BaseContactListAdapter.java
index 78af58b5b8938511b41a368a3b1c265f706d0b83..22e8391485f913402c89f51826c2a8180962818c 100644
--- a/briar-android/src/org/briarproject/android/contact/BaseContactListAdapter.java
+++ b/briar-android/src/org/briarproject/android/contact/BaseContactListAdapter.java
@@ -39,10 +39,8 @@ public abstract class BaseContactListAdapter<I extends ContactItem, VH extends C
 	}
 
 	@Override
-	public boolean areContentsTheSame(I c1, I c2) {
-		// check for all properties that influence visual
-		// representation of contact
-		return c1.isConnected() == c2.isConnected();
+	public boolean areContentsTheSame(ContactItem c1, ContactItem c2) {
+		return true;
 	}
 
 	int findItemPosition(ContactId c) {
diff --git a/briar-android/src/org/briarproject/android/contact/ContactItem.java b/briar-android/src/org/briarproject/android/contact/ContactItem.java
index c5e5764a75597557836d31bae0520204ffb1d8db..eaa7f66c578d2ed1c908504e6aed4e61164a817f 100644
--- a/briar-android/src/org/briarproject/android/contact/ContactItem.java
+++ b/briar-android/src/org/briarproject/android/contact/ContactItem.java
@@ -11,23 +11,12 @@ public class ContactItem {
 
 	private final Contact contact;
 
-	private boolean connected;
-
-	public ContactItem(Contact contact, boolean connected) {
+	public ContactItem(Contact contact) {
 		this.contact = contact;
-		this.connected = connected;
 	}
 
 	public Contact getContact() {
 		return contact;
 	}
 
-	boolean isConnected() {
-		return connected;
-	}
-
-	void setConnected(boolean connected) {
-		this.connected = connected;
-	}
-
 }
diff --git a/briar-android/src/org/briarproject/android/contact/ContactListAdapter.java b/briar-android/src/org/briarproject/android/contact/ContactListAdapter.java
index ae6c45b9aacb28b40004edf8d56c24920f5c2b91..707ceeacdd4c787d4a585b4055f477a8b21eef90 100644
--- a/briar-android/src/org/briarproject/android/contact/ContactListAdapter.java
+++ b/briar-android/src/org/briarproject/android/contact/ContactListAdapter.java
@@ -34,7 +34,7 @@ public class ContactListAdapter extends
 		if (c1.getTimestamp() != c2.getTimestamp()) {
 			return false;
 		}
-		return super.areContentsTheSame(c1, c2);
+		return c1.isConnected() == c2.isConnected();
 	}
 
 	@Override
diff --git a/briar-android/src/org/briarproject/android/contact/ContactListItem.java b/briar-android/src/org/briarproject/android/contact/ContactListItem.java
index 73f49047ad5dfc2a351c33132a63b78a3b13d1ea..18e4946c16fe685c82e386dc5edc98d25324f2fb 100644
--- a/briar-android/src/org/briarproject/android/contact/ContactListItem.java
+++ b/briar-android/src/org/briarproject/android/contact/ContactListItem.java
@@ -10,13 +10,14 @@ import javax.annotation.concurrent.NotThreadSafe;
 @NotNullByDefault
 public class ContactListItem extends ContactItem {
 
-	private boolean empty;
+	private boolean connected, empty;
 	private long timestamp;
 	private int unread;
 
 	public ContactListItem(Contact contact, boolean connected,
 			GroupCount count) {
-		super(contact, connected);
+		super(contact);
+		this.connected = connected;
 		this.empty = count.getMsgCount() == 0;
 		this.unread = count.getUnreadCount();
 		this.timestamp = count.getLatestMsgTime();
@@ -29,6 +30,14 @@ public class ContactListItem extends ContactItem {
 			unread++;
 	}
 
+	boolean isConnected() {
+		return connected;
+	}
+
+	void setConnected(boolean connected) {
+		this.connected = connected;
+	}
+
 	boolean isEmpty() {
 		return empty;
 	}
diff --git a/briar-android/src/org/briarproject/android/contactselection/BaseContactSelectorAdapter.java b/briar-android/src/org/briarproject/android/contactselection/BaseContactSelectorAdapter.java
new file mode 100644
index 0000000000000000000000000000000000000000..8134d5d941eddb8f0524cf5d5be7736e2170bbc2
--- /dev/null
+++ b/briar-android/src/org/briarproject/android/contactselection/BaseContactSelectorAdapter.java
@@ -0,0 +1,30 @@
+package org.briarproject.android.contactselection;
+
+import android.content.Context;
+
+import org.briarproject.android.contact.BaseContactListAdapter;
+import org.briarproject.android.contact.ContactItemViewHolder;
+import org.briarproject.api.contact.ContactId;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+public abstract class BaseContactSelectorAdapter<I extends SelectableContactItem, H extends ContactItemViewHolder<I>>
+		extends BaseContactListAdapter<I, H> {
+
+	BaseContactSelectorAdapter(Context context, Class<I> c,
+			OnContactClickListener<I> listener) {
+		super(context, c, listener);
+	}
+
+	Collection<ContactId> getSelectedContactIds() {
+		Collection<ContactId> selected = new ArrayList<>();
+
+		for (int i = 0; i < items.size(); i++) {
+			SelectableContactItem item = items.get(i);
+			if (item.isSelected()) selected.add(item.getContact().getId());
+		}
+		return selected;
+	}
+
+}
diff --git a/briar-android/src/org/briarproject/android/contactselection/BaseContactSelectorFragment.java b/briar-android/src/org/briarproject/android/contactselection/BaseContactSelectorFragment.java
new file mode 100644
index 0000000000000000000000000000000000000000..2f9c91c6f8d640dad93ee34042afa6ef987b1c3d
--- /dev/null
+++ b/briar-android/src/org/briarproject/android/contactselection/BaseContactSelectorFragment.java
@@ -0,0 +1,138 @@
+package org.briarproject.android.contactselection;
+
+import android.content.Context;
+import android.os.Build;
+import android.os.Bundle;
+import android.support.annotation.CallSuper;
+import android.support.annotation.Nullable;
+import android.support.v7.widget.LinearLayoutManager;
+import android.transition.Fade;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import org.briarproject.R;
+import org.briarproject.android.contact.BaseContactListAdapter.OnContactClickListener;
+import org.briarproject.android.contact.ContactItemViewHolder;
+import org.briarproject.android.controller.handler.UiResultExceptionHandler;
+import org.briarproject.android.fragment.BaseFragment;
+import org.briarproject.android.view.BriarRecyclerView;
+import org.briarproject.api.contact.ContactId;
+import org.briarproject.api.db.DbException;
+import org.briarproject.api.sync.GroupId;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+import static org.briarproject.android.contactselection.ContactSelectorActivity.CONTACTS;
+import static org.briarproject.android.contactselection.ContactSelectorActivity.getContactsFromIds;
+import static org.briarproject.android.contactselection.ContactSelectorActivity.getContactsFromIntegers;
+import static org.briarproject.api.sharing.SharingConstants.GROUP_ID;
+
+public abstract class BaseContactSelectorFragment<I extends SelectableContactItem, H extends ContactItemViewHolder<I>>
+		extends BaseFragment
+		implements OnContactClickListener<I> {
+
+	protected BriarRecyclerView list;
+	protected BaseContactSelectorAdapter<I, H> adapter;
+	protected Collection<ContactId> selectedContacts;
+	protected ContactSelectorListener<I> listener;
+
+	private GroupId groupId;
+	private ContactSelectorController<I> controller;
+
+	@Override
+	public void onAttach(Context context) {
+		super.onAttach(context);
+		//noinspection unchecked
+		listener = (ContactSelectorListener<I>) context;
+	}
+
+	@Override
+	public void onCreate(Bundle savedInstanceState) {
+		super.onCreate(savedInstanceState);
+
+		Bundle args = getArguments();
+		byte[] b = args.getByteArray(GROUP_ID);
+		if (b == null) throw new IllegalStateException("No GroupId");
+		groupId = new GroupId(b);
+	}
+
+	@Override
+	@CallSuper
+	public View onCreateView(LayoutInflater inflater, ViewGroup container,
+			Bundle savedInstanceState) {
+
+		View contentView = inflater.inflate(R.layout.list, container, false);
+
+		if (Build.VERSION.SDK_INT >= 21) {
+			setExitTransition(new Fade());
+		}
+
+		list = (BriarRecyclerView) contentView.findViewById(R.id.list);
+		list.setLayoutManager(new LinearLayoutManager(getActivity()));
+		list.setEmptyText(getString(R.string.no_contacts_selector));
+
+		// restore selected contacts if available
+		if (savedInstanceState != null) {
+			ArrayList<Integer> intContacts =
+					savedInstanceState.getIntegerArrayList(CONTACTS);
+			if (intContacts != null) {
+				selectedContacts = getContactsFromIntegers(intContacts);
+			}
+		}
+		return contentView;
+	}
+
+	@Override
+	public void onStart() {
+		super.onStart();
+		controller = listener.getController();
+		loadContacts(selectedContacts);
+	}
+
+	@Override
+	public void onStop() {
+		super.onStop();
+		adapter.clear();
+		list.showProgressBar();
+	}
+
+	@Override
+	public void onSaveInstanceState(Bundle outState) {
+		super.onSaveInstanceState(outState);
+		if (adapter != null) {
+			selectedContacts = adapter.getSelectedContactIds();
+			outState.putIntegerArrayList(CONTACTS,
+					getContactsFromIds(selectedContacts));
+		}
+	}
+
+	@Override
+	public void onItemClick(View view, I item) {
+		item.toggleSelected();
+		adapter.notifyItemChanged(adapter.findItemPosition(item), item);
+		onSelectionChanged();
+	}
+
+	private void loadContacts(@Nullable final Collection<ContactId> selection) {
+		controller.loadContacts(groupId, selection,
+				new UiResultExceptionHandler<Collection<I>, DbException>(
+						listener) {
+					@Override
+					public void onResultUi(Collection<I> contacts) {
+						if (contacts.isEmpty()) list.showData();
+						else adapter.addAll(contacts);
+						onSelectionChanged();
+					}
+
+					@Override
+					public void onExceptionUi(DbException exception) {
+
+					}
+				});
+	}
+
+	protected abstract void onSelectionChanged();
+
+}
diff --git a/briar-android/src/org/briarproject/android/sharing/ContactSelectorActivity.java b/briar-android/src/org/briarproject/android/contactselection/ContactSelectorActivity.java
similarity index 72%
rename from briar-android/src/org/briarproject/android/sharing/ContactSelectorActivity.java
rename to briar-android/src/org/briarproject/android/contactselection/ContactSelectorActivity.java
index 4c7d51389570b64e3ed52afe4018b6cf27ee8aa5..1a84cf43fd3c28798d139072de3609e7da9b35ad 100644
--- a/briar-android/src/org/briarproject/android/sharing/ContactSelectorActivity.java
+++ b/briar-android/src/org/briarproject/android/contactselection/ContactSelectorActivity.java
@@ -1,4 +1,4 @@
-package org.briarproject.android.sharing;
+package org.briarproject.android.contactselection;
 
 import android.os.Bundle;
 import android.support.annotation.CallSuper;
@@ -14,11 +14,13 @@ import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
 
-public abstract class ContactSelectorActivity extends BriarActivity implements
-		BaseFragmentListener, ContactSelectorListener {
+public abstract class ContactSelectorActivity<I extends SelectableContactItem>
+		extends BriarActivity
+		implements BaseFragmentListener, ContactSelectorListener<I> {
 
 	final static String CONTACTS = "contacts";
 
+	// Subclasses may initialise the group ID in different places
 	protected GroupId groupId;
 	protected Collection<ContactId> contacts;
 
@@ -29,6 +31,10 @@ public abstract class ContactSelectorActivity extends BriarActivity implements
 		setContentView(R.layout.activity_fragment_container);
 
 		if (bundle != null) {
+			// restore group ID if it was saved
+			byte[] groupBytes = bundle.getByteArray(GROUP_ID);
+			if (groupBytes != null) groupId = new GroupId(groupBytes);
+			// restore selected contacts if a selection was saved
 			ArrayList<Integer> intContacts =
 					bundle.getIntegerArrayList(CONTACTS);
 			if (intContacts != null) {
@@ -40,6 +46,10 @@ public abstract class ContactSelectorActivity extends BriarActivity implements
 	@Override
 	public void onSaveInstanceState(Bundle outState) {
 		super.onSaveInstanceState(outState);
+		if (groupId != null) {
+			// save the group ID here regardless of how subclasses initialize it
+			outState.putByteArray(GROUP_ID, groupId.getBytes());
+		}
 		if (contacts != null) {
 			outState.putIntegerArrayList(CONTACTS,
 					getContactsFromIds(contacts));
@@ -49,9 +59,7 @@ public abstract class ContactSelectorActivity extends BriarActivity implements
 	@CallSuper
 	@UiThread
 	@Override
-	public void contactsSelected(GroupId groupId,
-			Collection<ContactId> contacts) {
-		this.groupId = groupId;
+	public void contactsSelected(Collection<ContactId> contacts) {
 		this.contacts = contacts;
 	}
 
diff --git a/briar-android/src/org/briarproject/android/contactselection/ContactSelectorAdapter.java b/briar-android/src/org/briarproject/android/contactselection/ContactSelectorAdapter.java
new file mode 100644
index 0000000000000000000000000000000000000000..360521011261267ae2bd8078b6356c81ce693830
--- /dev/null
+++ b/briar-android/src/org/briarproject/android/contactselection/ContactSelectorAdapter.java
@@ -0,0 +1,26 @@
+package org.briarproject.android.contactselection;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import org.briarproject.R;
+
+public class ContactSelectorAdapter extends
+		BaseContactSelectorAdapter<SelectableContactItem, SelectableContactHolder> {
+
+	ContactSelectorAdapter(Context context,
+			OnContactClickListener<SelectableContactItem> listener) {
+		super(context, SelectableContactItem.class, listener);
+	}
+
+	@Override
+	public SelectableContactHolder onCreateViewHolder(ViewGroup viewGroup,
+			int i) {
+		View v = LayoutInflater.from(ctx).inflate(
+				R.layout.list_item_selectable_contact, viewGroup, false);
+		return new SelectableContactHolder(v);
+	}
+
+}
diff --git a/briar-android/src/org/briarproject/android/contactselection/ContactSelectorController.java b/briar-android/src/org/briarproject/android/contactselection/ContactSelectorController.java
new file mode 100644
index 0000000000000000000000000000000000000000..6ae4465290e00e4d57b01d853e8c5dbf6a717e45
--- /dev/null
+++ b/briar-android/src/org/briarproject/android/contactselection/ContactSelectorController.java
@@ -0,0 +1,19 @@
+package org.briarproject.android.contactselection;
+
+import android.support.annotation.Nullable;
+
+import org.briarproject.android.controller.DbController;
+import org.briarproject.android.controller.handler.ResultExceptionHandler;
+import org.briarproject.api.contact.ContactId;
+import org.briarproject.api.db.DbException;
+import org.briarproject.api.sync.GroupId;
+
+import java.util.Collection;
+
+public interface ContactSelectorController<I extends SelectableContactItem>
+		extends DbController {
+
+	void loadContacts(GroupId g, @Nullable Collection<ContactId> selection,
+			ResultExceptionHandler<Collection<I>, DbException> handler);
+
+}
diff --git a/briar-android/src/org/briarproject/android/contactselection/ContactSelectorControllerImpl.java b/briar-android/src/org/briarproject/android/contactselection/ContactSelectorControllerImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..ce12b5b9925fadc3aa76c7b682f0dec19c7b0a16
--- /dev/null
+++ b/briar-android/src/org/briarproject/android/contactselection/ContactSelectorControllerImpl.java
@@ -0,0 +1,72 @@
+package org.briarproject.android.contactselection;
+
+import org.briarproject.android.controller.DbControllerImpl;
+import org.briarproject.android.controller.handler.ResultExceptionHandler;
+import org.briarproject.api.contact.Contact;
+import org.briarproject.api.contact.ContactId;
+import org.briarproject.api.contact.ContactManager;
+import org.briarproject.api.db.DatabaseExecutor;
+import org.briarproject.api.db.DbException;
+import org.briarproject.api.lifecycle.LifecycleManager;
+import org.briarproject.api.sync.GroupId;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.concurrent.Executor;
+import java.util.logging.Logger;
+
+import static java.util.logging.Level.WARNING;
+
+public abstract class ContactSelectorControllerImpl<I extends SelectableContactItem>
+		extends DbControllerImpl
+		implements ContactSelectorController<I> {
+
+	protected static final Logger LOG =
+			Logger.getLogger("ContactSelectorController");
+
+	private final ContactManager contactManager;
+
+	public ContactSelectorControllerImpl(@DatabaseExecutor Executor dbExecutor,
+			LifecycleManager lifecycleManager, ContactManager contactManager) {
+		super(dbExecutor, lifecycleManager);
+		this.contactManager = contactManager;
+	}
+
+	@Override
+	public void loadContacts(final GroupId g,
+			@Nullable final Collection<ContactId> selection,
+			final ResultExceptionHandler<Collection<I>, DbException> handler) {
+		runOnDbThread(new Runnable() {
+			@Override
+			public void run() {
+				try {
+					Collection<I> contacts = new ArrayList<>();
+					for (Contact c : contactManager.getActiveContacts()) {
+						// was this contact already selected?
+						boolean selected = isSelected(c, selection != null &&
+								selection.contains(c.getId()));
+						// can this contact be selected?
+						boolean disabled = isDisabled(g, c);
+						contacts.add(getItem(c, selected, disabled));
+					}
+					handler.onResult(contacts);
+				} catch (DbException e) {
+					if (LOG.isLoggable(WARNING))
+						LOG.log(WARNING, e.toString(), e);
+					handler.onException(e);
+				}
+			}
+		});
+	}
+
+	@DatabaseExecutor
+	protected abstract boolean isSelected(Contact c, boolean wasSelected);
+
+	@DatabaseExecutor
+	protected abstract boolean isDisabled(GroupId g, Contact c)
+			throws DbException;
+
+	protected abstract I getItem(Contact c, boolean selected, boolean disabled);
+
+}
diff --git a/briar-android/src/org/briarproject/android/contactselection/ContactSelectorFragment.java b/briar-android/src/org/briarproject/android/contactselection/ContactSelectorFragment.java
new file mode 100644
index 0000000000000000000000000000000000000000..b766b95e7a4605e93f21b42e8ed336a56a1f8f4b
--- /dev/null
+++ b/briar-android/src/org/briarproject/android/contactselection/ContactSelectorFragment.java
@@ -0,0 +1,89 @@
+package org.briarproject.android.contactselection;
+
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+
+import org.briarproject.R;
+import org.briarproject.android.ActivityComponent;
+import org.briarproject.android.contact.BaseContactListAdapter.OnContactClickListener;
+import org.briarproject.api.sync.GroupId;
+
+import static org.briarproject.api.sharing.SharingConstants.GROUP_ID;
+
+public class ContactSelectorFragment extends
+		BaseContactSelectorFragment<SelectableContactItem, SelectableContactHolder>
+		implements OnContactClickListener<SelectableContactItem> {
+
+	public static final String TAG = ContactSelectorFragment.class.getName();
+
+	private Menu menu;
+
+	public static ContactSelectorFragment newInstance(GroupId groupId) {
+		Bundle args = new Bundle();
+		args.putByteArray(GROUP_ID, groupId.getBytes());
+		ContactSelectorFragment fragment = new ContactSelectorFragment();
+		fragment.setArguments(args);
+		return fragment;
+	}
+
+	@Override
+	public void injectFragment(ActivityComponent component) {
+		component.inject(this);
+	}
+
+	@Override
+	public View onCreateView(LayoutInflater inflater, ViewGroup container,
+			Bundle savedInstanceState) {
+		View contentView =
+				super.onCreateView(inflater, container, savedInstanceState);
+		adapter = new ContactSelectorAdapter(getActivity(), this);
+		list.setAdapter(adapter);
+		return contentView;
+	}
+
+	@Override
+	public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+		inflater.inflate(R.menu.contact_selection_actions, menu);
+		super.onCreateOptionsMenu(menu, inflater);
+		this.menu = menu;
+		// hide sharing action initially, if no contact is selected
+		onSelectionChanged();
+	}
+
+	@Override
+	public boolean onOptionsItemSelected(final MenuItem item) {
+		switch (item.getItemId()) {
+			case R.id.action_contacts_selected:
+				selectedContacts = adapter.getSelectedContactIds();
+				listener.contactsSelected(selectedContacts);
+				return true;
+			default:
+				return super.onOptionsItemSelected(item);
+		}
+	}
+
+	@Override
+	public String getUniqueTag() {
+		return TAG;
+	}
+
+	@Override
+	protected void onSelectionChanged() {
+		if (menu == null) return;
+		MenuItem item = menu.findItem(R.id.action_contacts_selected);
+		if (item == null) return;
+
+		selectedContacts = adapter.getSelectedContactIds();
+		if (selectedContacts.size() > 0) {
+			item.setVisible(true);
+		} else {
+			item.setVisible(false);
+		}
+	}
+
+}
diff --git a/briar-android/src/org/briarproject/android/contactselection/ContactSelectorListener.java b/briar-android/src/org/briarproject/android/contactselection/ContactSelectorListener.java
new file mode 100644
index 0000000000000000000000000000000000000000..fa8bc3d7891ddf889df74597e808ca7570037220
--- /dev/null
+++ b/briar-android/src/org/briarproject/android/contactselection/ContactSelectorListener.java
@@ -0,0 +1,18 @@
+package org.briarproject.android.contactselection;
+
+import android.support.annotation.UiThread;
+
+import org.briarproject.android.DestroyableContext;
+import org.briarproject.api.contact.ContactId;
+
+import java.util.Collection;
+
+interface ContactSelectorListener<I extends SelectableContactItem>
+		extends DestroyableContext {
+
+	ContactSelectorController<I> getController();
+
+	@UiThread
+	void contactsSelected(Collection<ContactId> contacts);
+
+}
diff --git a/briar-android/src/org/briarproject/android/sharing/SelectableContactHolder.java b/briar-android/src/org/briarproject/android/contactselection/SelectableContactHolder.java
similarity index 94%
rename from briar-android/src/org/briarproject/android/sharing/SelectableContactHolder.java
rename to briar-android/src/org/briarproject/android/contactselection/SelectableContactHolder.java
index 9f0eddfe8c1af47ca72d0f43d0167ba79600fed2..47173b51a87c00462c7462af90e45040eeb427da 100644
--- a/briar-android/src/org/briarproject/android/sharing/SelectableContactHolder.java
+++ b/briar-android/src/org/briarproject/android/contactselection/SelectableContactHolder.java
@@ -1,4 +1,4 @@
-package org.briarproject.android.sharing;
+package org.briarproject.android.contactselection;
 
 import android.support.annotation.UiThread;
 import android.view.View;
@@ -16,7 +16,7 @@ import static android.view.View.VISIBLE;
 
 @UiThread
 @NotNullByDefault
-class SelectableContactHolder
+public class SelectableContactHolder
 		extends ContactItemViewHolder<SelectableContactItem> {
 
 	private final CheckBox checkBox;
diff --git a/briar-android/src/org/briarproject/android/sharing/SelectableContactItem.java b/briar-android/src/org/briarproject/android/contactselection/SelectableContactItem.java
similarity index 68%
rename from briar-android/src/org/briarproject/android/sharing/SelectableContactItem.java
rename to briar-android/src/org/briarproject/android/contactselection/SelectableContactItem.java
index 03a9ae3fce24ecc6f36735bc76eb50a903c06d64..23ed9e0ec155910d5ae6d8b9c61efac661f2c6f2 100644
--- a/briar-android/src/org/briarproject/android/sharing/SelectableContactItem.java
+++ b/briar-android/src/org/briarproject/android/contactselection/SelectableContactItem.java
@@ -1,4 +1,4 @@
-package org.briarproject.android.sharing;
+package org.briarproject.android.contactselection;
 
 import org.briarproject.android.contact.ContactItem;
 import org.briarproject.api.contact.Contact;
@@ -8,13 +8,13 @@ import javax.annotation.concurrent.NotThreadSafe;
 
 @NotThreadSafe
 @NotNullByDefault
-class SelectableContactItem extends ContactItem {
+public class SelectableContactItem extends ContactItem {
 
 	private boolean selected, disabled;
 
-	SelectableContactItem(Contact contact, boolean connected,
-			boolean selected, boolean disabled) {
-		super(contact, connected);
+	public SelectableContactItem(Contact contact, boolean selected,
+			boolean disabled) {
+		super(contact);
 		this.selected = selected;
 		this.disabled = disabled;
 	}
diff --git a/briar-android/src/org/briarproject/android/privategroup/creation/BaseGroupInviteActivity.java b/briar-android/src/org/briarproject/android/privategroup/creation/BaseGroupInviteActivity.java
index 895f6188f7f83a81404066958d1299c0caf5f50d..8359cfe91722ed59b08f21908df673cf3b9c50d2 100644
--- a/briar-android/src/org/briarproject/android/privategroup/creation/BaseGroupInviteActivity.java
+++ b/briar-android/src/org/briarproject/android/privategroup/creation/BaseGroupInviteActivity.java
@@ -1,14 +1,13 @@
 package org.briarproject.android.privategroup.creation;
 
-import android.os.Bundle;
-
 import org.briarproject.R;
+import org.briarproject.android.contactselection.ContactSelectorActivity;
+import org.briarproject.android.contactselection.ContactSelectorController;
+import org.briarproject.android.contactselection.SelectableContactItem;
 import org.briarproject.android.controller.handler.UiResultExceptionHandler;
 import org.briarproject.android.sharing.BaseMessageFragment.MessageFragmentListener;
-import org.briarproject.android.sharing.ContactSelectorActivity;
 import org.briarproject.api.contact.ContactId;
 import org.briarproject.api.db.DbException;
-import org.briarproject.api.sync.GroupId;
 import org.jetbrains.annotations.NotNull;
 
 import java.util.Collection;
@@ -18,36 +17,20 @@ import javax.inject.Inject;
 import static org.briarproject.api.privategroup.PrivateGroupConstants.MAX_GROUP_INVITATION_MSG_LENGTH;
 
 public abstract class BaseGroupInviteActivity
-		extends ContactSelectorActivity
+		extends ContactSelectorActivity<SelectableContactItem>
 		implements MessageFragmentListener {
 
 	@Inject
 	CreateGroupController controller;
 
 	@Override
-	public void onCreate(Bundle bundle) {
-		super.onCreate(bundle);
-
-		// Subclasses may initialise the group ID in different places,
-		// restore it if it was saved
-		if (bundle != null) {
-			byte[] groupBytes = bundle.getByteArray(GROUP_ID);
-			if (groupBytes != null) groupId = new GroupId(groupBytes);
-		}
-	}
-
-	@Override
-	public void onSaveInstanceState(Bundle outState) {
-		super.onSaveInstanceState(outState);
-		if (groupId != null) {
-			outState.putByteArray(GROUP_ID, groupId.getBytes());
-		}
+	public ContactSelectorController<SelectableContactItem> getController() {
+		return controller;
 	}
 
 	@Override
-	public void contactsSelected(GroupId groupId,
-			Collection<ContactId> contacts) {
-		super.contactsSelected(groupId, contacts);
+	public void contactsSelected(Collection<ContactId> contacts) {
+		super.contactsSelected(contacts);
 
 		CreateGroupMessageFragment fragment = new CreateGroupMessageFragment();
 		getSupportFragmentManager().beginTransaction()
@@ -62,6 +45,8 @@ public abstract class BaseGroupInviteActivity
 
 	@Override
 	public boolean onButtonClick(@NotNull String message) {
+		if (groupId == null)
+			throw new IllegalStateException("GroupId was not initialized");
 		controller.sendInvitation(groupId, contacts, message,
 				new UiResultExceptionHandler<Void, DbException>(this) {
 					@Override
diff --git a/briar-android/src/org/briarproject/android/privategroup/creation/CreateGroupActivity.java b/briar-android/src/org/briarproject/android/privategroup/creation/CreateGroupActivity.java
index a82b4cc1deeacd409f0c28c1c1e2552d75cf5ace..5b571fe4a78ac89a93a939f0d2a6ec5dd70fc9bb 100644
--- a/briar-android/src/org/briarproject/android/privategroup/creation/CreateGroupActivity.java
+++ b/briar-android/src/org/briarproject/android/privategroup/creation/CreateGroupActivity.java
@@ -7,12 +7,10 @@ import android.support.v4.app.ActivityOptionsCompat;
 
 import org.briarproject.R;
 import org.briarproject.android.ActivityComponent;
+import org.briarproject.android.contactselection.ContactSelectorFragment;
 import org.briarproject.android.controller.handler.UiResultExceptionHandler;
 import org.briarproject.android.privategroup.conversation.GroupActivity;
 import org.briarproject.android.sharing.BaseMessageFragment.MessageFragmentListener;
-import org.briarproject.android.sharing.ContactSelectorFragment;
-import org.briarproject.api.contact.Contact;
-import org.briarproject.api.db.DatabaseExecutor;
 import org.briarproject.api.db.DbException;
 import org.briarproject.api.sync.GroupId;
 
@@ -38,13 +36,6 @@ public class CreateGroupActivity extends BaseGroupInviteActivity implements
 		}
 	}
 
-	@Override
-	@DatabaseExecutor
-	public boolean isDisabled(GroupId groupId, Contact c) throws DbException {
-		// contacts can always be invited into a new group
-		return false;
-	}
-
 	@Override
 	public void onBackPressed() {
 		if (getSupportFragmentManager().getBackStackEntryCount() == 1) {
diff --git a/briar-android/src/org/briarproject/android/privategroup/creation/CreateGroupController.java b/briar-android/src/org/briarproject/android/privategroup/creation/CreateGroupController.java
index cc46f6e47a2cd675f6a69cef72cd6f3ae58083ce..2434ccd7d2a9da11add41888d7ee992ec84cc7d9 100644
--- a/briar-android/src/org/briarproject/android/privategroup/creation/CreateGroupController.java
+++ b/briar-android/src/org/briarproject/android/privategroup/creation/CreateGroupController.java
@@ -1,6 +1,7 @@
 package org.briarproject.android.privategroup.creation;
 
-import org.briarproject.android.controller.DbController;
+import org.briarproject.android.contactselection.ContactSelectorController;
+import org.briarproject.android.contactselection.SelectableContactItem;
 import org.briarproject.android.controller.handler.ResultExceptionHandler;
 import org.briarproject.api.contact.ContactId;
 import org.briarproject.api.db.DbException;
@@ -10,12 +11,13 @@ import org.briarproject.api.sync.GroupId;
 import java.util.Collection;
 
 @NotNullByDefault
-public interface CreateGroupController extends DbController {
+public interface CreateGroupController
+		extends ContactSelectorController<SelectableContactItem> {
 
 	void createGroup(String name,
 			ResultExceptionHandler<GroupId, DbException> result);
 
-	void sendInvitation(GroupId groupId, Collection<ContactId> contacts,
+	void sendInvitation(GroupId g, Collection<ContactId> contacts,
 			String message, ResultExceptionHandler<Void, DbException> result);
 
 }
diff --git a/briar-android/src/org/briarproject/android/privategroup/creation/CreateGroupControllerImpl.java b/briar-android/src/org/briarproject/android/privategroup/creation/CreateGroupControllerImpl.java
index 4f7ff84a55ec56fcfad74fde5cb71406fa1442b2..a0a849c7c0b96d264212e31cebda1c2be2ddff7a 100644
--- a/briar-android/src/org/briarproject/android/privategroup/creation/CreateGroupControllerImpl.java
+++ b/briar-android/src/org/briarproject/android/privategroup/creation/CreateGroupControllerImpl.java
@@ -1,6 +1,7 @@
 package org.briarproject.android.privategroup.creation;
 
-import org.briarproject.android.controller.DbControllerImpl;
+import org.briarproject.android.contactselection.ContactSelectorControllerImpl;
+import org.briarproject.android.contactselection.SelectableContactItem;
 import org.briarproject.android.controller.handler.ResultExceptionHandler;
 import org.briarproject.api.contact.Contact;
 import org.briarproject.api.contact.ContactId;
@@ -36,7 +37,8 @@ import static java.util.logging.Level.WARNING;
 
 @Immutable
 @NotNullByDefault
-public class CreateGroupControllerImpl extends DbControllerImpl
+public class CreateGroupControllerImpl
+		extends ContactSelectorControllerImpl<SelectableContactItem>
 		implements CreateGroupController {
 
 	private static final Logger LOG =
@@ -61,7 +63,7 @@ public class CreateGroupControllerImpl extends DbControllerImpl
 			PrivateGroupManager groupManager,
 			GroupInvitationFactory groupInvitationFactory,
 			GroupInvitationManager groupInvitationManager, Clock clock) {
-		super(dbExecutor, lifecycleManager);
+		super(dbExecutor, lifecycleManager, contactManager);
 		this.cryptoExecutor = cryptoExecutor;
 		this.contactManager = contactManager;
 		this.identityManager = identityManager;
@@ -128,6 +130,22 @@ public class CreateGroupControllerImpl extends DbControllerImpl
 		});
 	}
 
+	@Override
+	protected boolean isSelected(Contact c, boolean wasSelected) {
+		return wasSelected;
+	}
+
+	@Override
+	protected boolean isDisabled(GroupId g, Contact c) throws DbException {
+		return !groupInvitationManager.isInvitationAllowed(c, g);
+	}
+
+	@Override
+	protected SelectableContactItem getItem(Contact c, boolean selected,
+			boolean disabled) {
+		return new SelectableContactItem(c, selected, disabled);
+	}
+
 	@Override
 	public void sendInvitation(final GroupId g,
 			final Collection<ContactId> contactIds, final String message,
@@ -191,6 +209,7 @@ public class CreateGroupControllerImpl extends DbControllerImpl
 							// Continue
 						}
 					}
+					//noinspection ConstantConditions
 					handler.onResult(null);
 				} catch (DbException e) {
 					if (LOG.isLoggable(WARNING))
diff --git a/briar-android/src/org/briarproject/android/privategroup/creation/GroupInviteActivity.java b/briar-android/src/org/briarproject/android/privategroup/creation/GroupInviteActivity.java
index abc776f5136ccecc338f95bed91610c9aec0de5b..d4b225cfca0f7c18fdc42061228cc49339ca5e0e 100644
--- a/briar-android/src/org/briarproject/android/privategroup/creation/GroupInviteActivity.java
+++ b/briar-android/src/org/briarproject/android/privategroup/creation/GroupInviteActivity.java
@@ -5,22 +5,13 @@ import android.os.Bundle;
 
 import org.briarproject.R;
 import org.briarproject.android.ActivityComponent;
+import org.briarproject.android.contactselection.ContactSelectorFragment;
 import org.briarproject.android.sharing.BaseMessageFragment.MessageFragmentListener;
-import org.briarproject.android.sharing.ContactSelectorFragment;
-import org.briarproject.api.contact.Contact;
-import org.briarproject.api.db.DatabaseExecutor;
-import org.briarproject.api.db.DbException;
-import org.briarproject.api.privategroup.invitation.GroupInvitationManager;
 import org.briarproject.api.sync.GroupId;
 
-import javax.inject.Inject;
-
 public class GroupInviteActivity extends BaseGroupInviteActivity
 		implements MessageFragmentListener {
 
-	@Inject
-	GroupInvitationManager groupInvitationManager;
-
 	@Override
 	public void injectActivity(ActivityComponent component) {
 		component.inject(this);
@@ -30,8 +21,6 @@ public class GroupInviteActivity extends BaseGroupInviteActivity
 	public void onCreate(Bundle bundle) {
 		super.onCreate(bundle);
 
-		// Initialise the group ID,
-		// it will be saved and restored by the superclass
 		Intent i = getIntent();
 		byte[] g = i.getByteArrayExtra(GROUP_ID);
 		if (g == null) throw new IllegalStateException("No GroupId in intent.");
@@ -46,10 +35,4 @@ public class GroupInviteActivity extends BaseGroupInviteActivity
 		}
 	}
 
-	@Override
-	@DatabaseExecutor
-	public boolean isDisabled(GroupId g, Contact c) throws DbException {
-		return !groupInvitationManager.isInvitationAllowed(c, g);
-	}
-
 }
diff --git a/briar-android/src/org/briarproject/android/sharing/ContactSelectorAdapter.java b/briar-android/src/org/briarproject/android/sharing/ContactSelectorAdapter.java
deleted file mode 100644
index 9573c6398dc0d5fa030bc8c03016d30dbe15ca49..0000000000000000000000000000000000000000
--- a/briar-android/src/org/briarproject/android/sharing/ContactSelectorAdapter.java
+++ /dev/null
@@ -1,41 +0,0 @@
-package org.briarproject.android.sharing;
-
-import android.content.Context;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-import org.briarproject.R;
-import org.briarproject.android.contact.BaseContactListAdapter;
-import org.briarproject.api.contact.ContactId;
-
-import java.util.ArrayList;
-import java.util.Collection;
-
-class ContactSelectorAdapter extends
-		BaseContactListAdapter<SelectableContactItem, SelectableContactHolder> {
-
-	ContactSelectorAdapter(Context context,
-			OnContactClickListener<SelectableContactItem> listener) {
-		super(context, SelectableContactItem.class, listener);
-	}
-
-	@Override
-	public SelectableContactHolder onCreateViewHolder(ViewGroup viewGroup,
-			int i) {
-		View v = LayoutInflater.from(ctx).inflate(
-				R.layout.list_item_selectable_contact, viewGroup, false);
-		return new SelectableContactHolder(v);
-	}
-
-	Collection<ContactId> getSelectedContactIds() {
-		Collection<ContactId> selected = new ArrayList<>();
-
-		for (int i = 0; i < items.size(); i++) {
-			SelectableContactItem item = items.get(i);
-			if (item.isSelected()) selected.add(item.getContact().getId());
-		}
-		return selected;
-	}
-
-}
diff --git a/briar-android/src/org/briarproject/android/sharing/ContactSelectorFragment.java b/briar-android/src/org/briarproject/android/sharing/ContactSelectorFragment.java
deleted file mode 100644
index cc8f46230b25d505000c71c67ffb96b60786b320..0000000000000000000000000000000000000000
--- a/briar-android/src/org/briarproject/android/sharing/ContactSelectorFragment.java
+++ /dev/null
@@ -1,233 +0,0 @@
-package org.briarproject.android.sharing;
-
-import android.content.Context;
-import android.os.Build;
-import android.os.Bundle;
-import android.support.annotation.Nullable;
-import android.support.v7.widget.LinearLayoutManager;
-import android.transition.Fade;
-import android.view.LayoutInflater;
-import android.view.Menu;
-import android.view.MenuInflater;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.ViewGroup;
-
-import org.briarproject.R;
-import org.briarproject.android.ActivityComponent;
-import org.briarproject.android.contact.BaseContactListAdapter.OnContactClickListener;
-import org.briarproject.android.fragment.BaseFragment;
-import org.briarproject.android.view.BriarRecyclerView;
-import org.briarproject.api.contact.Contact;
-import org.briarproject.api.contact.ContactId;
-import org.briarproject.api.contact.ContactManager;
-import org.briarproject.api.db.DbException;
-import org.briarproject.api.plugins.ConnectionRegistry;
-import org.briarproject.api.sync.GroupId;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-import java.util.logging.Logger;
-
-import javax.inject.Inject;
-
-import static java.util.logging.Level.INFO;
-import static java.util.logging.Level.WARNING;
-import static org.briarproject.android.sharing.ShareActivity.CONTACTS;
-import static org.briarproject.android.sharing.ShareActivity.getContactsFromIds;
-import static org.briarproject.android.sharing.ShareActivity.getContactsFromIntegers;
-import static org.briarproject.api.sharing.SharingConstants.GROUP_ID;
-
-public class ContactSelectorFragment extends BaseFragment
-		implements OnContactClickListener<SelectableContactItem> {
-
-	public static final String TAG = ContactSelectorFragment.class.getName();
-	private static final Logger LOG = Logger.getLogger(TAG);
-
-	private Menu menu;
-	private BriarRecyclerView list;
-	private ContactSelectorAdapter adapter;
-	private Collection<ContactId> selectedContacts;
-
-	// Fields that are accessed from background threads must be volatile
-	@Inject
-	volatile ContactManager contactManager;
-	@Inject
-	volatile ConnectionRegistry connectionRegistry;
-
-	private volatile GroupId groupId;
-	private volatile ContactSelectorListener listener;
-
-	public static ContactSelectorFragment newInstance(GroupId groupId) {
-		Bundle args = new Bundle();
-		args.putByteArray(GROUP_ID, groupId.getBytes());
-		ContactSelectorFragment fragment = new ContactSelectorFragment();
-		fragment.setArguments(args);
-		return fragment;
-	}
-
-	@Override
-	public void injectFragment(ActivityComponent component) {
-		component.inject(this);
-	}
-
-	@Override
-	public void onAttach(Context context) {
-		super.onAttach(context);
-		listener = (ContactSelectorListener) context;
-	}
-
-	@Override
-	public void onCreate(Bundle savedInstanceState) {
-		super.onCreate(savedInstanceState);
-
-		Bundle args = getArguments();
-		byte[] b = args.getByteArray(GROUP_ID);
-		if (b == null) throw new IllegalStateException("No GroupId");
-		groupId = new GroupId(b);
-	}
-
-	@Override
-	public View onCreateView(LayoutInflater inflater, ViewGroup container,
-			Bundle savedInstanceState) {
-
-		View contentView = inflater.inflate(R.layout.list, container, false);
-
-		if (Build.VERSION.SDK_INT >= 21) {
-			setExitTransition(new Fade());
-		}
-
-		adapter = new ContactSelectorAdapter(getActivity(), this);
-
-		list = (BriarRecyclerView) contentView.findViewById(R.id.list);
-		list.setLayoutManager(new LinearLayoutManager(getActivity()));
-		list.setAdapter(adapter);
-		list.setEmptyText(getString(R.string.no_contacts_selector));
-
-		// restore selected contacts if available
-		if (savedInstanceState != null) {
-			ArrayList<Integer> intContacts =
-					savedInstanceState.getIntegerArrayList(CONTACTS);
-			if (intContacts != null) {
-				selectedContacts = getContactsFromIntegers(intContacts);
-			}
-		}
-		return contentView;
-	}
-
-	@Override
-	public void onStart() {
-		super.onStart();
-		loadContacts(selectedContacts);
-	}
-
-	@Override
-	public void onStop() {
-		super.onStop();
-		adapter.clear();
-		list.showProgressBar();
-	}
-
-	@Override
-	public void onSaveInstanceState(Bundle outState) {
-		super.onSaveInstanceState(outState);
-		if (adapter != null) {
-			selectedContacts = adapter.getSelectedContactIds();
-			outState.putIntegerArrayList(CONTACTS,
-					getContactsFromIds(selectedContacts));
-		}
-	}
-
-	@Override
-	public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
-		inflater.inflate(R.menu.contact_selection_actions, menu);
-		super.onCreateOptionsMenu(menu, inflater);
-		this.menu = menu;
-		// hide sharing action initially, if no contact is selected
-		updateMenuItem();
-	}
-
-	@Override
-	public boolean onOptionsItemSelected(final MenuItem item) {
-		// Handle presses on the action bar items
-		switch (item.getItemId()) {
-			case R.id.action_contacts_selected:
-				selectedContacts = adapter.getSelectedContactIds();
-				listener.contactsSelected(groupId, selectedContacts);
-				return true;
-			default:
-				return super.onOptionsItemSelected(item);
-		}
-	}
-
-	@Override
-	public String getUniqueTag() {
-		return TAG;
-	}
-
-	@Override
-	public void onItemClick(View view, SelectableContactItem item) {
-		item.toggleSelected();
-		adapter.notifyItemChanged(adapter.findItemPosition(item), item);
-
-		updateMenuItem();
-	}
-
-	private void loadContacts(@Nullable final Collection<ContactId> selection) {
-		listener.runOnDbThread(new Runnable() {
-			@Override
-			public void run() {
-				try {
-					long now = System.currentTimeMillis();
-					List<SelectableContactItem> contacts =
-							new ArrayList<>();
-					for (Contact c : contactManager.getActiveContacts()) {
-						// is this contact online?
-						boolean connected =
-								connectionRegistry.isConnected(c.getId());
-						// was this contact already selected?
-						boolean selected = selection != null &&
-								selection.contains(c.getId());
-						// do we have already some sharing with that contact?
-						boolean disabled = listener.isDisabled(groupId, c);
-						contacts.add(new SelectableContactItem(c, connected,
-								selected, disabled));
-					}
-					long duration = System.currentTimeMillis() - now;
-					if (LOG.isLoggable(INFO))
-						LOG.info("Load took " + duration + " ms");
-					displayContacts(contacts);
-				} catch (DbException e) {
-					if (LOG.isLoggable(WARNING))
-						LOG.log(WARNING, e.toString(), e);
-				}
-			}
-		});
-	}
-
-	private void displayContacts(
-			final List<SelectableContactItem> contacts) {
-		listener.runOnUiThreadUnlessDestroyed(new Runnable() {
-			@Override
-			public void run() {
-				if (contacts.isEmpty()) list.showData();
-				else adapter.addAll(contacts);
-				updateMenuItem();
-			}
-		});
-	}
-
-	private void updateMenuItem() {
-		if (menu == null) return;
-		MenuItem item = menu.findItem(R.id.action_contacts_selected);
-		if (item == null) return;
-
-		selectedContacts = adapter.getSelectedContactIds();
-		if (selectedContacts.size() > 0) {
-			item.setVisible(true);
-		} else {
-			item.setVisible(false);
-		}
-	}
-}
diff --git a/briar-android/src/org/briarproject/android/sharing/ContactSelectorListener.java b/briar-android/src/org/briarproject/android/sharing/ContactSelectorListener.java
deleted file mode 100644
index 48949b14cd1ea23da60646461030c8b07847a17a..0000000000000000000000000000000000000000
--- a/briar-android/src/org/briarproject/android/sharing/ContactSelectorListener.java
+++ /dev/null
@@ -1,28 +0,0 @@
-package org.briarproject.android.sharing;
-
-import android.support.annotation.UiThread;
-
-import org.briarproject.android.DestroyableContext;
-import org.briarproject.api.contact.Contact;
-import org.briarproject.api.contact.ContactId;
-import org.briarproject.api.db.DatabaseExecutor;
-import org.briarproject.api.db.DbException;
-import org.briarproject.api.sync.GroupId;
-
-import java.util.Collection;
-
-interface ContactSelectorListener extends DestroyableContext {
-
-	@Deprecated
-	void runOnDbThread(Runnable runnable);
-
-	@DatabaseExecutor
-	boolean isDisabled(GroupId groupId, Contact c) throws DbException;
-
-	@UiThread
-	void contactsSelected(GroupId groupId, Collection<ContactId> contacts);
-
-	@UiThread
-	void onBackPressed();
-
-}
diff --git a/briar-android/src/org/briarproject/android/sharing/ShareActivity.java b/briar-android/src/org/briarproject/android/sharing/ShareActivity.java
index e89fe277e1251b88c9cdbffe644fbfafbdc92697..53d93628eb587761bb3f5c624b187947ab7747f1 100644
--- a/briar-android/src/org/briarproject/android/sharing/ShareActivity.java
+++ b/briar-android/src/org/briarproject/android/sharing/ShareActivity.java
@@ -2,29 +2,22 @@ package org.briarproject.android.sharing;
 
 import android.content.Intent;
 import android.os.Bundle;
-import android.support.annotation.StringRes;
 import android.support.annotation.UiThread;
-import android.widget.Toast;
 
 import org.briarproject.R;
+import org.briarproject.android.contactselection.ContactSelectorActivity;
+import org.briarproject.android.contactselection.ContactSelectorFragment;
+import org.briarproject.android.contactselection.SelectableContactItem;
 import org.briarproject.android.sharing.BaseMessageFragment.MessageFragmentListener;
 import org.briarproject.api.contact.ContactId;
-import org.briarproject.api.db.DatabaseExecutor;
-import org.briarproject.api.db.DbException;
 import org.briarproject.api.sync.GroupId;
 import org.jetbrains.annotations.NotNull;
 
 import java.util.Collection;
-import java.util.logging.Logger;
 
-import static android.widget.Toast.LENGTH_SHORT;
-import static java.util.logging.Level.WARNING;
-
-public abstract class ShareActivity extends ContactSelectorActivity implements
-		MessageFragmentListener {
-
-	private final static Logger LOG =
-			Logger.getLogger(ShareActivity.class.getName());
+public abstract class ShareActivity
+		extends ContactSelectorActivity<SelectableContactItem>
+		implements MessageFragmentListener {
 
 	@Override
 	public void onCreate(Bundle bundle) {
@@ -46,9 +39,8 @@ public abstract class ShareActivity extends ContactSelectorActivity implements
 
 	@UiThread
 	@Override
-	public void contactsSelected(GroupId groupId,
-			Collection<ContactId> contacts) {
-		super.contactsSelected(groupId, contacts);
+	public void contactsSelected(Collection<ContactId> contacts) {
+		super.contactsSelected(contacts);
 
 		BaseMessageFragment messageFragment = getMessageFragment();
 		getSupportFragmentManager().beginTransaction()
@@ -67,45 +59,12 @@ public abstract class ShareActivity extends ContactSelectorActivity implements
 	@UiThread
 	@Override
 	public boolean onButtonClick(@NotNull String message) {
-		share(groupId, contacts, message);
+		share(contacts, message);
 		setResult(RESULT_OK);
 		supportFinishAfterTransition();
 		return true;
 	}
 
-	private void share(final GroupId g, final Collection<ContactId> contacts,
-			final String msg) {
-		runOnDbThread(new Runnable() {
-			@Override
-			public void run() {
-				try {
-					for (ContactId c : contacts) {
-						share(g, c, msg);
-					}
-				} catch (DbException e) {
-					// TODO proper error handling
-					sharingError();
-					if (LOG.isLoggable(WARNING))
-						LOG.log(WARNING, e.toString(), e);
-				}
-			}
-		});
-	}
-
-	@DatabaseExecutor
-	protected abstract void share(GroupId g, ContactId c, String msg)
-			throws DbException;
-
-	private void sharingError() {
-		runOnUiThreadUnlessDestroyed(new Runnable() {
-			@Override
-			public void run() {
-				int res = getSharingError();
-				Toast.makeText(ShareActivity.this, res, LENGTH_SHORT).show();
-			}
-		});
-	}
-
-	protected abstract @StringRes int getSharingError();
+	abstract void share(Collection<ContactId> contacts, String msg);
 
 }
diff --git a/briar-android/src/org/briarproject/android/sharing/ShareBlogActivity.java b/briar-android/src/org/briarproject/android/sharing/ShareBlogActivity.java
index 7d5cdeef3e63ae5e211dc9dabbe283bafd0f654f..ffd218a381a273597ed5df99091f646307883c5b 100644
--- a/briar-android/src/org/briarproject/android/sharing/ShareBlogActivity.java
+++ b/briar-android/src/org/briarproject/android/sharing/ShareBlogActivity.java
@@ -1,22 +1,26 @@
 package org.briarproject.android.sharing;
 
+import android.widget.Toast;
+
 import org.briarproject.R;
 import org.briarproject.android.ActivityComponent;
-import org.briarproject.api.blogs.BlogSharingManager;
-import org.briarproject.api.contact.Contact;
+import org.briarproject.android.contactselection.ContactSelectorController;
+import org.briarproject.android.contactselection.SelectableContactItem;
+import org.briarproject.android.controller.handler.UiResultExceptionHandler;
 import org.briarproject.api.contact.ContactId;
 import org.briarproject.api.db.DbException;
-import org.briarproject.api.sync.GroupId;
+
+import java.util.Collection;
 
 import javax.inject.Inject;
 
+import static android.widget.Toast.LENGTH_SHORT;
 import static org.briarproject.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH;
 
 public class ShareBlogActivity extends ShareActivity {
 
-	// Fields that are accessed from background threads must be volatile
 	@Inject
-	volatile BlogSharingManager blogSharingManager;
+	ShareBlogController controller;
 
 	@Override
 	BaseMessageFragment getMessageFragment() {
@@ -29,23 +33,33 @@ public class ShareBlogActivity extends ShareActivity {
 	}
 
 	@Override
-	public boolean isDisabled(GroupId groupId, Contact c) throws DbException {
-		return !blogSharingManager.canBeShared(groupId, c);
+	public ContactSelectorController<SelectableContactItem> getController() {
+		return controller;
 	}
 
 	@Override
-	protected void share(GroupId g, ContactId c, String msg)
-			throws DbException {
-		blogSharingManager.sendInvitation(g, c, msg);
+	public int getMaximumMessageLength() {
+		return MAX_MESSAGE_BODY_LENGTH;
 	}
 
 	@Override
-	protected int getSharingError() {
-		return R.string.blogs_sharing_error;
-	}
+	void share(Collection<ContactId> contacts, String msg) {
+		controller.share(groupId, contacts, msg,
+				new UiResultExceptionHandler<Void, DbException>(this) {
+					@Override
+					public void onResultUi(Void result) {
+
+					}
+
+					@Override
+					public void onExceptionUi(DbException exception) {
+						// TODO proper error handling
+						Toast.makeText(ShareBlogActivity.this,
+								R.string.blogs_sharing_error, LENGTH_SHORT)
+								.show();
+					}
+				});
 
-	@Override
-	public int getMaximumMessageLength() {
-		return MAX_MESSAGE_BODY_LENGTH;
 	}
+
 }
diff --git a/briar-android/src/org/briarproject/android/sharing/ShareBlogController.java b/briar-android/src/org/briarproject/android/sharing/ShareBlogController.java
new file mode 100644
index 0000000000000000000000000000000000000000..ffaa0ecac9ef41a1e1475d171935c5212b1a3b6c
--- /dev/null
+++ b/briar-android/src/org/briarproject/android/sharing/ShareBlogController.java
@@ -0,0 +1,18 @@
+package org.briarproject.android.sharing;
+
+import org.briarproject.android.contactselection.ContactSelectorController;
+import org.briarproject.android.contactselection.SelectableContactItem;
+import org.briarproject.android.controller.handler.ResultExceptionHandler;
+import org.briarproject.api.contact.ContactId;
+import org.briarproject.api.db.DbException;
+import org.briarproject.api.sync.GroupId;
+
+import java.util.Collection;
+
+public interface ShareBlogController
+		extends ContactSelectorController<SelectableContactItem> {
+
+	void share(GroupId g, Collection<ContactId> contacts, String msg,
+			ResultExceptionHandler<Void, DbException> handler);
+
+}
diff --git a/briar-android/src/org/briarproject/android/sharing/ShareBlogControllerImpl.java b/briar-android/src/org/briarproject/android/sharing/ShareBlogControllerImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..0955989972db18ae28fb9bf68fdf849e310fe85c
--- /dev/null
+++ b/briar-android/src/org/briarproject/android/sharing/ShareBlogControllerImpl.java
@@ -0,0 +1,74 @@
+package org.briarproject.android.sharing;
+
+import org.briarproject.android.contactselection.ContactSelectorControllerImpl;
+import org.briarproject.android.contactselection.SelectableContactItem;
+import org.briarproject.android.controller.handler.ResultExceptionHandler;
+import org.briarproject.api.blogs.BlogSharingManager;
+import org.briarproject.api.contact.Contact;
+import org.briarproject.api.contact.ContactId;
+import org.briarproject.api.contact.ContactManager;
+import org.briarproject.api.db.DatabaseExecutor;
+import org.briarproject.api.db.DbException;
+import org.briarproject.api.lifecycle.LifecycleManager;
+import org.briarproject.api.sync.GroupId;
+
+import java.util.Collection;
+import java.util.concurrent.Executor;
+
+import javax.inject.Inject;
+
+import static java.util.logging.Level.WARNING;
+
+public class ShareBlogControllerImpl
+		extends ContactSelectorControllerImpl<SelectableContactItem>
+		implements ShareBlogController {
+
+	private final BlogSharingManager blogSharingManager;
+
+	@Inject
+	public ShareBlogControllerImpl(
+			@DatabaseExecutor Executor dbExecutor,
+			LifecycleManager lifecycleManager,
+			ContactManager contactManager,
+			BlogSharingManager blogSharingManager) {
+		super(dbExecutor, lifecycleManager, contactManager);
+		this.blogSharingManager = blogSharingManager;
+	}
+
+	@Override
+	protected boolean isSelected(Contact c, boolean wasSelected) {
+		return wasSelected;
+	}
+
+	@Override
+	protected boolean isDisabled(GroupId g, Contact c) throws DbException {
+		return !blogSharingManager.canBeShared(g, c);
+	}
+
+	@Override
+	protected SelectableContactItem getItem(Contact c, boolean selected,
+			boolean disabled) {
+		return new SelectableContactItem(c, selected, disabled);
+	}
+
+	@Override
+	public void share(final GroupId g, final Collection<ContactId> contacts,
+			final String msg,
+			final ResultExceptionHandler<Void, DbException> handler) {
+		runOnDbThread(new Runnable() {
+			@Override
+			public void run() {
+				try {
+					for (ContactId c : contacts) {
+						blogSharingManager.sendInvitation(g, c, msg);
+					}
+				} catch (DbException e) {
+					if (LOG.isLoggable(WARNING))
+						LOG.log(WARNING, e.toString(), e);
+					handler.onException(e);
+				}
+			}
+		});
+	}
+
+}
diff --git a/briar-android/src/org/briarproject/android/sharing/ShareForumActivity.java b/briar-android/src/org/briarproject/android/sharing/ShareForumActivity.java
index 492e3615b23b4903a61d433d04779170e7e557f4..16133e3cfca2cbfd223c81b6b1d5f0665293138f 100644
--- a/briar-android/src/org/briarproject/android/sharing/ShareForumActivity.java
+++ b/briar-android/src/org/briarproject/android/sharing/ShareForumActivity.java
@@ -1,22 +1,27 @@
 package org.briarproject.android.sharing;
 
+import android.os.Bundle;
+import android.widget.Toast;
+
 import org.briarproject.R;
 import org.briarproject.android.ActivityComponent;
-import org.briarproject.api.contact.Contact;
+import org.briarproject.android.contactselection.ContactSelectorController;
+import org.briarproject.android.contactselection.SelectableContactItem;
+import org.briarproject.android.controller.handler.UiResultExceptionHandler;
 import org.briarproject.api.contact.ContactId;
 import org.briarproject.api.db.DbException;
-import org.briarproject.api.forum.ForumSharingManager;
-import org.briarproject.api.sync.GroupId;
+
+import java.util.Collection;
 
 import javax.inject.Inject;
 
+import static android.widget.Toast.LENGTH_SHORT;
 import static org.briarproject.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH;
 
 public class ShareForumActivity extends ShareActivity {
 
-	// Fields that are accessed from background threads must be volatile
 	@Inject
-	volatile ForumSharingManager forumSharingManager;
+	ShareForumController controller;
 
 	@Override
 	BaseMessageFragment getMessageFragment() {
@@ -29,23 +34,37 @@ public class ShareForumActivity extends ShareActivity {
 	}
 
 	@Override
-	public boolean isDisabled(GroupId groupId, Contact c) throws DbException {
-		return !forumSharingManager.canBeShared(groupId, c);
+	public ContactSelectorController<SelectableContactItem> getController() {
+		return controller;
 	}
 
 	@Override
-	protected void share(GroupId g, ContactId c, String msg)
-			throws DbException {
-		forumSharingManager.sendInvitation(g, c, msg);
+	public void onCreate(Bundle bundle) {
+		super.onCreate(bundle);
 	}
 
 	@Override
-	protected int getSharingError() {
-		return R.string.forum_share_error;
+	public int getMaximumMessageLength() {
+		return MAX_MESSAGE_BODY_LENGTH;
 	}
 
 	@Override
-	public int getMaximumMessageLength() {
-		return MAX_MESSAGE_BODY_LENGTH;
+	void share(Collection<ContactId> contacts, String msg) {
+		controller.share(groupId, contacts, msg,
+				new UiResultExceptionHandler<Void, DbException>(this) {
+					@Override
+					public void onResultUi(Void result) {
+
+					}
+
+					@Override
+					public void onExceptionUi(DbException exception) {
+						// TODO proper error handling
+						Toast.makeText(ShareForumActivity.this,
+								R.string.forum_share_error, LENGTH_SHORT)
+								.show();
+					}
+				});
 	}
+
 }
diff --git a/briar-android/src/org/briarproject/android/sharing/ShareForumController.java b/briar-android/src/org/briarproject/android/sharing/ShareForumController.java
new file mode 100644
index 0000000000000000000000000000000000000000..000398f5fabe9f49acfdc92b3b601c76d22f8d37
--- /dev/null
+++ b/briar-android/src/org/briarproject/android/sharing/ShareForumController.java
@@ -0,0 +1,18 @@
+package org.briarproject.android.sharing;
+
+import org.briarproject.android.contactselection.ContactSelectorController;
+import org.briarproject.android.contactselection.SelectableContactItem;
+import org.briarproject.android.controller.handler.ResultExceptionHandler;
+import org.briarproject.api.contact.ContactId;
+import org.briarproject.api.db.DbException;
+import org.briarproject.api.sync.GroupId;
+
+import java.util.Collection;
+
+public interface ShareForumController
+		extends ContactSelectorController<SelectableContactItem> {
+
+	void share(GroupId g, Collection<ContactId> contacts, String msg,
+			ResultExceptionHandler<Void, DbException> handler);
+
+}
diff --git a/briar-android/src/org/briarproject/android/sharing/ShareForumControllerImpl.java b/briar-android/src/org/briarproject/android/sharing/ShareForumControllerImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..e4bf4a5559d117eeb2cece5e634be171101c686a
--- /dev/null
+++ b/briar-android/src/org/briarproject/android/sharing/ShareForumControllerImpl.java
@@ -0,0 +1,74 @@
+package org.briarproject.android.sharing;
+
+import org.briarproject.android.contactselection.ContactSelectorControllerImpl;
+import org.briarproject.android.contactselection.SelectableContactItem;
+import org.briarproject.android.controller.handler.ResultExceptionHandler;
+import org.briarproject.api.contact.Contact;
+import org.briarproject.api.contact.ContactId;
+import org.briarproject.api.contact.ContactManager;
+import org.briarproject.api.db.DatabaseExecutor;
+import org.briarproject.api.db.DbException;
+import org.briarproject.api.forum.ForumSharingManager;
+import org.briarproject.api.lifecycle.LifecycleManager;
+import org.briarproject.api.sync.GroupId;
+
+import java.util.Collection;
+import java.util.concurrent.Executor;
+
+import javax.inject.Inject;
+
+import static java.util.logging.Level.WARNING;
+
+public class ShareForumControllerImpl
+		extends ContactSelectorControllerImpl<SelectableContactItem>
+		implements ShareForumController {
+
+	private final ForumSharingManager forumSharingManager;
+
+	@Inject
+	public ShareForumControllerImpl(
+			@DatabaseExecutor Executor dbExecutor,
+			LifecycleManager lifecycleManager,
+			ContactManager contactManager,
+			ForumSharingManager forumSharingManager) {
+		super(dbExecutor, lifecycleManager, contactManager);
+		this.forumSharingManager = forumSharingManager;
+	}
+
+	@Override
+	protected boolean isSelected(Contact c, boolean wasSelected) {
+		return wasSelected;
+	}
+
+	@Override
+	protected boolean isDisabled(GroupId g, Contact c) throws DbException {
+		return !forumSharingManager.canBeShared(g, c);
+	}
+
+	@Override
+	protected SelectableContactItem getItem(Contact c, boolean selected,
+			boolean disabled) {
+		return new SelectableContactItem(c, selected, disabled);
+	}
+
+	@Override
+	public void share(final GroupId g, final Collection<ContactId> contacts,
+			final String msg,
+			final ResultExceptionHandler<Void, DbException> handler) {
+		runOnDbThread(new Runnable() {
+			@Override
+			public void run() {
+				try {
+					for (ContactId c : contacts) {
+						forumSharingManager.sendInvitation(g, c, msg);
+					}
+				} catch (DbException e) {
+					if (LOG.isLoggable(WARNING))
+						LOG.log(WARNING, e.toString(), e);
+					handler.onException(e);
+				}
+			}
+		});
+	}
+
+}
diff --git a/briar-android/src/org/briarproject/android/sharing/SharingStatusActivity.java b/briar-android/src/org/briarproject/android/sharing/SharingStatusActivity.java
index f1a7bda25378d76a717d670558ddacc8370d97f4..4e62598ae54fff52118e03c71a7cd31736f21675 100644
--- a/briar-android/src/org/briarproject/android/sharing/SharingStatusActivity.java
+++ b/briar-android/src/org/briarproject/android/sharing/SharingStatusActivity.java
@@ -11,7 +11,6 @@ import org.briarproject.android.contact.ContactItem;
 import org.briarproject.android.view.BriarRecyclerView;
 import org.briarproject.api.contact.Contact;
 import org.briarproject.api.db.DbException;
-import org.briarproject.api.plugins.ConnectionRegistry;
 import org.briarproject.api.sync.GroupId;
 
 import java.util.ArrayList;
@@ -19,8 +18,6 @@ import java.util.Collection;
 import java.util.List;
 import java.util.logging.Logger;
 
-import javax.inject.Inject;
-
 import static java.util.logging.Level.WARNING;
 
 abstract class SharingStatusActivity extends BriarActivity {
@@ -32,10 +29,6 @@ abstract class SharingStatusActivity extends BriarActivity {
 	private BriarRecyclerView sharedByList, sharedWithList;
 	private SharingStatusAdapter sharedByAdapter, sharedWithAdapter;
 
-	// Fields that are accessed from background threads must be volatile
-	@Inject
-	volatile ConnectionRegistry connectionRegistry;
-
 	@Override
 	public void onCreate(Bundle savedInstanceState) {
 		super.onCreate(savedInstanceState);
@@ -109,9 +102,7 @@ abstract class SharingStatusActivity extends BriarActivity {
 				try {
 					List<ContactItem> contactItems = new ArrayList<>();
 					for (Contact c : getSharedBy()) {
-						boolean isConnected =
-								connectionRegistry.isConnected(c.getId());
-						ContactItem item = new ContactItem(c, isConnected);
+						ContactItem item = new ContactItem(c);
 						contactItems.add(item);
 					}
 					displaySharedBy(contactItems);
@@ -140,9 +131,7 @@ abstract class SharingStatusActivity extends BriarActivity {
 				try {
 					List<ContactItem> contactItems = new ArrayList<>();
 					for (Contact c : getSharedWith()) {
-						boolean isConnected =
-								connectionRegistry.isConnected(c.getId());
-						ContactItem item = new ContactItem(c, isConnected);
+						ContactItem item = new ContactItem(c);
 						contactItems.add(item);
 					}
 					displaySharedWith(contactItems);