diff --git a/briar-android/build.gradle b/briar-android/build.gradle
index 11ddb77b20a2f7fa5797258ef242a1b80b8fb308..113bb63dfbc0564fca20eeb344c8211e9e4095d4 100644
--- a/briar-android/build.gradle
+++ b/briar-android/build.gradle
@@ -32,7 +32,7 @@ dependencies {
 	}
 	compile 'info.guardianproject.panic:panic:0.5'
 	compile 'info.guardianproject.trustedintents:trustedintents:0.2'
-	compile 'de.hdodenhof:circleimageview:2.0.0'
+	compile 'de.hdodenhof:circleimageview:2.1.0'
 	compile 'com.google.zxing:core:3.2.1'
 	apt 'com.google.dagger:dagger-compiler:2.0.2'
 	provided 'javax.annotation:jsr250-api:1.0'
@@ -53,7 +53,7 @@ dependencyVerification {
 			'ch.acra:acra:afd5b28934d5166b55f261c85685ad59e8a4ebe9ca1960906afaa8c76d8dc9eb',
 			'info.guardianproject.panic:panic:a7ed9439826db2e9901649892cf9afbe76f00991b768d8f4c26332d7c9406cb2',
 			'info.guardianproject.trustedintents:trustedintents:6221456d8821a8d974c2acf86306900237cf6afaaa94a4c9c44e161350f80f3e',
-			'de.hdodenhof:circleimageview:c76d936395b50705a3f98c9220c22d2599aeb9e609f559f6048975cfc1f686b8',
+			'de.hdodenhof:circleimageview:bcbc588e19e6dcf8c120b1957776bfe229efba5d2fbe5da7156372eeacf65503',
 			'com.google.zxing:core:b4d82452e7a6bf6ec2698904b332431717ed8f9a850224f295aec89de80f2259',
 			'com.android.support:support-v4:81ce890f26d35c75ad17d0f998a7e3230330c3b41e0b629566bc744bee89e448',
 			'com.android.support:appcompat-v7:00f9d93acacd6731f309724054bf51492814b4b2869f16d7d5c0038dcb8c9a0d',
diff --git a/briar-android/res/drawable/ic_contacts.xml b/briar-android/res/drawable/ic_contacts.xml
new file mode 100644
index 0000000000000000000000000000000000000000..cd3e499303967f8488262729e32cd1254d0b242d
--- /dev/null
+++ b/briar-android/res/drawable/ic_contacts.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:alpha="0.54"
+        android:viewportHeight="24.0"
+        android:viewportWidth="24.0">
+	<path
+		android:fillColor="#FF000000"
+		android:pathData="M20,0L4,0v2h16L20,0zM4,24h16v-2L4,22v2zM20,4L4,4c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,6c0,-1.1 -0.9,-2 -2,-2zM12,6.75c1.24,0 2.25,1.01 2.25,2.25s-1.01,2.25 -2.25,2.25S9.75,10.24 9.75,9 10.76,6.75 12,6.75zM17,17L7,17v-1.5c0,-1.67 3.33,-2.5 5,-2.5s5,0.83 5,2.5L17,17z"/>
+</vector>
diff --git a/briar-android/res/drawable/ic_contacts_black_24dp.xml b/briar-android/res/drawable/ic_group.xml
similarity index 100%
rename from briar-android/res/drawable/ic_contacts_black_24dp.xml
rename to briar-android/res/drawable/ic_group.xml
diff --git a/briar-android/res/drawable/navigation_drawer_header.png b/briar-android/res/drawable/navigation_drawer_header.png
new file mode 100644
index 0000000000000000000000000000000000000000..d829743726116470e35367ab3cc443cd855ccefa
Binary files /dev/null and b/briar-android/res/drawable/navigation_drawer_header.png differ
diff --git a/briar-android/res/layout/activity_invitations.xml b/briar-android/res/layout/activity_invitations.xml
deleted file mode 100644
index 4d3d61cea8a12c6d62b84193ee0866489f6d54e3..0000000000000000000000000000000000000000
--- a/briar-android/res/layout/activity_invitations.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<org.briarproject.android.view.BriarRecyclerView
-	android:id="@+id/invitationsView"
-	xmlns:android="http://schemas.android.com/apk/res/android"
-	android:layout_width="match_parent"
-	android:layout_height="match_parent"/>
diff --git a/briar-android/res/layout/fragment_contact_list.xml b/briar-android/res/layout/fragment_contact_list.xml
deleted file mode 100644
index de3bddebe14b129d135d400ace09f2d6081a9f4e..0000000000000000000000000000000000000000
--- a/briar-android/res/layout/fragment_contact_list.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<org.briarproject.android.view.BriarRecyclerView
-		xmlns:android="http://schemas.android.com/apk/res/android"
-		android:id="@+id/contactList"
-		android:layout_width="match_parent"
-		android:layout_height="match_parent"/>
diff --git a/briar-android/res/layout/introduction_contact_chooser.xml b/briar-android/res/layout/list.xml
similarity index 64%
rename from briar-android/res/layout/introduction_contact_chooser.xml
rename to briar-android/res/layout/list.xml
index 4a99ecbed9378374dfb47cf3e3d355d52e564569..0fbe7cede515e0b4c9e85998d44d9d2de013e8ce 100644
--- a/briar-android/res/layout/introduction_contact_chooser.xml
+++ b/briar-android/res/layout/list.xml
@@ -1,8 +1,8 @@
 <?xml version="1.0" encoding="utf-8"?>
 <org.briarproject.android.view.BriarRecyclerView
+	android:id="@+id/list"
 	xmlns:android="http://schemas.android.com/apk/res/android"
-	xmlns:tools="http://schemas.android.com/tools"
-	android:id="@+id/contactList"
+	xmlns:app="http://schemas.android.com/apk/res-auto"
 	android:layout_width="match_parent"
 	android:layout_height="match_parent"
-	tools:listitem="@layout/list_item_contact"/>
+	app:scrollToEnd="false"/>
diff --git a/briar-android/res/layout/list_item_forum.xml b/briar-android/res/layout/list_item_forum.xml
index c143b6a22ba0773ccb4fd3d4aa487d696d34a8e6..5c4df7bd173ffe09c3142510844775fa50777dcb 100644
--- a/briar-android/res/layout/list_item_forum.xml
+++ b/briar-android/res/layout/list_item_forum.xml
@@ -15,7 +15,8 @@
 		android:layout_alignParentLeft="true"
 		android:layout_alignParentStart="true"
 		android:layout_centerVertical="true"
-		android:layout_marginRight="@dimen/listitem_horizontal_margin"/>
+		android:layout_marginRight="@dimen/listitem_horizontal_margin"
+		android:layout_marginTop="@dimen/listitem_horizontal_margin"/>
 
 	<org.thoughtcrime.securesms.components.emoji.EmojiTextView
 		android:id="@+id/forumNameView"
diff --git a/briar-android/res/layout/list_item_group.xml b/briar-android/res/layout/list_item_group.xml
new file mode 100644
index 0000000000000000000000000000000000000000..afb0b642c1d63557b3eb94111b25cf1921bfcfaa
--- /dev/null
+++ b/briar-android/res/layout/list_item_group.xml
@@ -0,0 +1,107 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout
+	xmlns:android="http://schemas.android.com/apk/res/android"
+	xmlns:tools="http://schemas.android.com/tools"
+	android:layout_width="match_parent"
+	android:layout_height="wrap_content"
+	android:layout_marginLeft="@dimen/listitem_horizontal_margin"
+	android:layout_marginStart="@dimen/listitem_horizontal_margin"
+	android:background="?attr/selectableItemBackground">
+
+	<org.briarproject.android.view.TextAvatarView
+		android:id="@+id/avatarView"
+		android:layout_width="@dimen/listitem_picture_frame_size"
+		android:layout_height="@dimen/listitem_picture_frame_size"
+		android:layout_alignParentLeft="true"
+		android:layout_alignParentStart="true"
+		android:layout_marginEnd="@dimen/listitem_horizontal_margin"
+		android:layout_marginRight="@dimen/listitem_horizontal_margin"
+		android:layout_marginTop="@dimen/listitem_horizontal_margin"/>
+
+	<org.thoughtcrime.securesms.components.emoji.EmojiTextView
+		android:id="@+id/nameView"
+		android:layout_width="wrap_content"
+		android:layout_height="wrap_content"
+		android:layout_alignParentTop="true"
+		android:layout_marginTop="@dimen/listitem_horizontal_margin"
+		android:layout_toEndOf="@+id/avatarView"
+		android:layout_toRightOf="@+id/avatarView"
+		android:maxLines="2"
+		android:textColor="@color/briar_text_primary"
+		android:textSize="@dimen/text_size_medium"
+		tools:text="This is a name of a Private Group"/>
+
+	<org.thoughtcrime.securesms.components.emoji.EmojiTextView
+		android:id="@+id/creatorView"
+		android:layout_width="wrap_content"
+		android:layout_height="wrap_content"
+		android:layout_below="@+id/nameView"
+		android:layout_marginBottom="@dimen/margin_small"
+		android:layout_toEndOf="@+id/avatarView"
+		android:layout_toRightOf="@+id/avatarView"
+		android:paddingTop="@dimen/margin_small"
+		android:textColor="@color/briar_text_secondary"
+		android:textSize="@dimen/text_size_small"
+		tools:text="Created by Santa Claus"/>
+
+	<TextView
+		android:id="@+id/messageCountView"
+		android:layout_width="wrap_content"
+		android:layout_height="wrap_content"
+		android:layout_below="@+id/creatorView"
+		android:layout_marginBottom="@dimen/margin_small"
+		android:layout_toEndOf="@+id/avatarView"
+		android:layout_toRightOf="@+id/avatarView"
+		android:paddingTop="@dimen/margin_small"
+		android:textColor="@color/briar_text_secondary"
+		android:textSize="@dimen/text_size_small"
+		tools:text="1337 messages"
+		tools:visibility="visible"/>
+
+	<TextView
+		android:id="@+id/dateView"
+		android:layout_width="wrap_content"
+		android:layout_height="wrap_content"
+		android:layout_alignParentEnd="true"
+		android:layout_alignParentRight="true"
+		android:layout_below="@+id/creatorView"
+		android:layout_marginEnd="@dimen/listitem_horizontal_margin"
+		android:layout_marginRight="@dimen/listitem_horizontal_margin"
+		android:paddingTop="@dimen/margin_small"
+		android:textColor="@color/briar_text_secondary"
+		android:textSize="@dimen/text_size_small"
+		tools:text="3 weeks ago, 12:00"
+		tools:visibility="visible"/>
+
+	<TextView
+		android:id="@+id/statusView"
+		android:layout_width="wrap_content"
+		android:layout_height="wrap_content"
+		android:layout_below="@+id/messageCountView"
+		android:layout_toEndOf="@+id/avatarView"
+		android:layout_toRightOf="@+id/avatarView"
+		android:paddingTop="@dimen/margin_small"
+		android:textColor="@color/briar_text_tertiary"
+		tools:text="@string/groups_group_is_empty"/>
+
+	<Button
+		android:id="@+id/removeButton"
+		style="@style/BriarButtonFlat.Negative"
+		android:layout_width="wrap_content"
+		android:layout_height="wrap_content"
+		android:layout_alignBottom="@+id/divider"
+		android:layout_alignParentRight="true"
+		android:layout_toRightOf="@+id/statusView"
+		android:text="@string/groups_remove"
+		tools:visibility="gone"/>
+
+	<View
+		android:id="@+id/divider"
+		style="@style/Divider.ForumList"
+		android:layout_alignParentLeft="true"
+		android:layout_alignParentStart="true"
+		android:layout_below="@+id/statusView"
+		android:layout_marginTop="@dimen/listitem_horizontal_margin"/>
+
+</RelativeLayout>
+
diff --git a/briar-android/res/layout/navigation_header.xml b/briar-android/res/layout/navigation_header.xml
index cc7f80ca838b5834b310a916456e1709bad7521d..ff120e9dd000903a1860a220a6c7d5e81cc128ca 100644
--- a/briar-android/res/layout/navigation_header.xml
+++ b/briar-android/res/layout/navigation_header.xml
@@ -1,21 +1,10 @@
 <?xml version="1.0" encoding="utf-8"?>
-<FrameLayout
+<ImageView
 	xmlns:android="http://schemas.android.com/apk/res/android"
 	xmlns:tools="http://schemas.android.com/tools"
 	android:layout_width="match_parent"
-	android:layout_height="wrap_content"
-	tools:showIn="@layout/navigation_menu">
-
-	<ImageView
-		android:layout_width="100dp"
-		android:layout_height="100dp"
-		android:layout_gravity="top|center_horizontal"
-		android:layout_margin="@dimen/margin_medium"
-		android:contentDescription="@string/app_name"
-		android:src="@drawable/briar_logo_large"/>
-
-	<View
-		style="@style/Divider.Horizontal"
-		android:layout_gravity="bottom"/>
-
-</FrameLayout>
\ No newline at end of file
+	android:layout_height="100dp"
+	android:contentDescription="@string/app_name"
+	android:scaleType="fitStart"
+	android:src="@drawable/navigation_drawer_header"
+	tools:showIn="@layout/navigation_menu"/>
diff --git a/briar-android/res/layout/text_avatar_view.xml b/briar-android/res/layout/text_avatar_view.xml
index 211d0f000ee231c52f03fd0067b57bd04ea29424..de7f3b868f670a1d08e76a6da76d11305622dbcb 100644
--- a/briar-android/res/layout/text_avatar_view.xml
+++ b/briar-android/res/layout/text_avatar_view.xml
@@ -1,7 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <merge
 	xmlns:android="http://schemas.android.com/apk/res/android"
-	xmlns:app="http://schemas.android.com/apk/res-auto"
 	xmlns:tools="http://schemas.android.com/tools"
 	tools:showIn="@layout/list_item_forum">
 
@@ -11,16 +10,15 @@
 		android:layout_width="@dimen/avatar_forum_size"
 		android:layout_height="@dimen/avatar_forum_size"
 		android:layout_gravity="bottom|left"
-		android:src="@android:color/transparent"
-		app:civ_fill_color="@color/briar_button_positive"/>
+		android:src="@color/briar_button_positive"/>
 
 	<android.support.v7.widget.AppCompatTextView
 		android:id="@+id/textAvatarView"
 		android:layout_width="wrap_content"
 		android:layout_height="wrap_content"
 		android:layout_gravity="center"
-		android:layout_marginRight="@dimen/listitem_picture_frame_offset"
-		android:layout_marginTop="@dimen/listitem_picture_frame_offset"
+		android:layout_marginRight="@dimen/listitem_picture_frame_offset_horizontal"
+		android:layout_marginTop="@dimen/listitem_picture_frame_offset_vertical"
 		android:maxLength="1"
 		android:shadowColor="@color/forum_avatar_shadow"
 		android:shadowDx="0"
diff --git a/briar-android/res/menu/groups_list_actions.xml b/briar-android/res/menu/groups_list_actions.xml
new file mode 100644
index 0000000000000000000000000000000000000000..511224ff1650225e103d86b24531ea559da54bcf
--- /dev/null
+++ b/briar-android/res/menu/groups_list_actions.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu
+	xmlns:android="http://schemas.android.com/apk/res/android"
+	xmlns:app="http://schemas.android.com/apk/res-auto">
+
+	<item
+		android:id="@+id/action_add_group"
+		android:icon="@drawable/ic_add_white"
+		android:title="@string/groups_add_group_title"
+		app:showAsAction="ifRoom"/>
+
+</menu>
\ No newline at end of file
diff --git a/briar-android/res/menu/navigation_drawer.xml b/briar-android/res/menu/navigation_drawer.xml
index dfeb05e62f5b18361d3e8dc08ea2bda45879a83b..9c98a51871f1a27c85dc638f60d476e625d99df8 100644
--- a/briar-android/res/menu/navigation_drawer.xml
+++ b/briar-android/res/menu/navigation_drawer.xml
@@ -5,8 +5,12 @@
 	<group android:checkableBehavior="single">
 		<item
 			android:id="@+id/nav_btn_contacts"
-			android:icon="@drawable/ic_contacts_black_24dp"
+			android:icon="@drawable/ic_contacts"
 			android:title="@string/contact_list_button"/>
+		<item
+			android:id="@+id/nav_btn_groups"
+			android:icon="@drawable/ic_group"
+			android:title="@string/groups_button"/>
 		<item
 			android:id="@+id/nav_btn_forums"
 			android:icon="@drawable/ic_forums_black_24dp"
@@ -15,6 +19,9 @@
 			android:id="@+id/nav_btn_blogs"
 			android:icon="@drawable/blogs"
 			android:title="@string/blogs_button"/>
+	</group>
+
+	<group android:checkableBehavior="single">
 		<item
 			android:id="@+id/nav_btn_settings"
 			android:icon="@drawable/ic_settings_black_24dp"
diff --git a/briar-android/res/values/dimens.xml b/briar-android/res/values/dimens.xml
index 5239b500dbe9429b3e143fe6625f01c0ba99395f..cc465f1ea840716b5d315141c85ce229e4caed28 100644
--- a/briar-android/res/values/dimens.xml
+++ b/briar-android/res/values/dimens.xml
@@ -24,8 +24,9 @@
 	<dimen name="listitem_height_one_line_avatar">56dp</dimen>
 	<dimen name="listitem_picture_size">48dp</dimen>
 	<dimen name="listitem_picture_size_small">23dp</dimen>
-	<dimen name="listitem_picture_frame_size">53dp</dimen>
-	<dimen name="listitem_picture_frame_offset">2dp</dimen>
+	<dimen name="listitem_picture_frame_size">51dp</dimen>
+	<dimen name="listitem_picture_frame_offset_horizontal">1dp</dimen>
+	<dimen name="listitem_picture_frame_offset_vertical">2dp</dimen>
 	<dimen name="listitem_selectable_picture_size">40dp</dimen>
 	<dimen name="avatar_forum_size">48dp</dimen>
 	<dimen name="avatar_border_width">2dp</dimen>
diff --git a/briar-android/res/values/strings.xml b/briar-android/res/values/strings.xml
index 48e3e1f778980382099fad974b3c204369a698e9..883dceee683a5640a16a3c49887b1589278b7aa5 100644
--- a/briar-android/res/values/strings.xml
+++ b/briar-android/res/values/strings.xml
@@ -34,6 +34,7 @@
 	<string name="nav_drawer_open_description">Open the navigation drawer</string>
 	<string name="nav_drawer_close_description">Close the navigation drawer</string>
 	<string name="contact_list_button">Contacts</string>
+	<string name="groups_button">Private Groups</string>
 	<string name="forums_button">Forums</string>
 	<string name="blogs_button">Blogs</string>
 	<string name="settings_button">Settings</string>
@@ -143,6 +144,18 @@
 		<item quantity="other">%d new contacts added.</item>
 	</plurals>
 
+	<!-- Private Groups -->
+	<string name="groups_list_empty">You are not participating in any groups.\n\nTap the + icon at the top to create a group yourself or ask your contacts to get invited into one of their groups.</string>
+	<string name="groups_created_by">Created by %s</string>
+	<plurals name="messages">
+		<item quantity="one">%d message</item>
+		<item quantity="other">%d messages</item>
+	</plurals>
+	<string name="groups_group_is_empty">This group is empty</string>
+	<string name="groups_group_is_dissolved">This group is dissolved</string>
+	<string name="groups_remove">Remove</string>
+	<string name="groups_add_group_title">Add Private Group</string>
+
 	<!-- Forums -->
 	<string name="no_forums">You don\'t have any forums yet.\n\nWhy don\'t you create a new one yourself by tapping the + icon at the top?\n\nYou can also ask your contacts to share forums with you.</string>
 	<string name="create_forum_title">New Forum</string>
diff --git a/briar-android/res/values/styles.xml b/briar-android/res/values/styles.xml
index 76c9c0a1f45ee25969ab480473235f6056cbef80..2056a57c793e076b34a1fd96bda1aee2d0e7f4f4 100644
--- a/briar-android/res/values/styles.xml
+++ b/briar-android/res/values/styles.xml
@@ -93,9 +93,6 @@
 	<style name="BriarAvatar">
 		<item name="civ_border_width">@dimen/avatar_border_width</item>
 		<item name="civ_border_color">@color/briar_primary</item>
-
-		<!-- Remove when we are using 'de.hdodenhof:circleimageview:2.1.0' -->
-		<item name="civ_border_overlay">true</item>
 	</style>
 
 	<style name="NavMenuButton" parent="Widget.AppCompat.Button.Borderless.Colored">
diff --git a/briar-android/src/org/briarproject/android/ActivityComponent.java b/briar-android/src/org/briarproject/android/ActivityComponent.java
index 990aea7a2e13af338a3bf12fa09acbcca70ce50c..d7cab786be65373e4c5efec208e210b539fdabb5 100644
--- a/briar-android/src/org/briarproject/android/ActivityComponent.java
+++ b/briar-android/src/org/briarproject/android/ActivityComponent.java
@@ -28,6 +28,7 @@ import org.briarproject.android.keyagreement.KeyAgreementActivity;
 import org.briarproject.android.keyagreement.ShowQrCodeFragment;
 import org.briarproject.android.panic.PanicPreferencesActivity;
 import org.briarproject.android.panic.PanicResponderActivity;
+import org.briarproject.android.privategroup.list.GroupListFragment;
 import org.briarproject.android.sharing.ContactSelectorFragment;
 import org.briarproject.android.sharing.InvitationsBlogActivity;
 import org.briarproject.android.sharing.InvitationsForumActivity;
@@ -114,6 +115,7 @@ public interface ActivityComponent {
 
 	// Fragments
 	void inject(ContactListFragment fragment);
+	void inject(GroupListFragment fragment);
 	void inject(ForumListFragment fragment);
 	void inject(FeedFragment fragment);
 	void inject(IntroFragment fragment);
diff --git a/briar-android/src/org/briarproject/android/ActivityModule.java b/briar-android/src/org/briarproject/android/ActivityModule.java
index 21a7d35ef0312f13904c567ca24c88924182231f..f415cc327f814902f5d5e150962073575f067913 100644
--- a/briar-android/src/org/briarproject/android/ActivityModule.java
+++ b/briar-android/src/org/briarproject/android/ActivityModule.java
@@ -22,6 +22,8 @@ import org.briarproject.android.controller.SetupController;
 import org.briarproject.android.controller.SetupControllerImpl;
 import org.briarproject.android.forum.ForumController;
 import org.briarproject.android.forum.ForumControllerImpl;
+import org.briarproject.android.privategroup.list.GroupListController;
+import org.briarproject.android.privategroup.list.GroupListControllerImpl;
 
 import dagger.Module;
 import dagger.Provides;
@@ -90,6 +92,13 @@ public class ActivityModule {
 		return dbController;
 	}
 
+	@ActivityScope
+	@Provides
+	protected GroupListController provideGroupListController(
+			GroupListControllerImpl groupListController) {
+		return groupListController;
+	}
+
 	@ActivityScope
 	@Provides
 	protected ForumController provideForumController(
diff --git a/briar-android/src/org/briarproject/android/AndroidComponent.java b/briar-android/src/org/briarproject/android/AndroidComponent.java
index ce051f5349f136428e6921607bca39516f24abe4..9b7e6edbaf0b64aecdf3ab00eab40732aeb3d419 100644
--- a/briar-android/src/org/briarproject/android/AndroidComponent.java
+++ b/briar-android/src/org/briarproject/android/AndroidComponent.java
@@ -35,6 +35,7 @@ import org.briarproject.api.messaging.MessagingManager;
 import org.briarproject.api.messaging.PrivateMessageFactory;
 import org.briarproject.api.plugins.ConnectionRegistry;
 import org.briarproject.api.plugins.PluginManager;
+import org.briarproject.api.privategroup.PrivateGroupManager;
 import org.briarproject.api.settings.SettingsManager;
 import org.briarproject.plugins.AndroidPluginsModule;
 import org.briarproject.system.AndroidSystemModule;
@@ -93,6 +94,8 @@ public interface AndroidComponent extends CoreEagerSingletons {
 
 	PrivateMessageFactory privateMessageFactory();
 
+	PrivateGroupManager privateGroupManager();
+
 	ForumManager forumManager();
 
 	ForumSharingManager forumSharingManager();
diff --git a/briar-android/src/org/briarproject/android/BriarFragmentActivity.java b/briar-android/src/org/briarproject/android/BriarFragmentActivity.java
index 946ebbd3569b672e1df1a331f584bbeada7f0407..70443553014378783e39d1844d63d4edd19abe9c 100644
--- a/briar-android/src/org/briarproject/android/BriarFragmentActivity.java
+++ b/briar-android/src/org/briarproject/android/BriarFragmentActivity.java
@@ -10,6 +10,7 @@ import org.briarproject.android.blogs.FeedFragment;
 import org.briarproject.android.contact.ContactListFragment;
 import org.briarproject.android.forum.ForumListFragment;
 import org.briarproject.android.fragment.BaseFragment;
+import org.briarproject.android.privategroup.list.GroupListFragment;
 
 import static android.support.v4.app.FragmentManager.POP_BACK_STACK_INCLUSIVE;
 
@@ -26,6 +27,8 @@ public abstract class BriarFragmentActivity extends BriarActivity {
 
 		if (fragmentTag.equals(ContactListFragment.TAG)) {
 			actionBar.setTitle(R.string.contact_list_button);
+		} else if (fragmentTag.equals(GroupListFragment.TAG)) {
+			actionBar.setTitle(R.string.groups_button);
 		} else if (fragmentTag.equals(ForumListFragment.TAG)) {
 			actionBar.setTitle(R.string.forums_button);
 		} else if (fragmentTag.equals(FeedFragment.TAG)) {
diff --git a/briar-android/src/org/briarproject/android/NavDrawerActivity.java b/briar-android/src/org/briarproject/android/NavDrawerActivity.java
index 62af704f57d51f06484c9b640b46b63b089e87ea..751700559ebc95d966a558eb0e4ad39458d51fea 100644
--- a/briar-android/src/org/briarproject/android/NavDrawerActivity.java
+++ b/briar-android/src/org/briarproject/android/NavDrawerActivity.java
@@ -28,6 +28,7 @@ import org.briarproject.android.controller.TransportStateListener;
 import org.briarproject.android.controller.handler.UiResultHandler;
 import org.briarproject.android.forum.ForumListFragment;
 import org.briarproject.android.fragment.BaseFragment.BaseFragmentListener;
+import org.briarproject.android.privategroup.list.GroupListFragment;
 import org.briarproject.api.TransportId;
 import org.briarproject.api.identity.LocalAuthor;
 
@@ -180,6 +181,9 @@ public class NavDrawerActivity extends BriarFragmentActivity implements
 			case R.id.nav_btn_contacts:
 				startFragment(ContactListFragment.newInstance());
 				break;
+			case R.id.nav_btn_groups:
+				startFragment(GroupListFragment.newInstance());
+				break;
 			case R.id.nav_btn_forums:
 				startFragment(ForumListFragment.newInstance());
 				break;
diff --git a/briar-android/src/org/briarproject/android/contact/ContactListFragment.java b/briar-android/src/org/briarproject/android/contact/ContactListFragment.java
index c4536bd9cbaebc97418c69c6cbab1bcf99cec082..891bfcfbc254b38e4b3242b1e092ae8d9d39f211 100644
--- a/briar-android/src/org/briarproject/android/contact/ContactListFragment.java
+++ b/briar-android/src/org/briarproject/android/contact/ContactListFragment.java
@@ -109,7 +109,7 @@ public class ContactListFragment extends BaseFragment implements EventListener {
 		setHasOptionsMenu(true);
 
 		View contentView =
-				inflater.inflate(R.layout.fragment_contact_list, container,
+				inflater.inflate(R.layout.list, container,
 						false);
 
 		BaseContactListAdapter.OnItemClickListener onItemClickListener =
@@ -141,7 +141,7 @@ public class ContactListFragment extends BaseFragment implements EventListener {
 				};
 
 		adapter = new ContactListAdapter(getContext(), onItemClickListener);
-		list = (BriarRecyclerView) contentView.findViewById(R.id.contactList);
+		list = (BriarRecyclerView) contentView.findViewById(R.id.list);
 		list.setLayoutManager(new LinearLayoutManager(getContext()));
 		list.setAdapter(adapter);
 		list.setEmptyText(getString(R.string.no_contacts));
diff --git a/briar-android/src/org/briarproject/android/introduction/ContactChooserFragment.java b/briar-android/src/org/briarproject/android/introduction/ContactChooserFragment.java
index 6a01c3020af3c6c0d1defea18b7138995729659b..b26d3a8dc05c9dbdf8911d96d13d81d116437846 100644
--- a/briar-android/src/org/briarproject/android/introduction/ContactChooserFragment.java
+++ b/briar-android/src/org/briarproject/android/introduction/ContactChooserFragment.java
@@ -77,10 +77,7 @@ public class ContactChooserFragment extends BaseFragment {
 	public View onCreateView(LayoutInflater inflater, ViewGroup container,
 			Bundle savedInstanceState) {
 
-		View contentView =
-				inflater.inflate(R.layout.introduction_contact_chooser,
-						container, false);
-
+		View contentView = inflater.inflate(R.layout.list, container, false);
 
 		if (Build.VERSION.SDK_INT >= 21) {
 			setExitTransition(new Fade());
@@ -105,7 +102,7 @@ public class ContactChooserFragment extends BaseFragment {
 				};
 		adapter = new ContactChooserAdapter(getActivity(), onItemClickListener);
 
-		list = (BriarRecyclerView) contentView.findViewById(R.id.contactList);
+		list = (BriarRecyclerView) contentView.findViewById(R.id.list);
 		list.setLayoutManager(new LinearLayoutManager(getActivity()));
 		list.setAdapter(adapter);
 		list.setEmptyText(getString(R.string.no_contacts));
diff --git a/briar-android/src/org/briarproject/android/privategroup/list/GroupItem.java b/briar-android/src/org/briarproject/android/privategroup/list/GroupItem.java
new file mode 100644
index 0000000000000000000000000000000000000000..89b208b00e2638488760fc8a2fd6259099e72933
--- /dev/null
+++ b/briar-android/src/org/briarproject/android/privategroup/list/GroupItem.java
@@ -0,0 +1,78 @@
+package org.briarproject.android.privategroup.list;
+
+import org.briarproject.api.identity.Author;
+import org.briarproject.api.privategroup.GroupMessageHeader;
+import org.briarproject.api.privategroup.PrivateGroup;
+import org.briarproject.api.sync.GroupId;
+import org.jetbrains.annotations.NotNull;
+
+// This class is not thread-safe
+class GroupItem {
+
+	private final PrivateGroup privateGroup;
+	private int messageCount;
+	private long lastUpdate;
+	private int unreadCount;
+	private boolean dissolved;
+
+	GroupItem(@NotNull PrivateGroup privateGroup, int messageCount,
+			long lastUpdate, int unreadCount, boolean dissolved) {
+
+		this.privateGroup = privateGroup;
+		this.messageCount = messageCount;
+		this.lastUpdate = lastUpdate;
+		this.unreadCount = unreadCount;
+		this.dissolved = dissolved;
+	}
+
+	void addMessageHeader(GroupMessageHeader header) {
+		messageCount++;
+		if (header.getTimestamp() > lastUpdate) {
+			lastUpdate = header.getTimestamp();
+		}
+		if (!header.isRead()) {
+			unreadCount++;
+		}
+	}
+
+	@NotNull
+	PrivateGroup getPrivateGroup() {
+		return privateGroup;
+	}
+
+	@NotNull
+	GroupId getId() {
+		return privateGroup.getId();
+	}
+
+	@NotNull
+	Author getCreator() {
+		return privateGroup.getAuthor();
+	}
+
+	@NotNull
+	String getName() {
+		return privateGroup.getName();
+	}
+
+	boolean isEmpty() {
+		return messageCount == 0;
+	}
+
+	int getMessageCount() {
+		return messageCount;
+	}
+
+	long getLastUpdate() {
+		return lastUpdate;
+	}
+
+	int getUnreadCount() {
+		return unreadCount;
+	}
+
+	boolean isDissolved() {
+		return dissolved;
+	}
+
+}
diff --git a/briar-android/src/org/briarproject/android/privategroup/list/GroupListAdapter.java b/briar-android/src/org/briarproject/android/privategroup/list/GroupListAdapter.java
new file mode 100644
index 0000000000000000000000000000000000000000..20210a486c9a903cd63d1f686d92a390a0d89716
--- /dev/null
+++ b/briar-android/src/org/briarproject/android/privategroup/list/GroupListAdapter.java
@@ -0,0 +1,79 @@
+package org.briarproject.android.privategroup.list;
+
+import android.content.Context;
+import android.support.annotation.Nullable;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import org.briarproject.R;
+import org.briarproject.android.privategroup.list.GroupViewHolder.OnGroupRemoveClickListener;
+import org.briarproject.android.util.BriarAdapter;
+import org.briarproject.api.sync.GroupId;
+import org.jetbrains.annotations.NotNull;
+
+import static android.support.v7.util.SortedList.INVALID_POSITION;
+
+class GroupListAdapter extends BriarAdapter<GroupItem, GroupViewHolder> {
+
+	private final OnGroupRemoveClickListener listener;
+
+	GroupListAdapter(Context ctx, OnGroupRemoveClickListener listener) {
+		super(ctx, GroupItem.class);
+		this.listener = listener;
+	}
+
+	@Override
+	public GroupViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+		View v = LayoutInflater.from(ctx).inflate(
+				R.layout.list_item_group, parent, false);
+		return new GroupViewHolder(v);
+	}
+
+	@Override
+	public void onBindViewHolder(GroupViewHolder ui, int position) {
+		ui.bindView(ctx, getItemAt(position), listener);
+	}
+
+	@Override
+	public int compare(GroupItem a, GroupItem b) {
+		if (a == b) return 0;
+		// The group with the latest message comes first
+		long aTime = a.getLastUpdate(), bTime = b.getLastUpdate();
+		if (aTime > bTime) return -1;
+		if (aTime < bTime) return 1;
+		// Break ties by group name
+		String aName = a.getName();
+		String bName = b.getName();
+		return String.CASE_INSENSITIVE_ORDER.compare(aName, bName);
+	}
+
+	@Override
+	public boolean areContentsTheSame(GroupItem a, GroupItem b) {
+		return a.getMessageCount() == b.getMessageCount() &&
+				a.getLastUpdate() == b.getLastUpdate() &&
+				a.getUnreadCount() == b.getUnreadCount() &&
+				a.isDissolved() == b.isDissolved();
+	}
+
+	@Override
+	public boolean areItemsTheSame(GroupItem a, GroupItem b) {
+		return a.getId().equals(b.getId());
+	}
+
+	int findItemPosition(@NotNull GroupId g) {
+		for (int i = 0; i < items.size(); i++) {
+			GroupItem item = items.get(i);
+			if (item.getId().equals(g)) {
+				return i;
+			}
+		}
+		return INVALID_POSITION;
+	}
+
+	void removeItem(GroupId groupId) {
+		int pos = findItemPosition(groupId);
+		if (pos != INVALID_POSITION) items.removeItemAt(pos);
+	}
+
+}
diff --git a/briar-android/src/org/briarproject/android/privategroup/list/GroupListController.java b/briar-android/src/org/briarproject/android/privategroup/list/GroupListController.java
new file mode 100644
index 0000000000000000000000000000000000000000..27e8c90d4a04104df35a4e26a4bff4b1c3773a97
--- /dev/null
+++ b/briar-android/src/org/briarproject/android/privategroup/list/GroupListController.java
@@ -0,0 +1,44 @@
+package org.briarproject.android.privategroup.list;
+
+import android.support.annotation.UiThread;
+
+import org.briarproject.android.DestroyableContext;
+import org.briarproject.android.controller.DbController;
+import org.briarproject.android.controller.handler.ResultExceptionHandler;
+import org.briarproject.api.db.DbException;
+import org.briarproject.api.privategroup.GroupMessageHeader;
+import org.briarproject.api.sync.GroupId;
+
+import java.util.Collection;
+
+public interface GroupListController extends DbController {
+
+	/**
+	 * The listener must be set right after the controller was injected
+	 */
+	void setGroupListListener(GroupListListener listener);
+
+	@UiThread
+	void onStart();
+
+	@UiThread
+	void onStop();
+
+	void loadGroups(
+			ResultExceptionHandler<Collection<GroupItem>, DbException> result);
+
+	void removeGroup(GroupId g,
+			ResultExceptionHandler<Void, DbException> result);
+
+	interface GroupListListener extends DestroyableContext {
+		@UiThread
+		void onGroupMessageAdded(GroupMessageHeader header);
+
+		@UiThread
+		void onGroupAdded(GroupId groupId);
+
+		@UiThread
+		void onGroupRemoved(GroupId groupId);
+	}
+
+}
diff --git a/briar-android/src/org/briarproject/android/privategroup/list/GroupListControllerImpl.java b/briar-android/src/org/briarproject/android/privategroup/list/GroupListControllerImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..6c30011dc60ac6a23e8c9a43f111a9d3dbea7dc6
--- /dev/null
+++ b/briar-android/src/org/briarproject/android/privategroup/list/GroupListControllerImpl.java
@@ -0,0 +1,156 @@
+package org.briarproject.android.privategroup.list;
+
+import android.support.annotation.CallSuper;
+
+import org.briarproject.android.api.AndroidNotificationManager;
+import org.briarproject.android.controller.DbControllerImpl;
+import org.briarproject.android.controller.handler.ResultExceptionHandler;
+import org.briarproject.api.clients.MessageTracker.GroupCount;
+import org.briarproject.api.db.DbException;
+import org.briarproject.api.event.Event;
+import org.briarproject.api.event.EventBus;
+import org.briarproject.api.event.EventListener;
+import org.briarproject.api.event.GroupAddedEvent;
+import org.briarproject.api.event.GroupMessageAddedEvent;
+import org.briarproject.api.event.GroupRemovedEvent;
+import org.briarproject.api.identity.IdentityManager;
+import org.briarproject.api.privategroup.PrivateGroup;
+import org.briarproject.api.privategroup.PrivateGroupManager;
+import org.briarproject.api.sync.ClientId;
+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.WARNING;
+
+public class GroupListControllerImpl extends DbControllerImpl
+		implements GroupListController, EventListener {
+
+	private static final Logger LOG =
+			Logger.getLogger(GroupListControllerImpl.class.getName());
+
+	@Inject
+	PrivateGroupManager groupManager;
+	@Inject
+	EventBus eventBus;
+	@Inject
+	AndroidNotificationManager notificationManager;
+	@Inject
+	IdentityManager identityManager;
+
+	protected volatile GroupListListener listener;
+
+	@Inject
+	GroupListControllerImpl() {
+
+	}
+
+	@Override
+	public void setGroupListListener(GroupListListener listener) {
+		this.listener = listener;
+	}
+
+	@CallSuper
+	public void onStart() {
+		if (listener == null)
+			throw new IllegalStateException(
+					"GroupListListener needs to be attached");
+		eventBus.addListener(this);
+	}
+
+	@CallSuper
+	public void onStop() {
+		eventBus.removeListener(this);
+	}
+
+	@Override
+	@CallSuper
+	public void eventOccurred(Event e) {
+		if (e instanceof GroupMessageAddedEvent) {
+			final GroupMessageAddedEvent m = (GroupMessageAddedEvent) e;
+			LOG.info("New group message added");
+			listener.runOnUiThreadUnlessDestroyed(new Runnable() {
+				@Override
+				public void run() {
+					listener.onGroupMessageAdded(m.getHeader());
+				}
+			});
+		} else if (e instanceof GroupAddedEvent) {
+			final GroupAddedEvent gae = (GroupAddedEvent) e;
+			ClientId id = gae.getGroup().getClientId();
+			if (id.equals(groupManager.getClientId())) {
+				LOG.info("Private group added");
+				listener.runOnUiThreadUnlessDestroyed(new Runnable() {
+					@Override
+					public void run() {
+						listener.onGroupAdded(gae.getGroup().getId());
+					}
+				});
+			}
+		} else if (e instanceof GroupRemovedEvent) {
+			final GroupRemovedEvent gre = (GroupRemovedEvent) e;
+			ClientId id = gre.getGroup().getClientId();
+			if (id.equals(groupManager.getClientId())) {
+				LOG.info("Private group removed");
+				listener.runOnUiThreadUnlessDestroyed(new Runnable() {
+					@Override
+					public void run() {
+						listener.onGroupRemoved(gre.getGroup().getId());
+					}
+				});
+			}
+		}
+	}
+
+	@Override
+	public void loadGroups(
+			final ResultExceptionHandler<Collection<GroupItem>, DbException> handler) {
+		runOnDbThread(new Runnable() {
+			@Override
+			public void run() {
+				LOG.info("Loading groups from database...");
+				try {
+					Collection<PrivateGroup> groups =
+							groupManager.getPrivateGroups();
+					List<GroupItem> items = new ArrayList<>(groups.size());
+					for (PrivateGroup g : groups) {
+						GroupCount c = groupManager.getGroupCount(g.getId());
+						boolean dissolved = groupManager.isDissolved(g.getId());
+						items.add(new GroupItem(g, c.getMsgCount(),
+								c.getLatestMsgTime(), c.getUnreadCount(),
+								dissolved));
+					}
+					handler.onResult(items);
+				} catch (DbException e) {
+					if (LOG.isLoggable(WARNING))
+						LOG.log(WARNING, e.toString(), e);
+					handler.onException(e);
+				}
+			}
+		});
+	}
+
+	@Override
+	public void removeGroup(final GroupId g,
+			final ResultExceptionHandler<Void, DbException> handler) {
+		runOnDbThread(new Runnable() {
+			@Override
+			public void run() {
+				LOG.info("Removing group from database...");
+				try {
+					groupManager.removePrivateGroup(g);
+				} 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/privategroup/list/GroupListFragment.java b/briar-android/src/org/briarproject/android/privategroup/list/GroupListFragment.java
new file mode 100644
index 0000000000000000000000000000000000000000..bf773c28c654d0be47ad835c51de88d40b1e1947
--- /dev/null
+++ b/briar-android/src/org/briarproject/android/privategroup/list/GroupListFragment.java
@@ -0,0 +1,171 @@
+package org.briarproject.android.privategroup.list;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.annotation.UiThread;
+import android.support.v7.widget.LinearLayoutManager;
+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.controller.handler.UiResultExceptionHandler;
+import org.briarproject.android.fragment.BaseFragment;
+import org.briarproject.android.invitation.AddContactActivity;
+import org.briarproject.android.privategroup.list.GroupListController.GroupListListener;
+import org.briarproject.android.privategroup.list.GroupViewHolder.OnGroupRemoveClickListener;
+import org.briarproject.android.view.BriarRecyclerView;
+import org.briarproject.api.db.DbException;
+import org.briarproject.api.privategroup.GroupMessageHeader;
+import org.briarproject.api.sync.GroupId;
+
+import java.util.Collection;
+
+import javax.inject.Inject;
+
+public class GroupListFragment extends BaseFragment implements
+		GroupListListener, OnGroupRemoveClickListener {
+
+	public final static String TAG = GroupListFragment.class.getName();
+
+	public static GroupListFragment newInstance() {
+		return new GroupListFragment();
+	}
+
+	@Inject
+	GroupListController controller;
+
+	private BriarRecyclerView list;
+	private GroupListAdapter adapter;
+
+	@Nullable
+	@Override
+	public View onCreateView(LayoutInflater inflater, ViewGroup container,
+			Bundle savedInstanceState) {
+
+		setHasOptionsMenu(true);
+
+		View v = inflater.inflate(R.layout.list, container, false);
+
+		adapter = new GroupListAdapter(getContext(), this);
+		list = (BriarRecyclerView) v.findViewById(R.id.list);
+		list.setEmptyText(R.string.groups_list_empty);
+		list.setLayoutManager(new LinearLayoutManager(getContext()));
+		list.setAdapter(adapter);
+
+		return v;
+	}
+
+	@Override
+	public void injectFragment(ActivityComponent component) {
+		component.inject(this);
+		controller.setGroupListListener(this);
+	}
+
+	@Override
+	public void onStart() {
+		super.onStart();
+		controller.onStart();
+		list.startPeriodicUpdate();
+		loadGroups();
+	}
+
+	@Override
+	public void onStop() {
+		super.onStop();
+		controller.onStop();
+		list.stopPeriodicUpdate();
+		adapter.clear();
+		list.showProgressBar();
+	}
+
+	@Override
+	public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+		inflater.inflate(R.menu.groups_list_actions, menu);
+		super.onCreateOptionsMenu(menu, inflater);
+	}
+
+	@Override
+	public boolean onOptionsItemSelected(final MenuItem item) {
+		switch (item.getItemId()) {
+			case R.id.action_add_group:
+				// TODO
+				return true;
+			default:
+				return super.onOptionsItemSelected(item);
+		}
+	}
+
+	@UiThread
+	@Override
+	public void onGroupRemoveClick(GroupItem item) {
+		controller.removeGroup(item.getId(),
+				new UiResultExceptionHandler<Void, DbException>(listener) {
+					@Override
+					public void onResultUi(Void result) {
+						// handled by GroupRemovedEvent and onGroupRemoved()
+					}
+
+					@Override
+					public void onExceptionUi(DbException exception) {
+						// TODO handle error
+						finish();
+					}
+				});
+	}
+
+	@UiThread
+	@Override
+	public void onGroupMessageAdded(GroupMessageHeader header) {
+		int position = adapter.findItemPosition(header.getGroupId());
+		GroupItem item = adapter.getItemAt(position);
+		if (item != null) {
+			item.addMessageHeader(header);
+			adapter.updateItemAt(position, item);
+		}
+	}
+
+	@UiThread
+	@Override
+	public void onGroupAdded(GroupId groupId) {
+		loadGroups();
+	}
+
+	@UiThread
+	@Override
+	public void onGroupRemoved(GroupId groupId) {
+		adapter.removeItem(groupId);
+	}
+
+	@Override
+	public String getUniqueTag() {
+		return TAG;
+	}
+
+	private void loadGroups() {
+		controller.loadGroups(
+				new UiResultExceptionHandler<Collection<GroupItem>, DbException>(
+						listener) {
+					@Override
+					public void onResultUi(Collection<GroupItem> result) {
+						if (result.isEmpty()) {
+							list.showData();
+						} else {
+							adapter.addAll(result);
+						}
+					}
+
+					@Override
+					public void onExceptionUi(DbException exception) {
+						// TODO handle this error
+						finish();
+					}
+				});
+	}
+
+}
diff --git a/briar-android/src/org/briarproject/android/privategroup/list/GroupViewHolder.java b/briar-android/src/org/briarproject/android/privategroup/list/GroupViewHolder.java
new file mode 100644
index 0000000000000000000000000000000000000000..12b956df4f688597947569e31fb25df1b5f0302a
--- /dev/null
+++ b/briar-android/src/org/briarproject/android/privategroup/list/GroupViewHolder.java
@@ -0,0 +1,135 @@
+package org.briarproject.android.privategroup.list;
+
+import android.content.Context;
+import android.support.annotation.Nullable;
+import android.support.v7.widget.RecyclerView;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.TextView;
+
+import org.briarproject.R;
+import org.briarproject.android.util.AndroidUtils;
+import org.briarproject.android.view.TextAvatarView;
+import org.jetbrains.annotations.NotNull;
+
+import static android.support.v4.content.ContextCompat.getColor;
+import static android.view.View.GONE;
+import static android.view.View.VISIBLE;
+
+class GroupViewHolder extends RecyclerView.ViewHolder {
+
+	private final static float ALPHA = 0.42f;
+
+	private final ViewGroup layout;
+	private final TextAvatarView avatar;
+	private final TextView name;
+	private final TextView creator;
+	private final TextView postCount;
+	private final TextView date;
+	private final TextView status;
+	private final Button remove;
+
+	GroupViewHolder(View v) {
+		super(v);
+
+		layout = (ViewGroup) v;
+		avatar = (TextAvatarView) v.findViewById(R.id.avatarView);
+		name = (TextView) v.findViewById(R.id.nameView);
+		creator = (TextView) v.findViewById(R.id.creatorView);
+		postCount = (TextView) v.findViewById(R.id.messageCountView);
+		date = (TextView) v.findViewById(R.id.dateView);
+		status = (TextView) v.findViewById(R.id.statusView);
+		remove = (Button) v.findViewById(R.id.removeButton);
+	}
+
+	void bindView(Context ctx, @Nullable final GroupItem group,
+			@NotNull final OnGroupRemoveClickListener listener) {
+		if (group == null) return;
+
+		// Avatar
+		avatar.setText(group.getName().substring(0, 1));
+		avatar.setBackgroundBytes(group.getId().getBytes());
+		avatar.setUnreadCount(group.getUnreadCount());
+
+		// Group Name
+		name.setText(group.getName());
+
+		// Creator
+		creator.setText(ctx.getString(R.string.groups_created_by,
+				group.getCreator().getName()));
+
+		if (!group.isDissolved()) {
+			// full visibility
+			avatar.setAlpha(1);
+			name.setAlpha(1);
+			creator.setAlpha(1);
+
+			// Date and Status
+			if (group.isEmpty()) {
+				postCount.setVisibility(GONE);
+				date.setVisibility(GONE);
+				avatar.setProblem(true);
+				status
+						.setText(ctx.getString(R.string.groups_group_is_empty));
+				status.setVisibility(VISIBLE);
+			} else {
+				// Message Count
+				int messageCount = group.getMessageCount();
+				postCount.setVisibility(VISIBLE);
+				postCount.setText(ctx.getResources()
+						.getQuantityString(R.plurals.messages, messageCount,
+								messageCount));
+				postCount.setTextColor(
+						getColor(ctx, R.color.briar_text_secondary));
+
+				long lastUpdate = group.getLastUpdate();
+				date.setText(AndroidUtils.formatDate(ctx, lastUpdate));
+				date.setVisibility(VISIBLE);
+				avatar.setProblem(false);
+				status.setVisibility(GONE);
+			}
+			remove.setVisibility(GONE);
+		} else {
+			// grey out
+			avatar.setAlpha(ALPHA);
+			name.setAlpha(ALPHA);
+			creator.setAlpha(ALPHA);
+
+			postCount.setVisibility(GONE);
+			date.setVisibility(GONE);
+			status
+					.setText(ctx.getString(R.string.groups_group_is_dissolved));
+			status.setVisibility(VISIBLE);
+			remove.setOnClickListener(new OnClickListener() {
+				@Override
+				public void onClick(View v) {
+					listener.onGroupRemoveClick(group);
+				}
+			});
+			remove.setVisibility(VISIBLE);
+		}
+
+		// Open Group on Click
+		layout.setOnClickListener(new OnClickListener() {
+			@Override
+			public void onClick(View v) {
+/*
+				Intent i = new Intent(ctx, GroupActivity.class);
+				GroupId id = item.getId();
+				i.putExtra(GROUP_ID, id.getBytes());
+				ActivityOptionsCompat options = ActivityOptionsCompat
+						.makeCustomAnimation(ctx, android.R.anim.fade_in,
+								android.R.anim.fade_out);
+				ActivityCompat.startActivity(ctx, i, options.toBundle());
+*/
+			}
+		});
+	}
+
+	interface OnGroupRemoveClickListener {
+		void onGroupRemoveClick(GroupItem item);
+	}
+
+}
diff --git a/briar-android/src/org/briarproject/android/sharing/ContactSelectorFragment.java b/briar-android/src/org/briarproject/android/sharing/ContactSelectorFragment.java
index 286e0616dc6192934de34a3bf678bf808d236362..8e270d6407ca27f62a3b4f2d411efa49023230a5 100644
--- a/briar-android/src/org/briarproject/android/sharing/ContactSelectorFragment.java
+++ b/briar-android/src/org/briarproject/android/sharing/ContactSelectorFragment.java
@@ -99,8 +99,7 @@ public class ContactSelectorFragment extends BaseFragment implements
 	public View onCreateView(LayoutInflater inflater, ViewGroup container,
 			Bundle savedInstanceState) {
 
-		View contentView = inflater.inflate(
-				R.layout.introduction_contact_chooser, container, false);
+		View contentView = inflater.inflate(R.layout.list, container, false);
 
 		if (Build.VERSION.SDK_INT >= 21) {
 			setExitTransition(new Fade());
@@ -108,7 +107,7 @@ public class ContactSelectorFragment extends BaseFragment implements
 
 		adapter = new ContactSelectorAdapter(getActivity(), this);
 
-		list = (BriarRecyclerView) contentView.findViewById(R.id.contactList);
+		list = (BriarRecyclerView) contentView.findViewById(R.id.list);
 		list.setLayoutManager(new LinearLayoutManager(getActivity()));
 		list.setAdapter(adapter);
 		list.setEmptyText(getString(R.string.no_contacts_selector));
diff --git a/briar-android/src/org/briarproject/android/sharing/InvitationsActivity.java b/briar-android/src/org/briarproject/android/sharing/InvitationsActivity.java
index 941dbb48f16d102557c16a22ed722211e9dce4de..a6b33a0d8c601d32f06ddb3b895df849a7e6a556 100644
--- a/briar-android/src/org/briarproject/android/sharing/InvitationsActivity.java
+++ b/briar-android/src/org/briarproject/android/sharing/InvitationsActivity.java
@@ -38,12 +38,12 @@ abstract class InvitationsActivity extends BriarActivity
 	public void onCreate(Bundle state) {
 		super.onCreate(state);
 
-		setContentView(R.layout.activity_invitations);
+		setContentView(R.layout.list);
 
 		adapter = getAdapter(this, this);
 
 
-		list = (BriarRecyclerView) findViewById(R.id.invitationsView);
+		list = (BriarRecyclerView) findViewById(R.id.list);
 		if (list != null) {
 			list.setLayoutManager(new LinearLayoutManager(this));
 			list.setAdapter(adapter);
diff --git a/briar-android/src/org/briarproject/android/view/TextAvatarView.java b/briar-android/src/org/briarproject/android/view/TextAvatarView.java
index 8b06ecb4c1cd49ffc4acd83b72f20ff08123ebb8..2f5a2cfb2c6d486e8a19b40306837bafffc12327 100644
--- a/briar-android/src/org/briarproject/android/view/TextAvatarView.java
+++ b/briar-android/src/org/briarproject/android/view/TextAvatarView.java
@@ -2,6 +2,7 @@ package org.briarproject.android.view;
 
 import android.content.Context;
 import android.graphics.Color;
+import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
 import android.support.annotation.Nullable;
 import android.support.annotation.UiThread;
@@ -78,7 +79,7 @@ public class TextAvatarView extends FrameLayout {
 		int b = getByte(bytes, 2) * 3 / 4 + 96;
 		int color = Color.rgb(r, g, b);
 
-		background.setFillColor(color);
+		background.setImageDrawable(new ColorDrawable(color));
 	}
 
 	private byte getByte(byte[] bytes, int index) {
diff --git a/briar-api/src/org/briarproject/api/clients/BaseGroup.java b/briar-api/src/org/briarproject/api/clients/BaseGroup.java
index 7a6d1754e805edffe0671958c7bb46716c51e64a..fe8a282f5b7fb50770be89e170dd3bf889d6171d 100644
--- a/briar-api/src/org/briarproject/api/clients/BaseGroup.java
+++ b/briar-api/src/org/briarproject/api/clients/BaseGroup.java
@@ -16,14 +16,17 @@ public abstract class BaseGroup {
 		this.salt = salt;
 	}
 
+	@NotNull
 	public GroupId getId() {
 		return group.getId();
 	}
 
+	@NotNull
 	public Group getGroup() {
 		return group;
 	}
 
+	@NotNull
 	public String getName() {
 		return name;
 	}
diff --git a/briar-api/src/org/briarproject/api/event/GroupMessageAddedEvent.java b/briar-api/src/org/briarproject/api/event/GroupMessageAddedEvent.java
new file mode 100644
index 0000000000000000000000000000000000000000..d0c05c105a315a1b2d3e9d1a0ef420f948de010c
--- /dev/null
+++ b/briar-api/src/org/briarproject/api/event/GroupMessageAddedEvent.java
@@ -0,0 +1,36 @@
+package org.briarproject.api.event;
+
+import org.briarproject.api.privategroup.GroupMessageHeader;
+import org.briarproject.api.sync.GroupId;
+
+/**
+ * An event that is broadcast when a private group message was added
+ * to the database.
+ */
+public class GroupMessageAddedEvent extends Event {
+
+	private final GroupId groupId;
+	private final GroupMessageHeader header;
+	private final boolean local;
+
+	public GroupMessageAddedEvent(GroupId groupId, GroupMessageHeader header,
+			boolean local) {
+
+		this.groupId = groupId;
+		this.header = header;
+		this.local = local;
+	}
+
+	public GroupId getGroupId() {
+		return groupId;
+	}
+
+	public GroupMessageHeader getHeader() {
+		return header;
+	}
+
+	public boolean isLocal() {
+		return local;
+	}
+
+}
diff --git a/briar-api/src/org/briarproject/api/privategroup/GroupMessageHeader.java b/briar-api/src/org/briarproject/api/privategroup/GroupMessageHeader.java
index 608e86ce0aae58fa1f78eaa230e30bca8e177b3c..21043297bb338e51de4b76a73800dd970207280a 100644
--- a/briar-api/src/org/briarproject/api/privategroup/GroupMessageHeader.java
+++ b/briar-api/src/org/briarproject/api/privategroup/GroupMessageHeader.java
@@ -2,13 +2,27 @@ package org.briarproject.api.privategroup;
 
 import org.briarproject.api.clients.PostHeader;
 import org.briarproject.api.identity.Author;
+import org.briarproject.api.identity.Author.Status;
+import org.briarproject.api.sync.GroupId;
 import org.briarproject.api.sync.MessageId;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
 
 public class GroupMessageHeader extends PostHeader {
 
-	public GroupMessageHeader(MessageId id, MessageId parentId, long timestamp,
-			Author author, Author.Status authorStatus, boolean read) {
+	private final GroupId groupId;
+
+	public GroupMessageHeader(@NotNull GroupId groupId, @NotNull MessageId id,
+			@Nullable MessageId parentId, long timestamp,
+			@NotNull Author author, @NotNull Status authorStatus,
+			boolean read) {
 		super(id, parentId, timestamp, author, authorStatus, read);
+		this.groupId = groupId;
+	}
+
+	@NotNull
+	public GroupId getGroupId() {
+		return groupId;
 	}
 
 }
diff --git a/briar-api/src/org/briarproject/api/privategroup/PrivateGroupManager.java b/briar-api/src/org/briarproject/api/privategroup/PrivateGroupManager.java
index ab02e86bc04a6e500b470505a5741754d6464544..a7fbd0cb12a5421e04d3c1c37eacebb09bf43c19 100644
--- a/briar-api/src/org/briarproject/api/privategroup/PrivateGroupManager.java
+++ b/briar-api/src/org/briarproject/api/privategroup/PrivateGroupManager.java
@@ -16,6 +16,9 @@ public interface PrivateGroupManager extends MessageTracker {
 	@NotNull
 	ClientId getClientId();
 
+	/** Removes a dissolved private group. */
+	void removePrivateGroup(GroupId g) throws DbException;
+
 	/** Stores (and sends) a local group message. */
 	void addLocalMessage(GroupMessage p) throws DbException;
 
@@ -33,6 +36,9 @@ public interface PrivateGroupManager extends MessageTracker {
 	@NotNull
 	Collection<PrivateGroup> getPrivateGroups() throws DbException;
 
+	/** Returns true if the private group has been dissolved. */
+	boolean isDissolved(GroupId g) throws DbException;
+
 	/** Returns the body of the group message with the given ID. */
 	@NotNull
 	String getMessageBody(MessageId m) throws DbException;
diff --git a/briar-core/src/org/briarproject/CoreEagerSingletons.java b/briar-core/src/org/briarproject/CoreEagerSingletons.java
index 4f1787c2b4317402540ec97ace9df11f83425a30..b4b7402dd9b7fb265ab9aa30c887f8f4089f3da4 100644
--- a/briar-core/src/org/briarproject/CoreEagerSingletons.java
+++ b/briar-core/src/org/briarproject/CoreEagerSingletons.java
@@ -1,5 +1,7 @@
 package org.briarproject;
 
+import org.briarproject.api.privategroup.PrivateGroup;
+import org.briarproject.api.privategroup.PrivateGroupManager;
 import org.briarproject.blogs.BlogsModule;
 import org.briarproject.contact.ContactModule;
 import org.briarproject.crypto.CryptoModule;
@@ -11,6 +13,7 @@ import org.briarproject.introduction.IntroductionModule;
 import org.briarproject.lifecycle.LifecycleModule;
 import org.briarproject.messaging.MessagingModule;
 import org.briarproject.plugins.PluginsModule;
+import org.briarproject.privategroup.PrivateGroupModule;
 import org.briarproject.properties.PropertiesModule;
 import org.briarproject.sharing.SharingModule;
 import org.briarproject.sync.SyncModule;
@@ -39,6 +42,8 @@ public interface CoreEagerSingletons {
 
 	void inject(PluginsModule.EagerSingletons init);
 
+	void inject(PrivateGroupModule.EagerSingletons init);
+
 	void inject(PropertiesModule.EagerSingletons init);
 
 	void inject(SharingModule.EagerSingletons init);
diff --git a/briar-core/src/org/briarproject/CoreModule.java b/briar-core/src/org/briarproject/CoreModule.java
index dbfaeb256a9c4d7f3670afbccf8181e61066212e..82754d66880e509cd1d3dbdafd78a4a6579b8a4f 100644
--- a/briar-core/src/org/briarproject/CoreModule.java
+++ b/briar-core/src/org/briarproject/CoreModule.java
@@ -17,6 +17,7 @@ import org.briarproject.keyagreement.KeyAgreementModule;
 import org.briarproject.lifecycle.LifecycleModule;
 import org.briarproject.messaging.MessagingModule;
 import org.briarproject.plugins.PluginsModule;
+import org.briarproject.privategroup.PrivateGroupModule;
 import org.briarproject.properties.PropertiesModule;
 import org.briarproject.reliability.ReliabilityModule;
 import org.briarproject.reporting.ReportingModule;
@@ -46,6 +47,7 @@ import dagger.Module;
 		LifecycleModule.class,
 		MessagingModule.class,
 		PluginsModule.class,
+		PrivateGroupModule.class,
 		PropertiesModule.class,
 		ReliabilityModule.class,
 		ReportingModule.class,
@@ -69,6 +71,7 @@ public class CoreModule {
 		c.inject(new LifecycleModule.EagerSingletons());
 		c.inject(new MessagingModule.EagerSingletons());
 		c.inject(new PluginsModule.EagerSingletons());
+		c.inject(new PrivateGroupModule.EagerSingletons());
 		c.inject(new PropertiesModule.EagerSingletons());
 		c.inject(new SharingModule.EagerSingletons());
 		c.inject(new SyncModule.EagerSingletons());
diff --git a/briar-core/src/org/briarproject/privategroup/PrivateGroupManagerImpl.java b/briar-core/src/org/briarproject/privategroup/PrivateGroupManagerImpl.java
index 93623476d9b273c78f93a924db973dc8a56b47e7..3c9f5814bc580c50a3bb1822855000993d613e82 100644
--- a/briar-core/src/org/briarproject/privategroup/PrivateGroupManagerImpl.java
+++ b/briar-core/src/org/briarproject/privategroup/PrivateGroupManagerImpl.java
@@ -25,12 +25,15 @@ import org.jetbrains.annotations.NotNull;
 
 import java.util.Collection;
 import java.util.Collections;
+import java.util.logging.Logger;
 
 import javax.inject.Inject;
 
 public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements
 		PrivateGroupManager {
 
+	private static final Logger LOG =
+			Logger.getLogger(PrivateGroupManagerImpl.class.getName());
 	static final ClientId CLIENT_ID = new ClientId(
 			StringUtils.fromHexString("5072697661746547726f75704d616e61"
 					+ "67657220627920546f727374656e2047"));
@@ -55,6 +58,11 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements
 		return CLIENT_ID;
 	}
 
+	@Override
+	public void removePrivateGroup(GroupId g) throws DbException {
+
+	}
+
 	@Override
 	public void addLocalMessage(GroupMessage m) throws DbException {
 		Transaction txn = db.startTransaction(false);
@@ -91,6 +99,11 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements
 		return Collections.emptyList();
 	}
 
+	@Override
+	public boolean isDissolved(GroupId g) throws DbException {
+		return false;
+	}
+
 	@NotNull
 	@Override
 	public String getMessageBody(MessageId m) throws DbException {