diff --git a/briar-android/res/values/strings.xml b/briar-android/res/values/strings.xml
index f71e6797919b507ac98c77f4092a0bfd072c5b75..229cf49244e741d472602327b661a039e10625ae 100644
--- a/briar-android/res/values/strings.xml
+++ b/briar-android/res/values/strings.xml
@@ -76,10 +76,8 @@
     <string name="add_button">Add</string>
     <string name="cancel_button">Cancel</string>
     <string name="post_sent_toast">Post sent</string>
-    <string name="private_message_notification_title">New private message</string>
-    <string name="private_message_notification_text">Touch to show.</string>
-    <string name="group_post_notification_title">New forum post</string>
-    <string name="group_post_notification_text">Touch to show.</string>
+    <string name="private_message_notification_text">New private message.</string>
+    <string name="group_post_notification_text">New forum post.</string>
     <string name="settings_title">Settings</string>
     <string name="activate_bluetooth_option">Activate Bluetooth while signed in</string>
     <string name="activate_bluetooth_explanation">Briar uses Bluetooth to communicate with nearby contacts</string>
diff --git a/briar-android/src/org/briarproject/android/AndroidModule.java b/briar-android/src/org/briarproject/android/AndroidModule.java
index 78c7a3a5798ef711154dbc75c3960c183773e2c1..3f871d2c9f77f1d0ea45e38ee65ffaacc49e96f1 100644
--- a/briar-android/src/org/briarproject/android/AndroidModule.java
+++ b/briar-android/src/org/briarproject/android/AndroidModule.java
@@ -14,6 +14,7 @@ import java.util.concurrent.ThreadPoolExecutor;
 import javax.inject.Singleton;
 
 import org.briarproject.api.android.AndroidExecutor;
+import org.briarproject.api.android.AndroidNotificationManager;
 import org.briarproject.api.android.DatabaseUiExecutor;
 import org.briarproject.api.android.ReferenceManager;
 import org.briarproject.api.db.DatabaseConfig;
@@ -57,9 +58,12 @@ public class AndroidModule extends AbstractModule {
 	}
 
 	protected void configure() {
-		bind(AndroidExecutor.class).to(AndroidExecutorImpl.class);
-		bind(ReferenceManager.class).to(
-				ReferenceManagerImpl.class).in(Singleton.class);
+		bind(AndroidExecutor.class).to(AndroidExecutorImpl.class).in(
+				Singleton.class);
+		bind(AndroidNotificationManager.class).to(
+				AndroidNotificationManagerImpl.class).in(Singleton.class);
+		bind(ReferenceManager.class).to(ReferenceManagerImpl.class).in(
+				Singleton.class);
 		bind(UiCallback.class).toInstance(uiCallback);
 	}
 
