diff --git a/briar-android/res/drawable-hdpi/rating_bad.png b/briar-android/res/drawable-hdpi/rating_bad.png
new file mode 100644
index 0000000000000000000000000000000000000000..4d332cc92e47bbaaa094105944721a7400b6abe4
Binary files /dev/null and b/briar-android/res/drawable-hdpi/rating_bad.png differ
diff --git a/briar-android/res/drawable-hdpi/rating_good.png b/briar-android/res/drawable-hdpi/rating_good.png
new file mode 100644
index 0000000000000000000000000000000000000000..f612bab60e2553211c7c55a836ccac63fc44951d
Binary files /dev/null and b/briar-android/res/drawable-hdpi/rating_good.png differ
diff --git a/briar-android/res/drawable-mdpi/rating_bad.png b/briar-android/res/drawable-mdpi/rating_bad.png
new file mode 100644
index 0000000000000000000000000000000000000000..5c5982ac649369ba8fa4c379c462a0f550ff2903
Binary files /dev/null and b/briar-android/res/drawable-mdpi/rating_bad.png differ
diff --git a/briar-android/res/drawable-mdpi/rating_good.png b/briar-android/res/drawable-mdpi/rating_good.png
new file mode 100644
index 0000000000000000000000000000000000000000..16fce26e1c2e484a855638960cca2ddebb93f115
Binary files /dev/null and b/briar-android/res/drawable-mdpi/rating_good.png differ
diff --git a/briar-android/res/drawable-xhdpi/rating_bad.png b/briar-android/res/drawable-xhdpi/rating_bad.png
new file mode 100644
index 0000000000000000000000000000000000000000..a8ad06ad9926fb4948f790f4699b1dd740c47d4c
Binary files /dev/null and b/briar-android/res/drawable-xhdpi/rating_bad.png differ
diff --git a/briar-android/res/drawable-xhdpi/rating_good.png b/briar-android/res/drawable-xhdpi/rating_good.png
new file mode 100644
index 0000000000000000000000000000000000000000..225eaea5f258984ccc6d015598dc431e9af8978a
Binary files /dev/null and b/briar-android/res/drawable-xhdpi/rating_good.png differ
diff --git a/briar-android/src/net/sf/briar/android/groups/GroupActivity.java b/briar-android/src/net/sf/briar/android/groups/GroupActivity.java
index 8ed287306acf942eaf298ed4440fb4441f9806ee..c0ae2820ec1ebb690dc7a47a1e7cd114ae6749fe 100644
--- a/briar-android/src/net/sf/briar/android/groups/GroupActivity.java
+++ b/briar-android/src/net/sf/briar/android/groups/GroupActivity.java
@@ -4,11 +4,14 @@ import static android.view.Gravity.CENTER_HORIZONTAL;
 import static android.widget.LinearLayout.VERTICAL;
 import static java.util.logging.Level.INFO;
 import static java.util.logging.Level.WARNING;
+import static net.sf.briar.api.Rating.UNRATED;
 
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.concurrent.Executor;
 import java.util.logging.Logger;
 
@@ -19,17 +22,20 @@ import net.sf.briar.android.BriarService;
 import net.sf.briar.android.BriarService.BriarServiceConnection;
 import net.sf.briar.android.widgets.CommonLayoutParams;
 import net.sf.briar.android.widgets.HorizontalBorder;
+import net.sf.briar.api.Rating;
 import net.sf.briar.api.db.DatabaseComponent;
 import net.sf.briar.api.db.DatabaseExecutor;
 import net.sf.briar.api.db.DbException;
 import net.sf.briar.api.db.GroupMessageHeader;
+import net.sf.briar.api.db.NoSuchSubscriptionException;
 import net.sf.briar.api.db.event.DatabaseEvent;
 import net.sf.briar.api.db.event.DatabaseListener;
 import net.sf.briar.api.db.event.MessageAddedEvent;
 import net.sf.briar.api.db.event.MessageExpiredEvent;
-import net.sf.briar.api.db.event.SubscriptionAddedEvent;
+import net.sf.briar.api.db.event.RatingChangedEvent;
 import net.sf.briar.api.db.event.SubscriptionRemovedEvent;
 import net.sf.briar.api.messaging.Author;
+import net.sf.briar.api.messaging.AuthorId;
 import net.sf.briar.api.messaging.GroupId;
 import android.content.Intent;
 import android.os.Bundle;