diff --git a/briar-android/src/org/briarproject/android/AndroidNotificationManagerImpl.java b/briar-android/src/org/briarproject/android/AndroidNotificationManagerImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..51dcea4d1e6cd9a9981833a797fb187726b333d7
--- /dev/null
+++ b/briar-android/src/org/briarproject/android/AndroidNotificationManagerImpl.java
@@ -0,0 +1,170 @@
+package org.briarproject.android;
+
+import static android.app.Notification.DEFAULT_ALL;
+import static android.content.Context.NOTIFICATION_SERVICE;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_SINGLE_TOP;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.inject.Inject;
+
+import org.briarproject.R;
+import org.briarproject.android.contact.ContactListActivity;
+import org.briarproject.android.contact.ConversationActivity;
+import org.briarproject.android.groups.GroupActivity;
+import org.briarproject.android.groups.GroupListActivity;
+import org.briarproject.api.ContactId;
+import org.briarproject.api.android.AndroidNotificationManager;
+import org.briarproject.api.messaging.GroupId;
+
+import android.app.Application;
+import android.app.NotificationManager;
+import android.content.Context;
+import android.content.Intent;
+import android.support.v4.app.NotificationCompat;
+import android.support.v4.app.TaskStackBuilder;
+
+class AndroidNotificationManagerImpl implements AndroidNotificationManager {
+
+	private static final int PRIVATE_MESSAGE_NOTIFICATION_ID = 3;
+	private static final int GROUP_POST_NOTIFICATION_ID = 4;
+
+	private final Context appContext;
+	private final Map<ContactId, Integer> contactCounts =
+			new HashMap<ContactId, Integer>(); // Locking: this
+	private final Map<GroupId, Integer> groupCounts =
+			new HashMap<GroupId, Integer>(); // Locking: this
+
+	private int privateTotal = 0, groupTotal = 0; // Locking: this
+
+	@Inject
+	public AndroidNotificationManagerImpl(Application app) {
+		this.appContext = app.getApplicationContext();
+	}
+
+	public synchronized void showPrivateMessageNotification(ContactId c) {
+		Integer count = contactCounts.get(c);
+		if(count == null) contactCounts.put(c, 1);
+		else contactCounts.put(c, count + 1);
+		privateTotal++;
+		updatePrivateMessageNotification();
+	}
+
+	public synchronized void clearPrivateMessageNotification(ContactId c) {
+		Integer count = contactCounts.remove(c);
+		if(count == null) return; // Already cleared
+		privateTotal -= count;
+		updatePrivateMessageNotification();
+	}
+
+	// Locking: this
+	private void updatePrivateMessageNotification() {
+		if(privateTotal == 0) {
+			clearPrivateMessageNotification();
+		} else {
+			NotificationCompat.Builder b =
+					new NotificationCompat.Builder(appContext);
+			b.setSmallIcon(R.drawable.message_notification_icon);
+			b.setContentTitle(appContext.getText(R.string.app_name));
+			b.setContentText(appContext.getText(
+					R.string.private_message_notification_text));
+			b.setDefaults(DEFAULT_ALL);
+			b.setOnlyAlertOnce(true);
+			if(contactCounts.size() == 1) {
+				Intent i = new Intent(appContext, ConversationActivity.class);
+				ContactId c = contactCounts.keySet().iterator().next();
+				i.putExtra("briar.CONTACT_ID", c.getInt());
+				i.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_SINGLE_TOP);
+				TaskStackBuilder tsb = TaskStackBuilder.create(appContext);
+				tsb.addParentStack(ConversationActivity.class);
+				tsb.addNextIntent(i);
+				b.setContentIntent(tsb.getPendingIntent(0, 0));
+			} else {
+				Intent i = new Intent(appContext, ContactListActivity.class);
+				i.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_SINGLE_TOP);
+				TaskStackBuilder tsb = TaskStackBuilder.create(appContext);
+				tsb.addParentStack(ContactListActivity.class);
+				tsb.addNextIntent(i);
+				b.setContentIntent(tsb.getPendingIntent(0, 0));
+			}
+			Object o = appContext.getSystemService(NOTIFICATION_SERVICE);
+			NotificationManager nm = (NotificationManager) o;
+			nm.notify(PRIVATE_MESSAGE_NOTIFICATION_ID, b.build());
+		}
+	}
+
+	// Locking: this
+	private void clearPrivateMessageNotification() {
+		Object o = appContext.getSystemService(NOTIFICATION_SERVICE);
+		NotificationManager nm = (NotificationManager) o;
+		nm.cancel(PRIVATE_MESSAGE_NOTIFICATION_ID);
+	}
+
+	public synchronized void showGroupPostNotification(GroupId g) {
+		Integer count = groupCounts.get(g);
+		if(count == null) groupCounts.put(g, 1);
+		else groupCounts.put(g, count + 1);
+		groupTotal++;
+		updateGroupPostNotification();
+	}
+
+	public synchronized void clearGroupPostNotification(GroupId g) {
+		Integer count = groupCounts.remove(g);
+		if(count == null) return; // Already cleared
+		groupTotal -= count;
+		updateGroupPostNotification();
+	}
+
+	// Locking: this
+	private void updateGroupPostNotification() {
+		if(groupTotal == 0) {
+			clearGroupPostNotification();
+		} else {
+			NotificationCompat.Builder b =
+					new NotificationCompat.Builder(appContext);
+			b.setSmallIcon(R.drawable.message_notification_icon);
+			b.setContentTitle(appContext.getText(R.string.app_name));
+			b.setContentText(appContext.getText(
+					R.string.group_post_notification_text));
+			b.setDefaults(DEFAULT_ALL);
+			b.setOnlyAlertOnce(true);
+			if(groupCounts.size() == 1) {
+				Intent i = new Intent(appContext, GroupActivity.class);
+				GroupId g = groupCounts.keySet().iterator().next();
+				i.putExtra("briar.GROUP_ID", g.getBytes());
+				i.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_SINGLE_TOP);
+				TaskStackBuilder tsb = TaskStackBuilder.create(appContext);
+				tsb.addParentStack(GroupActivity.class);
+				tsb.addNextIntent(i);
+				b.setContentIntent(tsb.getPendingIntent(0, 0));
+			} else {
+				Intent i = new Intent(appContext, GroupListActivity.class);
+				i.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_SINGLE_TOP);
+				TaskStackBuilder tsb = TaskStackBuilder.create(appContext);
+				tsb.addParentStack(GroupListActivity.class);
+				tsb.addNextIntent(i);
+				b.setContentIntent(tsb.getPendingIntent(0, 0));
+			}
+			Object o = appContext.getSystemService(NOTIFICATION_SERVICE);
+			NotificationManager nm = (NotificationManager) o;
+			nm.notify(GROUP_POST_NOTIFICATION_ID, b.build());
+		}
+	}
+
+	// Locking: this
+	private void clearGroupPostNotification() {
+		Object o = appContext.getSystemService(NOTIFICATION_SERVICE);
+		NotificationManager nm = (NotificationManager) o;
+		nm.cancel(GROUP_POST_NOTIFICATION_ID);
+	}
+
+	public synchronized void clearNotifications() {
+		contactCounts.clear();
+		groupCounts.clear();
+		privateTotal = groupTotal = 0;
+		clearPrivateMessageNotification();
+		clearGroupPostNotification();
+	}
+}
diff --git a/briar-android/src/org/briarproject/android/BriarService.java b/briar-android/src/org/briarproject/android/BriarService.java
index 4e556b2db732dfdba3886051ef8dc1afe31c35e7..bbf576174c128f8c3daf08df74f22895244d3469 100644
--- a/briar-android/src/org/briarproject/android/BriarService.java
+++ b/briar-android/src/org/briarproject/android/BriarService.java
@@ -1,6 +1,5 @@
 package org.briarproject.android;
 