@@ -121,8 +127,23 @@ OnClickListener, OnItemClickListener {
 							db.getMessageHeaders(groupId);
 					if(LOG.isLoggable(INFO))
 						LOG.info("Loaded " + headers.size() + " headers");
+					// Load the ratings for the authors
+					Map<Author, Rating> ratings = new HashMap<Author, Rating>();
+					for(GroupMessageHeader h : headers) {
+						Author a = h.getAuthor();
+						if(a != null && !ratings.containsKey(a))
+							ratings.put(a, db.getRating(a.getId()));
+					}
+					ratings = Collections.unmodifiableMap(ratings);
 					// Update the conversation
-					updateConversation(headers);
+					updateConversation(headers, ratings);
+				} catch(NoSuchSubscriptionException e) {
+					if(LOG.isLoggable(INFO)) LOG.info("Subscription removed");
+					runOnUiThread(new Runnable() {
+						public void run() {
+							finish();
+						}
+					});
 				} catch(DbException e) {
 					if(LOG.isLoggable(WARNING))
 						LOG.log(WARNING, e.toString(), e);
@@ -136,7 +157,8 @@ OnClickListener, OnItemClickListener {
 	}
 
 	private void updateConversation(
-			final Collection<GroupMessageHeader> headers) {
+			final Collection<GroupMessageHeader> headers,
+			final Map<Author, Rating> ratings) {
 		runOnUiThread(new Runnable() {
 			public void run() {
 				List<GroupMessageHeader> sort =
@@ -147,7 +169,9 @@ OnClickListener, OnItemClickListener {
 				for(GroupMessageHeader h : sort) {
 					if(firstUnread == -1 && !h.isRead())
 						firstUnread = adapter.getCount();
-					adapter.add(h);
+					Author a = h.getAuthor();
+					if(a == null) adapter.add(new GroupItem(h, UNRATED));
+					else adapter.add(new GroupItem(h, ratings.get(a)));
 				}
 				if(firstUnread == -1) list.setSelection(adapter.getCount() - 1);
 				else list.setSelection(firstUnread);
@@ -169,15 +193,36 @@ OnClickListener, OnItemClickListener {
 		} else if(e instanceof MessageExpiredEvent) {
 			if(LOG.isLoggable(INFO)) LOG.info("Message removed, reloading");
 			reloadMessageHeaders();
-		} else if(e instanceof SubscriptionAddedEvent) {
-			if(LOG.isLoggable(INFO)) LOG.info("Group added, reloading");
-			reloadMessageHeaders();
+		} else if(e instanceof RatingChangedEvent) {
+			RatingChangedEvent r = (RatingChangedEvent) e;
+			updateRating(r.getAuthorId(), r.getRating());
 		} else if(e instanceof SubscriptionRemovedEvent) {
-			if(LOG.isLoggable(INFO)) LOG.info("Group removed, reloading");
-			reloadMessageHeaders();
+			SubscriptionRemovedEvent s = (SubscriptionRemovedEvent) e;
+			if(s.getGroupId().equals(groupId)) {
+				if(LOG.isLoggable(INFO)) LOG.info("Subscription removed");
+				finish();
+			}
 		}
 	}
 
+	private void updateRating(final AuthorId a, final Rating r) {
+		runOnUiThread(new Runnable() {
+			public void run() {
+				boolean affected = false;
+				int count = adapter.getCount();
+				for(int i = 0; i < count; i++) {
+					GroupItem item = adapter.getItem(i);
+					Author author = item.getAuthor();
+					if(author != null && author.getId().equals(a)) {
+						item.setRating(r);
+						affected = true;
+					}
+				}
+				if(affected) list.invalidate();
+			}
+		});
+	}
+
 	public void onClick(View view) {
 		Intent i = new Intent(this, WriteGroupMessageActivity.class);
 		i.putExtra("net.sf.briar.GROUP_ID", groupId.getBytes());
@@ -191,7 +236,7 @@ OnClickListener, OnItemClickListener {
 	}
 
 	private void showMessage(int position) {
-		GroupMessageHeader item = adapter.getItem(position);
+		GroupItem item = adapter.getItem(position);
 		Intent i = new Intent(this, ReadGroupMessageActivity.class);
 		i.putExtra("net.sf.briar.GROUP_ID", groupId.getBytes());
 		i.putExtra("net.sf.briar.GROUP_NAME", groupName);
@@ -203,6 +248,7 @@ OnClickListener, OnItemClickListener {
 			i.putExtra("net.sf.briar.ANONYMOUS", false);
 			i.putExtra("net.sf.briar.AUTHOR_ID", author.getId().getBytes());
 			i.putExtra("net.sf.briar.AUTHOR_NAME", author.getName());
+			i.putExtra("net.sf.briar.RATING", item.getRating().toString());
 		}
 		i.putExtra("net.sf.briar.CONTENT_TYPE", item.getContentType());
 		i.putExtra("net.sf.briar.TIMESTAMP", item.getTimestamp());
diff --git a/briar-android/src/net/sf/briar/android/groups/GroupAdapter.java b/briar-android/src/net/sf/briar/android/groups/GroupAdapter.java
index 17915c8d45abe00087b8e98784b6a36b26c4e0a6..10f8b11722ab76c8301f87d7ca59f7dd2da70d9f 100644
--- a/briar-android/src/net/sf/briar/android/groups/GroupAdapter.java
+++ b/briar-android/src/net/sf/briar/android/groups/GroupAdapter.java
@@ -5,13 +5,15 @@ import static android.view.Gravity.CENTER_VERTICAL;
 import static android.widget.LinearLayout.HORIZONTAL;
 import static android.widget.LinearLayout.VERTICAL;
 import static java.text.DateFormat.SHORT;
+import static net.sf.briar.api.Rating.BAD;
+import static net.sf.briar.api.Rating.GOOD;
 
 import java.util.ArrayList;
 
 import net.sf.briar.R;
 import net.sf.briar.android.widgets.CommonLayoutParams;
 import net.sf.briar.android.widgets.HorizontalSpace;
-import net.sf.briar.api.db.GroupMessageHeader;
+import net.sf.briar.api.Rating;
 import net.sf.briar.api.messaging.Author;
 import android.content.Context;
 import android.content.res.Resources;
@@ -23,16 +25,16 @@ import android.widget.ImageView;
 import android.widget.LinearLayout;
 import android.widget.TextView;
 
-class GroupAdapter extends ArrayAdapter<GroupMessageHeader> {
+class GroupAdapter extends ArrayAdapter<GroupItem> {
 
 	GroupAdapter(Context ctx) {
 		super(ctx, android.R.layout.simple_expandable_list_item_1,
-				new ArrayList<GroupMessageHeader>());
+				new ArrayList<GroupItem>());
 	}
 
 	@Override
 	public View getView(int position, View convertView, ViewGroup parent) {
-		GroupMessageHeader item = getItem(position);
+		GroupItem item = getItem(position);
 		Context ctx = getContext();
 		// FIXME: Use a RelativeLayout
 		LinearLayout layout = new LinearLayout(ctx);
@@ -44,12 +46,28 @@ class GroupAdapter extends ArrayAdapter<GroupMessageHeader> {
 		innerLayout.setLayoutParams(CommonLayoutParams.WRAP_WRAP_1);
 		innerLayout.setOrientation(VERTICAL);
 
-		Author author = item.getAuthor();
+		LinearLayout innerInnerLayout = new LinearLayout(ctx);
+		innerInnerLayout.setOrientation(HORIZONTAL);
+		innerInnerLayout.setGravity(CENTER_VERTICAL);
+
+		Rating rating = item.getRating();
+		if(rating == GOOD) {
+			ImageView good = new ImageView(ctx);
+			good.setPadding(0, 10, 10, 10);
+			good.setImageResource(R.drawable.rating_good);
+			innerInnerLayout.addView(good);
+		} else if(rating == BAD) {
+			ImageView bad = new ImageView(ctx);
+			bad.setPadding(0, 10, 10, 10);
+			bad.setImageResource(R.drawable.rating_bad);
+			innerInnerLayout.addView(bad);
+		}
 
 		TextView name = new TextView(ctx);
 		name.setTextSize(18);
 		name.setMaxLines(1);
 		name.setPadding(10, 10, 10, 10);
+		Author author = item.getAuthor();
 		Resources res = ctx.getResources();
 		if(author == null) {
 			name.setTextColor(res.getColor(R.color.anonymous_author));
@@ -58,7 +76,8 @@ class GroupAdapter extends ArrayAdapter<GroupMessageHeader> {
 			name.setTextColor(res.getColor(R.color.pseudonymous_author));
 			name.setText(author.getName());
 		}
-		innerLayout.addView(name);
+		innerInnerLayout.addView(name);
+		innerLayout.addView(innerInnerLayout);
 
 		if(item.getContentType().equals("text/plain")) {
 			TextView subject = new TextView(ctx);
@@ -69,15 +88,12 @@ class GroupAdapter extends ArrayAdapter<GroupMessageHeader> {
 			subject.setText(item.getSubject());
 			innerLayout.addView(subject);
 		} else {
-			LinearLayout innerInnerLayout = new LinearLayout(ctx);
-			innerInnerLayout.setOrientation(HORIZONTAL);
 			ImageView attachment = new ImageView(ctx);
 			attachment.setPadding(10, 0, 10, 10);
 			attachment.setImageResource(R.drawable.content_attachment);
 			innerInnerLayout.addView(attachment);
-			innerInnerLayout.addView(new HorizontalSpace(ctx));
-			innerLayout.addView(innerInnerLayout);
 		}
+		innerInnerLayout.addView(new HorizontalSpace(ctx));
 		layout.addView(innerLayout);
 
 		TextView date = new TextView(ctx);
diff --git a/briar-android/src/net/sf/briar/android/groups/GroupItem.java b/briar-android/src/net/sf/briar/android/groups/GroupItem.java
new file mode 100644
index 0000000000000000000000000000000000000000..3e6cb182136fd264721795132e4b5178b242a142
--- /dev/null
+++ b/briar-android/src/net/sf/briar/android/groups/GroupItem.java
@@ -0,0 +1,50 @@
+package net.sf.briar.android.groups;
+
+import net.sf.briar.api.Rating;
+import net.sf.briar.api.db.GroupMessageHeader;
+import net.sf.briar.api.messaging.Author;
+import net.sf.briar.api.messaging.MessageId;
+
+// This class is not thread-safe
+class GroupItem {
+
+	private final GroupMessageHeader header;
+	private Rating rating;
+
+	GroupItem(GroupMessageHeader header, Rating rating) {
+		this.header = header;
+		this.rating = rating;
+	}
+
+	MessageId getId() {
+		return header.getId();
+	}
+
+	Author getAuthor() {
+		return header.getAuthor();
+	}
+
+	String getContentType() {
+		return header.getContentType();
+	}
+
+	String getSubject() {
+		return header.getSubject();
+	}
+
+	long getTimestamp() {
+		return header.getTimestamp();
+	}
+
+	boolean isRead() {
+		return header.isRead();
+	}
+
+	Rating getRating() {
+		return rating;
+	}
+
+	void setRating(Rating rating) {
+		this.rating = rating;
+	}
+}
diff --git a/briar-android/src/net/sf/briar/android/groups/GroupListActivity.java b/briar-android/src/net/sf/briar/android/groups/GroupListActivity.java
index 5c0c3beecf8e5b9c99e5ee571c86e6d9ea816035..ee38bfd8d8b997cb04d839d6007388daec71178c 100644
--- a/briar-android/src/net/sf/briar/android/groups/GroupListActivity.java
+++ b/briar-android/src/net/sf/briar/android/groups/GroupListActivity.java
@@ -4,6 +4,8 @@ import static android.view.Gravity.CENTER_HORIZONTAL;
 import static android.widget.LinearLayout.VERTICAL;
 import static java.util.logging.Level.INFO;
 import static java.util.logging.Level.WARNING;
+import static net.sf.briar.api.Rating.BAD;
+import static net.sf.briar.api.Rating.GOOD;
 
 import java.io.IOException;
 import java.security.GeneralSecurityException;
@@ -131,8 +133,10 @@ implements OnClickListener, DatabaseListener {
 					PrivateKey privateKey = keyPair.getPrivate();
 					Author author = authorFactory.createAuthor("Batman",
 							publicKey);
+					db.setRating(author.getId(), BAD);
 					Author author1 = authorFactory.createAuthor("Duckman",
 							publicKey);
+					db.setRating(author1.getId(), GOOD);
 					// Insert some fake groups and make them visible
 					Group group = groupFactory.createGroup("DisneyLeaks");
 					db.subscribe(group);
diff --git a/briar-android/src/net/sf/briar/android/groups/ReadGroupMessageActivity.java b/briar-android/src/net/sf/briar/android/groups/ReadGroupMessageActivity.java
index d5750019ba58a3137df5a79cc18ddbb119621a9a..61b4183e588d90492d6f2569e1b3eb33a065fbe2 100644
--- a/briar-android/src/net/sf/briar/android/groups/ReadGroupMessageActivity.java
+++ b/briar-android/src/net/sf/briar/android/groups/ReadGroupMessageActivity.java
@@ -2,11 +2,16 @@ package net.sf.briar.android.groups;
 
 import static android.view.Gravity.CENTER;
 import static android.view.Gravity.CENTER_VERTICAL;
+import static android.view.View.GONE;
+import static android.view.View.VISIBLE;
 import static android.widget.LinearLayout.HORIZONTAL;
 import static android.widget.LinearLayout.VERTICAL;
 import static java.text.DateFormat.SHORT;
 import static java.util.logging.Level.INFO;
 import static java.util.logging.Level.WARNING;
+import static net.sf.briar.api.Rating.BAD;
+import static net.sf.briar.api.Rating.GOOD;
+import static net.sf.briar.api.Rating.UNRATED;
 
 import java.io.UnsupportedEncodingException;
 import java.util.concurrent.Executor;
@@ -19,10 +24,12 @@ import net.sf.briar.android.BriarService.BriarServiceConnection;
 import net.sf.briar.android.widgets.CommonLayoutParams;
 import net.sf.briar.android.widgets.HorizontalBorder;
 import net.sf.briar.android.widgets.HorizontalSpace;
+import net.sf.briar.api.Rating;
 import net.sf.briar.api.android.BundleEncrypter;
 import net.sf.briar.api.db.DatabaseComponent;
 import net.sf.briar.api.db.DatabaseExecutor;
 import net.sf.briar.api.db.DbException;
+import net.sf.briar.api.db.NoSuchMessageException;
 import net.sf.briar.api.messaging.AuthorId;
 import net.sf.briar.api.messaging.GroupId;
 import net.sf.briar.api.messaging.MessageId;
@@ -33,6 +40,7 @@ import android.text.format.DateUtils;
 import android.view.View;
 import android.view.View.OnClickListener;
 import android.widget.ImageButton;
+import android.widget.ImageView;
 import android.widget.LinearLayout;
 import android.widget.ScrollView;
 import android.widget.TextView;
@@ -60,8 +68,11 @@ implements OnClickListener {
 	private MessageId messageId = null;
 	private AuthorId authorId = null;
 	private String authorName = null;
+	private Rating rating = UNRATED;
 	private boolean read;
-	private ImageButton readButton = null, prevButton = null, nextButton = null;
+	private ImageView thumb = null;
+	private ImageButton goodButton = null, badButton = null, readButton = null;
+	private ImageButton prevButton = null, nextButton = null;
 	private ImageButton replyButton = null;
 	private TextView content = null;
 
@@ -86,6 +97,8 @@ implements OnClickListener {
 			authorId = new AuthorId(id);
 			authorName = i.getStringExtra("net.sf.briar.AUTHOR_NAME");
 			if(authorName == null) throw new IllegalStateException();
+			String r = i.getStringExtra("net.sf.briar.RATING");
+			if(r != null) rating = Rating.valueOf(r);
 		}
 		String contentType = i.getStringExtra("net.sf.briar.CONTENT_TYPE");
 		if(contentType == null) throw new IllegalStateException();
@@ -117,6 +130,13 @@ implements OnClickListener {
 		header.setOrientation(HORIZONTAL);
 		header.setGravity(CENTER_VERTICAL);
 
+		thumb = new ImageView(this);
+		thumb.setPadding(0, 10, 10, 10);
+		if(rating == GOOD) thumb.setImageResource(R.drawable.rating_good);
+		else if(rating == BAD) thumb.setImageResource(R.drawable.rating_bad);
+		else thumb.setVisibility(GONE);
+		header.addView(thumb);
+
 		TextView author = new TextView(this);
 		// Give me all the unused width
 		author.setLayoutParams(CommonLayoutParams.WRAP_WRAP_1);
@@ -158,6 +178,23 @@ implements OnClickListener {
 		footer.setOrientation(HORIZONTAL);
 		footer.setGravity(CENTER);
 
+		goodButton = new ImageButton(this);
+		goodButton.setBackgroundResource(0);
+		goodButton.setImageResource(R.drawable.rating_good);
+		if(authorName == null) goodButton.setEnabled(false);
+		else goodButton.setOnClickListener(this);
+		footer.addView(goodButton);
+		footer.addView(new HorizontalSpace(this));
+
+		badButton = new ImageButton(this);
+		badButton.setBackgroundResource(0);
+		badButton.setImageResource(R.drawable.rating_bad);
+		badButton.setOnClickListener(this);
+		if(authorName == null) badButton.setEnabled(false);
+		else badButton.setOnClickListener(this);
+		footer.addView(badButton);
+		footer.addView(new HorizontalSpace(this));
+
 		readButton = new ImageButton(this);
 		readButton.setBackgroundResource(0);
 		if(read) readButton.setImageResource(R.drawable.content_unread);
@@ -241,6 +278,13 @@ implements OnClickListener {
 							content.setText(text);
 						}
 					});
+				} catch(NoSuchMessageException e) {
+					if(LOG.isLoggable(INFO)) LOG.info("Message removed");
+					runOnUiThread(new Runnable() {
+						public void run() {
+							finish();
+						}
+					});
 				} catch(DbException e) {
 					if(LOG.isLoggable(WARNING))
 						LOG.log(WARNING, e.toString(), e);
@@ -268,7 +312,13 @@ implements OnClickListener {
 	}
 
 	public void onClick(View view) {
-		if(view == readButton) {
+		if(view == goodButton) {
+			if(rating == BAD) setRatingInDatabase(UNRATED);
+			else if(rating == UNRATED) setRatingInDatabase(GOOD);
+		} else if(view == badButton) {
+			if(rating == GOOD) setRatingInDatabase(UNRATED);
+			else if(rating == UNRATED) setRatingInDatabase(BAD);
+		} else if(view == readButton) {
 			setReadInDatabase(!read);
 		} else if(view == prevButton) {
 			setResult(RESULT_PREV);
@@ -285,4 +335,42 @@ implements OnClickListener {
 			finish();
 		}
 	}
+
+	private void setRatingInDatabase(final Rating r) {
+		final DatabaseComponent db = this.db;
+		final AuthorId authorId = this.authorId;
+		dbExecutor.execute(new Runnable() {
+			public void run() {
+				try {
+					serviceConnection.waitForStartup();
+					db.setRating(authorId, r);
+					setRatingInUi(r);
+				} catch(DbException e) {
+					if(LOG.isLoggable(WARNING))
+						LOG.log(WARNING, e.toString(), e);
+				} catch(InterruptedException e) {
+					if(LOG.isLoggable(INFO))
+						LOG.info("Interrupted while waiting for service");
+					Thread.currentThread().interrupt();
+				}
+			}
+		});
+	}
+
+	private void setRatingInUi(final Rating r) {
+		runOnUiThread(new Runnable() {
+			public void run() {
+				ReadGroupMessageActivity.this.rating = r;
+				if(r == GOOD) {
+					thumb.setImageResource(R.drawable.rating_good);
+					thumb.setVisibility(VISIBLE);
+				} else if(r == BAD) {
+					thumb.setImageResource(R.drawable.rating_bad);
+					thumb.setVisibility(VISIBLE);
+				} else {
+					thumb.setVisibility(GONE);
+				}
+			}
+		});
+	}
 }
diff --git a/briar-android/src/net/sf/briar/android/messages/ConversationActivity.java b/briar-android/src/net/sf/briar/android/messages/ConversationActivity.java
index b382e920ee2553ed4477be64aab9c25065a2ef3f..f130f17374933e72a9b63f1a2372116ffde96d67 100644
--- a/briar-android/src/net/sf/briar/android/messages/ConversationActivity.java
+++ b/briar-android/src/net/sf/briar/android/messages/ConversationActivity.java
@@ -23,7 +23,9 @@ import net.sf.briar.api.ContactId;
 import net.sf.briar.api.db.DatabaseComponent;
 import net.sf.briar.api.db.DatabaseExecutor;
 import net.sf.briar.api.db.DbException;
+import net.sf.briar.api.db.NoSuchContactException;
 import net.sf.briar.api.db.PrivateMessageHeader;
+import net.sf.briar.api.db.event.ContactRemovedEvent;
 import net.sf.briar.api.db.event.DatabaseEvent;
 import net.sf.briar.api.db.event.DatabaseListener;
 import net.sf.briar.api.db.event.MessageAddedEvent;
@@ -120,6 +122,13 @@ implements DatabaseListener, OnClickListener, OnItemClickListener {
 						LOG.info("Loaded " + headers.size() + " headers");
 					// Update the conversation
 					updateConversation(headers);
+				} catch(NoSuchContactException e) {
+					if(LOG.isLoggable(INFO)) LOG.info("Contact removed");
+					runOnUiThread(new Runnable() {
+						public void run() {
+							finish();
+						}
+					});
 				} catch(DbException e) {
 					if(LOG.isLoggable(WARNING))
 						LOG.log(WARNING, e.toString(), e);
@@ -173,7 +182,13 @@ implements DatabaseListener, OnClickListener, OnItemClickListener {
 	}
 
 	public void eventOccurred(DatabaseEvent e) {
-		if(e instanceof MessageAddedEvent) {
+		if(e instanceof ContactRemovedEvent) {
+			ContactRemovedEvent c = (ContactRemovedEvent) e;
+			if(c.getContactId().equals(contactId)) {
+				if(LOG.isLoggable(INFO)) LOG.info("Contact removed");
+				finish();
+			}
+		} else if(e instanceof MessageAddedEvent) {
 			if(LOG.isLoggable(INFO)) LOG.info("Message added, reloading");
 			reloadMessageHeaders();
 		} else if(e instanceof MessageExpiredEvent) {
diff --git a/briar-android/src/net/sf/briar/android/messages/ReadPrivateMessageActivity.java b/briar-android/src/net/sf/briar/android/messages/ReadPrivateMessageActivity.java
index afe0944578c549bc4b61bba270f150c8c6db5a6b..222bb307f29cbb5d51340e76618d17bee1f79cc0 100644
--- a/briar-android/src/net/sf/briar/android/messages/ReadPrivateMessageActivity.java
+++ b/briar-android/src/net/sf/briar/android/messages/ReadPrivateMessageActivity.java
@@ -24,6 +24,7 @@ import net.sf.briar.api.android.BundleEncrypter;
 import net.sf.briar.api.db.DatabaseComponent;
 import net.sf.briar.api.db.DatabaseExecutor;
 import net.sf.briar.api.db.DbException;
+import net.sf.briar.api.db.NoSuchMessageException;
 import net.sf.briar.api.messaging.MessageId;
 import android.content.Intent;
 import android.os.Bundle;
@@ -226,6 +227,13 @@ implements OnClickListener {
 							content.setText(text);
 						}
 					});
+				} catch(NoSuchMessageException e) {
+					if(LOG.isLoggable(INFO)) LOG.info("Message removed");
+					runOnUiThread(new Runnable() {
+						public void run() {
+							finish();
+						}
+					});
 				} catch(DbException e) {
 					if(LOG.isLoggable(WARNING))
 						LOG.log(WARNING, e.toString(), e);
diff --git a/briar-api/src/net/sf/briar/api/crypto/CryptoExecutor.java b/briar-api/src/net/sf/briar/api/crypto/CryptoExecutor.java
index 346ed893873589647d31d04d1e78085016433883..8e3ba8dc9cbcb4a9542e823ac3fd01914c0e37bc 100644
--- a/briar-api/src/net/sf/briar/api/crypto/CryptoExecutor.java
+++ b/briar-api/src/net/sf/briar/api/crypto/CryptoExecutor.java
@@ -1,5 +1,6 @@
 package net.sf.briar.api.crypto;
 
+import static java.lang.annotation.ElementType.FIELD;
 import static java.lang.annotation.ElementType.PARAMETER;
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
@@ -10,6 +11,6 @@ import com.google.inject.BindingAnnotation;
 
 /** Annotation for injecting the executor for long-running crypto tasks. */
 @BindingAnnotation
-@Target({ PARAMETER })
+@Target({ FIELD, PARAMETER })
 @Retention(RUNTIME)
 public @interface CryptoExecutor {}
diff --git a/briar-api/src/net/sf/briar/api/db/event/RatingChangedEvent.java b/briar-api/src/net/sf/briar/api/db/event/RatingChangedEvent.java
index aec11c50f2de58b730c96822132b42e984f74a20..2c2bf6d7aa8a5da1bab7e3bf1f58b0d3d6275f55 100644
--- a/briar-api/src/net/sf/briar/api/db/event/RatingChangedEvent.java
+++ b/briar-api/src/net/sf/briar/api/db/event/RatingChangedEvent.java
@@ -13,7 +13,7 @@ public class RatingChangedEvent extends DatabaseEvent {
 		this.rating = rating;
 	}
 
-	public AuthorId getAuthor() {
+	public AuthorId getAuthorId() {
 		return author;
 	}
 
diff --git a/briar-core/src/net/sf/briar/messaging/duplex/DuplexConnection.java b/briar-core/src/net/sf/briar/messaging/duplex/DuplexConnection.java
index 9b56a720bff76a68937105d613f304699ed65431..2681ec67b5ed200457b0641d3ea0f2521203066f 100644
--- a/briar-core/src/net/sf/briar/messaging/duplex/DuplexConnection.java
+++ b/briar-core/src/net/sf/briar/messaging/duplex/DuplexConnection.java
@@ -86,7 +86,7 @@ abstract class DuplexConnection implements DatabaseListener {
 	protected final ContactId contactId;
 	protected final TransportId transportId;
 
-	private final Executor dbExecutor, verificationExecutor;
+	private final Executor dbExecutor, cryptoExecutor;
 	private final MessageVerifier messageVerifier;
 	private final long maxLatency;
 	private final AtomicBoolean canSendOffer, disposed;
@@ -97,7 +97,7 @@ abstract class DuplexConnection implements DatabaseListener {
 	private volatile PacketWriter writer = null;
 
 	DuplexConnection(@DatabaseExecutor Executor dbExecutor,
-			@CryptoExecutor Executor verificationExecutor,
+			@CryptoExecutor Executor cryptoExecutor,
 			MessageVerifier messageVerifier, DatabaseComponent db,
 			ConnectionRegistry connRegistry,
 			ConnectionReaderFactory connReaderFactory,
@@ -106,7 +106,7 @@ abstract class DuplexConnection implements DatabaseListener {
 			PacketWriterFactory packetWriterFactory, ConnectionContext ctx,
 			DuplexTransportConnection transport) {
 		this.dbExecutor = dbExecutor;
-		this.verificationExecutor = verificationExecutor;
+		this.cryptoExecutor = cryptoExecutor;
 		this.messageVerifier = messageVerifier;
 		this.db = db;
 		this.connRegistry = connRegistry;
@@ -171,7 +171,7 @@ abstract class DuplexConnection implements DatabaseListener {
 					dbExecutor.execute(new ReceiveAck(a));
 				} else if(reader.hasMessage()) {
 					UnverifiedMessage m = reader.readMessage();
-					verificationExecutor.execute(new VerifyMessage(m));
+					cryptoExecutor.execute(new VerifyMessage(m));
 				} else if(reader.hasOffer()) {
 					Offer o = reader.readOffer();
 					dbExecutor.execute(new ReceiveOffer(o));
diff --git a/briar-core/src/net/sf/briar/messaging/duplex/DuplexConnectionFactoryImpl.java b/briar-core/src/net/sf/briar/messaging/duplex/DuplexConnectionFactoryImpl.java
index 2c98cbf690327c548a1629b04932aa8df3a6f020..f141fce031e54e0f3658207fe0a6e3bcb8dd8873 100644
--- a/briar-core/src/net/sf/briar/messaging/duplex/DuplexConnectionFactoryImpl.java
+++ b/briar-core/src/net/sf/briar/messaging/duplex/DuplexConnectionFactoryImpl.java
@@ -28,7 +28,7 @@ class DuplexConnectionFactoryImpl implements DuplexConnectionFactory {
 	private static final Logger LOG =
 			Logger.getLogger(DuplexConnectionFactoryImpl.class.getName());
 
-	private final Executor dbExecutor, verificationExecutor;
+	private final Executor dbExecutor, cryptoExecutor;
 	private final MessageVerifier messageVerifier;
 	private final DatabaseComponent db;
 	private final KeyManager keyManager;
@@ -40,7 +40,7 @@ class DuplexConnectionFactoryImpl implements DuplexConnectionFactory {
 
 	@Inject
 	DuplexConnectionFactoryImpl(@DatabaseExecutor Executor dbExecutor,
-			@CryptoExecutor Executor verificationExecutor,
+			@CryptoExecutor Executor cryptoExecutor,
 			MessageVerifier messageVerifier, DatabaseComponent db,
 			KeyManager keyManager, ConnectionRegistry connRegistry,
 			ConnectionReaderFactory connReaderFactory,
@@ -48,7 +48,7 @@ class DuplexConnectionFactoryImpl implements DuplexConnectionFactory {
 			PacketReaderFactory packetReaderFactory,
 			PacketWriterFactory packetWriterFactory) {
 		this.dbExecutor = dbExecutor;
-		this.verificationExecutor = verificationExecutor;
+		this.cryptoExecutor = cryptoExecutor;
 		this.messageVerifier = messageVerifier;
 		this.db = db;
 		this.keyManager = keyManager;
@@ -62,7 +62,7 @@ class DuplexConnectionFactoryImpl implements DuplexConnectionFactory {
 	public void createIncomingConnection(ConnectionContext ctx,
 			DuplexTransportConnection transport) {
 		final DuplexConnection conn = new IncomingDuplexConnection(dbExecutor,
-				verificationExecutor, messageVerifier, db, connRegistry,
+				cryptoExecutor, messageVerifier, db, connRegistry,
 				connReaderFactory, connWriterFactory, packetReaderFactory,
 				packetWriterFactory, ctx, transport);
 		Runnable write = new Runnable() {
@@ -88,7 +88,7 @@ class DuplexConnectionFactoryImpl implements DuplexConnectionFactory {
 			return;
 		}
 		final DuplexConnection conn = new OutgoingDuplexConnection(dbExecutor,
-				verificationExecutor, messageVerifier, db, connRegistry,
+				cryptoExecutor, messageVerifier, db, connRegistry,
 				connReaderFactory, connWriterFactory, packetReaderFactory,
 				packetWriterFactory, ctx, transport);
 		Runnable write = new Runnable() {
diff --git a/briar-core/src/net/sf/briar/messaging/duplex/IncomingDuplexConnection.java b/briar-core/src/net/sf/briar/messaging/duplex/IncomingDuplexConnection.java
index 08e955af9b4f4e9111ab1a4d3dbc10aef70043c4..61fe58c97a7ae61d036bc4ddbe853afaeda39cf3 100644
--- a/briar-core/src/net/sf/briar/messaging/duplex/IncomingDuplexConnection.java
+++ b/briar-core/src/net/sf/briar/messaging/duplex/IncomingDuplexConnection.java
@@ -20,7 +20,7 @@ import net.sf.briar.api.transport.ConnectionWriterFactory;
 class IncomingDuplexConnection extends DuplexConnection {
 
 	IncomingDuplexConnection(@DatabaseExecutor Executor dbExecutor,
-			@CryptoExecutor Executor verificationExecutor,
+			@CryptoExecutor Executor cryptoExecutor,
 			MessageVerifier messageVerifier, DatabaseComponent db,
 			ConnectionRegistry connRegistry,
 			ConnectionReaderFactory connReaderFactory,
@@ -28,9 +28,9 @@ class IncomingDuplexConnection extends DuplexConnection {
 			PacketReaderFactory packetReaderFactory,
 			PacketWriterFactory packetWriterFactory,
 			ConnectionContext ctx, DuplexTransportConnection transport) {
-		super(dbExecutor, verificationExecutor, messageVerifier, db,
-				connRegistry, connReaderFactory, connWriterFactory,
-				packetReaderFactory, packetWriterFactory, ctx, transport);
+		super(dbExecutor, cryptoExecutor, messageVerifier, db, connRegistry,
+				connReaderFactory, connWriterFactory, packetReaderFactory,
+				packetWriterFactory, ctx, transport);
 	}
 
 	@Override
diff --git a/briar-core/src/net/sf/briar/messaging/duplex/OutgoingDuplexConnection.java b/briar-core/src/net/sf/briar/messaging/duplex/OutgoingDuplexConnection.java
index 2a852a45c14ce4b78a6daf361c0e8b7684d9988a..e59adb983f933102ef8bafd437d005b01a5ce424 100644
--- a/briar-core/src/net/sf/briar/messaging/duplex/OutgoingDuplexConnection.java
+++ b/briar-core/src/net/sf/briar/messaging/duplex/OutgoingDuplexConnection.java
@@ -20,7 +20,7 @@ import net.sf.briar.api.transport.ConnectionWriterFactory;
 class OutgoingDuplexConnection extends DuplexConnection {
 
 	OutgoingDuplexConnection(@DatabaseExecutor Executor dbExecutor,
-			@CryptoExecutor Executor verificationExecutor,
+			@CryptoExecutor Executor cryptoExecutor,
 			MessageVerifier messageVerifier, DatabaseComponent db,
 			ConnectionRegistry connRegistry,
 			ConnectionReaderFactory connReaderFactory,
@@ -28,9 +28,9 @@ class OutgoingDuplexConnection extends DuplexConnection {
 			PacketReaderFactory packetReaderFactory,
 			PacketWriterFactory packetWriterFactory, ConnectionContext ctx,
 			DuplexTransportConnection transport) {
-		super(dbExecutor, verificationExecutor, messageVerifier, db,
-				connRegistry, connReaderFactory, connWriterFactory,
-				packetReaderFactory, packetWriterFactory, ctx, transport);
+		super(dbExecutor, cryptoExecutor, messageVerifier, db, connRegistry,
+				connReaderFactory, connWriterFactory, packetReaderFactory,
+				packetWriterFactory, ctx, transport);
 	}
 
 	@Override
diff --git a/briar-core/src/net/sf/briar/messaging/simplex/IncomingSimplexConnection.java b/briar-core/src/net/sf/briar/messaging/simplex/IncomingSimplexConnection.java
index bac3930cabc0cfca536f688a8b7f40e23e38c766..cfb18c5039fba8c6b1d44b1c25f77cacf4cc9525 100644
--- a/briar-core/src/net/sf/briar/messaging/simplex/IncomingSimplexConnection.java
+++ b/briar-core/src/net/sf/briar/messaging/simplex/IncomingSimplexConnection.java
@@ -39,7 +39,7 @@ class IncomingSimplexConnection {
 	private static final Logger LOG =
 			Logger.getLogger(IncomingSimplexConnection.class.getName());
 
-	private final Executor dbExecutor, verificationExecutor;
+	private final Executor dbExecutor, cryptoExecutor;
 	private final MessageVerifier messageVerifier;
 	private final DatabaseComponent db;
 	private final ConnectionRegistry connRegistry;
@@ -51,14 +51,14 @@ class IncomingSimplexConnection {
 	private final TransportId transportId;
 
 	IncomingSimplexConnection(@DatabaseExecutor Executor dbExecutor,
-			@CryptoExecutor Executor verificationExecutor,
+			@CryptoExecutor Executor cryptoExecutor,
 			MessageVerifier messageVerifier, DatabaseComponent db,
 			ConnectionRegistry connRegistry,
 			ConnectionReaderFactory connReaderFactory,
 			PacketReaderFactory packetReaderFactory, ConnectionContext ctx,
 			SimplexTransportReader transport) {
 		this.dbExecutor = dbExecutor;
-		this.verificationExecutor = verificationExecutor;
+		this.cryptoExecutor = cryptoExecutor;
 		this.messageVerifier = messageVerifier;
 		this.db = db;
 		this.connRegistry = connRegistry;
@@ -84,7 +84,7 @@ class IncomingSimplexConnection {
 					dbExecutor.execute(new ReceiveAck(a));
 				} else if(reader.hasMessage()) {
 					UnverifiedMessage m = reader.readMessage();
-					verificationExecutor.execute(new VerifyMessage(m));
+					cryptoExecutor.execute(new VerifyMessage(m));
 				} else if(reader.hasRetentionAck()) {
 					RetentionAck a = reader.readRetentionAck();
 					dbExecutor.execute(new ReceiveRetentionAck(a));
diff --git a/briar-core/src/net/sf/briar/messaging/simplex/SimplexConnectionFactoryImpl.java b/briar-core/src/net/sf/briar/messaging/simplex/SimplexConnectionFactoryImpl.java
index 5764c512b3a47610988bcf12fc4d5d80d872e633..b7f5ecdd3b11b688624532fecd65f0ccfe3b78d0 100644
--- a/briar-core/src/net/sf/briar/messaging/simplex/SimplexConnectionFactoryImpl.java
+++ b/briar-core/src/net/sf/briar/messaging/simplex/SimplexConnectionFactoryImpl.java
@@ -29,7 +29,7 @@ class SimplexConnectionFactoryImpl implements SimplexConnectionFactory {
 	private static final Logger LOG =
 			Logger.getLogger(SimplexConnectionFactoryImpl.class.getName());
 
-	private final Executor dbExecutor, verificationExecutor;
+	private final Executor dbExecutor, cryptoExecutor;
 	private final MessageVerifier messageVerifier;
 	private final DatabaseComponent db;
 	private final KeyManager keyManager;
@@ -41,7 +41,7 @@ class SimplexConnectionFactoryImpl implements SimplexConnectionFactory {
 
 	@Inject
 	SimplexConnectionFactoryImpl(@DatabaseExecutor Executor dbExecutor,
-			@CryptoExecutor Executor verificationExecutor,
+			@CryptoExecutor Executor cryptoExecutor,
 			MessageVerifier messageVerifier, DatabaseComponent db,
 			KeyManager keyManager, ConnectionRegistry connRegistry,
 			ConnectionReaderFactory connReaderFactory,
@@ -49,7 +49,7 @@ class SimplexConnectionFactoryImpl implements SimplexConnectionFactory {
 			PacketReaderFactory packetReaderFactory,
 			PacketWriterFactory packetWriterFactory) {
 		this.dbExecutor = dbExecutor;
-		this.verificationExecutor = verificationExecutor;
+		this.cryptoExecutor = cryptoExecutor;
 		this.messageVerifier = messageVerifier;
 		this.db = db;
 		this.keyManager = keyManager;
@@ -60,10 +60,11 @@ class SimplexConnectionFactoryImpl implements SimplexConnectionFactory {
 		this.packetWriterFactory = packetWriterFactory;
 	}
 
-	public void createIncomingConnection(ConnectionContext ctx, SimplexTransportReader r) {
+	public void createIncomingConnection(ConnectionContext ctx,
+			SimplexTransportReader r) {
 		final IncomingSimplexConnection conn = new IncomingSimplexConnection(
-				dbExecutor, verificationExecutor, messageVerifier, db,
-				connRegistry, connReaderFactory, packetReaderFactory, ctx, r);
+				dbExecutor, cryptoExecutor, messageVerifier, db, connRegistry,
+				connReaderFactory, packetReaderFactory, ctx, r);
 		Runnable read = new Runnable() {
 			public void run() {
 				conn.read();