-import static android.app.Notification.DEFAULT_ALL;
 import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
 import static android.content.Intent.FLAG_ACTIVITY_SINGLE_TOP;
@@ -15,10 +14,9 @@ import java.util.logging.Logger;
 import javax.inject.Inject;
 
 import org.briarproject.R;
-import org.briarproject.android.contact.ContactListActivity;
-import org.briarproject.android.groups.GroupListActivity;
 import org.briarproject.api.ContactId;
 import org.briarproject.api.android.AndroidExecutor;
+import org.briarproject.api.android.AndroidNotificationManager;
 import org.briarproject.api.android.DatabaseUiExecutor;
 import org.briarproject.api.db.DatabaseComponent;
 import org.briarproject.api.db.DatabaseConfig;
@@ -38,14 +36,11 @@ import android.content.ServiceConnection;
 import android.os.Binder;
 import android.os.IBinder;
 import android.support.v4.app.NotificationCompat;
-import android.support.v4.app.TaskStackBuilder;
 
 public class BriarService extends RoboService implements EventListener {
 
 	private static final int ONGOING_NOTIFICATION_ID = 1;
 	private static final int FAILURE_NOTIFICATION_ID = 2;
-	private static final int PRIVATE_MESSAGE_NOTIFICATION_ID = 3;
-	private static final int GROUP_POST_NOTIFICATION_ID = 4;
 
 	private static final Logger LOG =
 			Logger.getLogger(BriarService.class.getName());
@@ -54,6 +49,7 @@ public class BriarService extends RoboService implements EventListener {
 	private final Binder binder = new BriarBinder();
 
 	@Inject private DatabaseConfig databaseConfig;
+	@Inject private AndroidNotificationManager notificationManager;
 
 	// Fields that are accessed from background threads must be volatile
 	@Inject private volatile LifecycleManager lifecycleManager;
@@ -135,11 +131,8 @@ public class BriarService extends RoboService implements EventListener {
 	public void onDestroy() {
 		super.onDestroy();
 		if(LOG.isLoggable(INFO)) LOG.info("Destroyed");
-		Object o = getSystemService(NOTIFICATION_SERVICE);
-		NotificationManager nm = (NotificationManager) o;
-		nm.cancel(PRIVATE_MESSAGE_NOTIFICATION_ID);
-		nm.cancel(GROUP_POST_NOTIFICATION_ID);
 		stopForeground(true);
+		notificationManager.clearNotifications();
 		// Stop the services in a background thread
 		new Thread() {
 			@Override
@@ -168,8 +161,8 @@ public class BriarService extends RoboService implements EventListener {
 				try {
 					lifecycleManager.waitForDatabase();
 					if(g.equals(db.getInboxGroupId(c)))
-						showPrivateMessageNotification();
-					else showGroupPostNotification();
+						notificationManager.showPrivateMessageNotification(c);
+					else notificationManager.showGroupPostNotification(g);
 				} catch(DbException e) {
 					if(LOG.isLoggable(WARNING))
 						LOG.log(WARNING, e.toString(), e);
@@ -182,42 +175,6 @@ public class BriarService extends RoboService implements EventListener {
 		});
 	}
 
-	private void showPrivateMessageNotification() {
-		NotificationCompat.Builder b = new NotificationCompat.Builder(this);
-		b.setSmallIcon(R.drawable.message_notification_icon);
-		b.setContentTitle(getText(R.string.private_message_notification_title));
-		b.setContentText(getText(R.string.private_message_notification_text));
-		b.setAutoCancel(true);
-		b.setDefaults(DEFAULT_ALL);
-		Intent i = new Intent(this, ContactListActivity.class);
-		i.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_SINGLE_TOP);
-		TaskStackBuilder tsb = TaskStackBuilder.create(this);
-		tsb.addParentStack(ContactListActivity.class);
-		tsb.addNextIntent(i);
-		b.setContentIntent(tsb.getPendingIntent(0, 0));
-		Object o = getSystemService(NOTIFICATION_SERVICE);
-		NotificationManager nm = (NotificationManager) o;
-		nm.notify(PRIVATE_MESSAGE_NOTIFICATION_ID, b.build());
-	}
-
-	private void showGroupPostNotification() {
-		NotificationCompat.Builder b = new NotificationCompat.Builder(this);
-		b.setSmallIcon(R.drawable.message_notification_icon);
-		b.setContentTitle(getText(R.string.group_post_notification_title));
-		b.setContentText(getText(R.string.group_post_notification_text));
-		b.setAutoCancel(true);
-		b.setDefaults(DEFAULT_ALL);
-		Intent i = new Intent(this, GroupListActivity.class);
-		i.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_SINGLE_TOP);
-		TaskStackBuilder tsb = TaskStackBuilder.create(this);
-		tsb.addParentStack(GroupListActivity.class);
-		tsb.addNextIntent(i);
-		b.setContentIntent(tsb.getPendingIntent(0, 0));
-		Object o = getSystemService(NOTIFICATION_SERVICE);
-		NotificationManager nm = (NotificationManager) o;
-		nm.notify(GROUP_POST_NOTIFICATION_ID, b.build());
-	}
-
 	/** Waits for the database to be opened before returning. */
 	public void waitForDatabase() throws InterruptedException {
 		lifecycleManager.waitForDatabase();
diff --git a/briar-android/src/org/briarproject/android/contact/ConversationActivity.java b/briar-android/src/org/briarproject/android/contact/ConversationActivity.java
index a3540fe1491d36312083c2d62c00fda5e4e086e5..e815591c006c54635c026fea37332e750d80cf00 100644
--- a/briar-android/src/org/briarproject/android/contact/ConversationActivity.java
+++ b/briar-android/src/org/briarproject/android/contact/ConversationActivity.java
@@ -37,7 +37,9 @@ import org.briarproject.android.util.HorizontalBorder;
 import org.briarproject.android.util.LayoutUtils;
 import org.briarproject.android.util.ListLoadingProgressBar;
 import org.briarproject.api.AuthorId;
+import org.briarproject.api.Contact;
 import org.briarproject.api.ContactId;
+import org.briarproject.api.android.AndroidNotificationManager;
 import org.briarproject.api.crypto.CryptoExecutor;
 import org.briarproject.api.db.DatabaseComponent;
 import org.briarproject.api.db.DbException;
@@ -81,9 +83,9 @@ implements EventListener, OnClickListener, OnItemClickListener {
 	private static final Logger LOG =
 			Logger.getLogger(ConversationActivity.class.getName());
 
+	@Inject private AndroidNotificationManager notificationManager;
 	@Inject @CryptoExecutor private Executor cryptoExecutor;
 	private Map<MessageId, byte[]> bodyCache = new HashMap<MessageId, byte[]>();
-	private String contactName = null;
 	private TextView empty = null;
 	private ConversationAdapter adapter = null;
 	private ListView list = null;
@@ -95,6 +97,7 @@ implements EventListener, OnClickListener, OnItemClickListener {
 	@Inject private volatile DatabaseComponent db;
 	@Inject private volatile MessageFactory messageFactory;
 	private volatile ContactId contactId = null;
+	private volatile String contactName = null;
 	private volatile GroupId groupId = null;
 	private volatile Group group = null;
 	private volatile AuthorId localAuthorId = null;
@@ -107,15 +110,6 @@ implements EventListener, OnClickListener, OnItemClickListener {
 		int id = i.getIntExtra("briar.CONTACT_ID", -1);
 		if(id == -1) throw new IllegalStateException();
 		contactId = new ContactId(id);
-		contactName = i.getStringExtra("briar.CONTACT_NAME");
-		if(contactName == null) throw new IllegalStateException();
-		setTitle(contactName);
-		byte[] b = i.getByteArrayExtra("briar.GROUP_ID");
-		if(b == null) throw new IllegalStateException();
-		groupId = new GroupId(b);
-		b = i.getByteArrayExtra("briar.LOCAL_AUTHOR_ID");
-		if(b == null) throw new IllegalStateException();
-		localAuthorId = new AuthorId(b);
 
 		Intent data = new Intent();
 		data.putExtra("briar.CONTACT_ID", id);
@@ -194,25 +188,59 @@ implements EventListener, OnClickListener, OnItemClickListener {
 	public void onResume() {
 		super.onResume();
 		db.addListener(this);
-		loadHeadersAndGroup();
+		loadContactAndGroup();
+		loadHeaders();
 	}
 
-	private void loadHeadersAndGroup() {
+	private void loadContactAndGroup() {
+		runOnDbThread(new Runnable() {
+			public void run() {
+				try {
+					long now = System.currentTimeMillis();
+					Contact contact = db.getContact(contactId);
+					contactName = contact.getAuthor().getName();
+					localAuthorId = contact.getLocalAuthorId();
+					groupId = db.getInboxGroupId(contactId);
+					group = db.getGroup(groupId);
+					long duration = System.currentTimeMillis() - now;
+					if(LOG.isLoggable(INFO)) {
+						LOG.info("Loading contact and group took "
+								+ duration + " ms");
+					}
+					displayContactName();
+				} catch(NoSuchContactException e) {
+					finishOnUiThread();
+				} catch(NoSuchSubscriptionException e) {
+					finishOnUiThread();
+				} catch(DbException e) {
+					if(LOG.isLoggable(WARNING))
+						LOG.log(WARNING, e.toString(), e);
+				}
+			}
+		});
+	}
+
+	private void displayContactName() {
+		runOnUiThread(new Runnable() {
+			public void run() {
+				setTitle(contactName);
+			}
+		});
+	}
+
+	private void loadHeaders() {
 		runOnDbThread(new Runnable() {
 			public void run() {
 				try {
 					long now = System.currentTimeMillis();
 					Collection<MessageHeader> headers =
 							db.getInboxMessageHeaders(contactId);
-					group = db.getGroup(groupId);
 					long duration = System.currentTimeMillis() - now;
 					if(LOG.isLoggable(INFO))
-						LOG.info("Load took " + duration + " ms");
+						LOG.info("Loading headers took " + duration + " ms");
 					displayHeaders(headers);
 				} catch(NoSuchContactException e) {
 					finishOnUiThread();
-				} catch(NoSuchSubscriptionException e) {
-					finishOnUiThread();
 				} catch(DbException e) {
 					if(LOG.isLoggable(WARNING))
 						LOG.log(WARNING, e.toString(), e);
@@ -225,6 +253,7 @@ implements EventListener, OnClickListener, OnItemClickListener {
 		runOnUiThread(new Runnable() {
 			public void run() {
 				loading.setVisibility(GONE);
+				setTitle(contactName);
 				sendButton.setEnabled(true);
 				adapter.clear();
 				if(headers.isEmpty()) {
@@ -306,6 +335,7 @@ implements EventListener, OnClickListener, OnItemClickListener {
 	}
 
 	private void markMessagesRead() {
+		notificationManager.clearPrivateMessageNotification(contactId);
 		List<MessageId> unread = new ArrayList<MessageId>();
 		int count = adapter.getCount();
 		for(int i = 0; i < count; i++) {
@@ -346,11 +376,11 @@ implements EventListener, OnClickListener, OnItemClickListener {
 			GroupId g = ((MessageAddedEvent) e).getGroup().getId();
 			if(g.equals(groupId)) {
 				if(LOG.isLoggable(INFO)) LOG.info("Message added, reloading");
-				loadHeadersAndGroup();
+				loadHeaders();
 			}
 		} else if(e instanceof MessageExpiredEvent) {
 			if(LOG.isLoggable(INFO)) LOG.info("Message expired, reloading");
-			loadHeadersAndGroup();
+			loadHeaders();
 		}
 	}
 
diff --git a/briar-android/src/org/briarproject/android/groups/GroupActivity.java b/briar-android/src/org/briarproject/android/groups/GroupActivity.java
index c38c7985e999158c4ca657d9af054c43e35505cd..ed343db93e6821e7242fe41a60563d982e0507eb 100644
--- a/briar-android/src/org/briarproject/android/groups/GroupActivity.java
+++ b/briar-android/src/org/briarproject/android/groups/GroupActivity.java
@@ -27,6 +27,7 @@ import org.briarproject.android.BriarActivity;
 import org.briarproject.android.util.HorizontalBorder;
 import org.briarproject.android.util.ListLoadingProgressBar;
 import org.briarproject.api.Author;
+import org.briarproject.api.android.AndroidNotificationManager;
 import org.briarproject.api.db.DatabaseComponent;
 import org.briarproject.api.db.DbException;
 import org.briarproject.api.db.MessageHeader;
@@ -37,6 +38,7 @@ import org.briarproject.api.event.EventListener;
 import org.briarproject.api.event.MessageAddedEvent;
 import org.briarproject.api.event.MessageExpiredEvent;
 import org.briarproject.api.event.SubscriptionRemovedEvent;
+import org.briarproject.api.messaging.Group;
 import org.briarproject.api.messaging.GroupId;
 import org.briarproject.api.messaging.MessageId;
 
@@ -59,8 +61,8 @@ OnClickListener, OnItemClickListener {
 	private static final Logger LOG =
 			Logger.getLogger(GroupActivity.class.getName());
 
+	@Inject private AndroidNotificationManager notificationManager;
 	private Map<MessageId, byte[]> bodyCache = new HashMap<MessageId, byte[]>();
-	private String groupName = null;
 	private TextView empty = null;
 	private GroupAdapter adapter = null;
 	private ListView list = null;
@@ -69,6 +71,7 @@ OnClickListener, OnItemClickListener {
 	// Fields that are accessed from background threads must be volatile
 	@Inject private volatile DatabaseComponent db;
 	private volatile GroupId groupId = null;
+	private volatile String groupName = null;
 
 	@Override
 	public void onCreate(Bundle state) {
@@ -78,9 +81,6 @@ OnClickListener, OnItemClickListener {
 		byte[] b = i.getByteArrayExtra("briar.GROUP_ID");
 		if(b == null) throw new IllegalStateException();
 		groupId = new GroupId(b);
-		groupName = i.getStringExtra("briar.GROUP_NAME");
-		if(groupName == null) throw new IllegalStateException();
-		setTitle(groupName);
 
 		LinearLayout layout = new LinearLayout(this);
 		layout.setLayoutParams(MATCH_MATCH);
@@ -128,9 +128,39 @@ OnClickListener, OnItemClickListener {
 	public void onResume() {
 		super.onResume();
 		db.addListener(this);
+		loadGroup();
 		loadHeaders();
 	}
 
+	private void loadGroup() {
+		runOnDbThread(new Runnable() {
+			public void run() {
+				try {
+					long now = System.currentTimeMillis();
+					Group g = db.getGroup(groupId);
+					groupName = g.getName();
+					long duration = System.currentTimeMillis() - now;
+					if(LOG.isLoggable(INFO))
+						LOG.info("Loading group " + duration + " ms");
+					displayGroupName();
+				} catch(NoSuchSubscriptionException e) {
+					finishOnUiThread();
+				} catch(DbException e) {
+					if(LOG.isLoggable(WARNING))
+						LOG.log(WARNING, e.toString(), e);
+				}
+			}
+		});
+	}
+
+	private void displayGroupName() {
+		runOnUiThread(new Runnable() {
+			public void run() {
+				setTitle(groupName);
+			}
+		});
+	}
+
 	private void loadHeaders() {
 		runOnDbThread(new Runnable() {
 			public void run() {
@@ -236,6 +266,7 @@ OnClickListener, OnItemClickListener {
 	}
 
 	private void markMessagesRead() {
+		notificationManager.clearGroupPostNotification(groupId);
 		List<MessageId> unread = new ArrayList<MessageId>();
 		int count = adapter.getCount();
 		for(int i = 0; i < count; i++) {
diff --git a/briar-api/src/org/briarproject/api/android/AndroidNotificationManager.java b/briar-api/src/org/briarproject/api/android/AndroidNotificationManager.java
new file mode 100644
index 0000000000000000000000000000000000000000..c0be8a754f920352d32d50d7184002a2a9f7b6f1
--- /dev/null
+++ b/briar-api/src/org/briarproject/api/android/AndroidNotificationManager.java
@@ -0,0 +1,21 @@
+package org.briarproject.api.android;
+
+import org.briarproject.api.ContactId;
+import org.briarproject.api.messaging.GroupId;
+
+/**
+ * Manages notifications for private messages and group posts. All methods must
+ * be called from the Android UI thread.
+ */
+public interface AndroidNotificationManager {
+
+	public void showPrivateMessageNotification(ContactId c);
+
+	public void clearPrivateMessageNotification(ContactId c);
+
+	public void showGroupPostNotification(GroupId g);
+
+	public void clearGroupPostNotification(GroupId g);
+
+	public void clearNotifications();
+}