diff --git a/briar-android/res/values/strings.xml b/briar-android/res/values/strings.xml
index 550e381e74593eb97a2e02aa84abebbf5c52da62..b43e6620e1194e50c1a2470b51adc24fb7b96d8e 100644
--- a/briar-android/res/values/strings.xml
+++ b/briar-android/res/values/strings.xml
@@ -13,15 +13,15 @@
 	<string name="contact_connected">Connected</string>
 	<string name="format_contact_last_connected">Last connected &lt;br /&gt; %1$s</string>
 	<string name="add_contact_title">Add a Contact</string>
-	<string name="choose_identity">Choose an identity to use with this contact:</string>
-	<string name="wifi_not_available">Wi-Fi is not available on this device</string>
+	<string name="your_identity">Your identity: </string>
+	<string name="wifi_not_available">Wi-Fi is NOT AVAILABLE</string>
 	<string name="wifi_disabled">Wi-Fi is OFF</string>
 	<string name="wifi_disconnected">Wi-Fi is DISCONNECTED</string>
-	<string name="format_wifi_connected">Wi-Fi is CONNECTED to %1$s</string>
-	<string name="bluetooth_not_available">Bluetooth is not available on this device</string>
+	<string name="format_wifi_connected">Wi-Fi is connected to %1$s</string>
+	<string name="bluetooth_not_available">Bluetooth is NOT AVAILABLE</string>
 	<string name="bluetooth_disabled">Bluetooth is OFF</string>
 	<string name="bluetooth_not_discoverable">Bluetooth is NOT DISCOVERABLE</string>
-	<string name="bluetooth_enabled">Bluetooth is DISCOVERABLE</string>
+	<string name="bluetooth_enabled">Bluetooth is discoverable</string>
 	<string name="continue_button">Continue</string>
 	<string name="your_invitation_code">Your invitation code is</string>
 	<string name="enter_invitation_code">Please enter your contact\'s invitation code:</string>
@@ -43,7 +43,8 @@
 	<string name="format_from">From: %1$s</string>
 	<string name="format_to">To: %1$s</string>
 	<string name="compose_message_title">New Message</string>
-	<string name="to">To:</string>
+	<string name="from">From: </string>
+	<string name="to">To: </string>
 	<string name="anonymous">(Anonymous)</string>
 	<string name="groups_title">Groups</string>
 	<string name="compose_group_title">New Post</string>
diff --git a/briar-android/src/net/sf/briar/android/AuthorNameComparator.java b/briar-android/src/net/sf/briar/android/AuthorNameComparator.java
new file mode 100644
index 0000000000000000000000000000000000000000..acde1829d838efa2a0b7cdfb7658e483755618f5
--- /dev/null
+++ b/briar-android/src/net/sf/briar/android/AuthorNameComparator.java
@@ -0,0 +1,16 @@
+package net.sf.briar.android;
+
+import java.util.Comparator;
+
+import net.sf.briar.api.Author;
+
+public class AuthorNameComparator implements Comparator<Author> {
+
+	public static final AuthorNameComparator INSTANCE =
+			new AuthorNameComparator();
+
+	public int compare(Author a1, Author a2) {
+		return String.CASE_INSENSITIVE_ORDER.compare(a1.getName(),
+				a2.getName());
+	}
+}
diff --git a/briar-android/src/net/sf/briar/android/LocalAuthorNameSpinnerAdapter.java b/briar-android/src/net/sf/briar/android/LocalAuthorNameSpinnerAdapter.java
new file mode 100644
index 0000000000000000000000000000000000000000..ffeb3feb9cc30ccbc23de9c5fb10a24828de2408
--- /dev/null
+++ b/briar-android/src/net/sf/briar/android/LocalAuthorNameSpinnerAdapter.java
@@ -0,0 +1,36 @@
+package net.sf.briar.android;
+
+import java.util.ArrayList;
+
+import net.sf.briar.api.LocalAuthor;
+import android.content.Context;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.SpinnerAdapter;
+import android.widget.TextView;
+
+public class LocalAuthorNameSpinnerAdapter extends ArrayAdapter<LocalAuthor>
+implements SpinnerAdapter {
+
+	public LocalAuthorNameSpinnerAdapter(Context context) {
+		super(context, android.R.layout.simple_spinner_item,
+				new ArrayList<LocalAuthor>());
+	}
+
+	@Override
+	public View getView(int position, View convertView, ViewGroup parent) {
+		TextView name = new TextView(getContext());
+		name.setTextSize(18);
+		name.setMaxLines(1);
+		name.setPadding(10, 10, 10, 10);
+		name.setText(getItem(position).getName());
+		return name;
+	}
+
+	@Override
+	public View getDropDownView(int position, View convertView,
+			ViewGroup parent) {
+		return getView(position, convertView, parent);
+	}
+}
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 294f18da96e86ff7a9f136a7b87e5e5afb3b1a84..32bbb5a9cf68a72f0153d0318fb58a5239bb03a4 100644
--- a/briar-android/src/net/sf/briar/android/groups/GroupActivity.java
+++ b/briar-android/src/net/sf/briar/android/groups/GroupActivity.java
@@ -6,7 +6,6 @@ import static java.util.logging.Level.INFO;
 import static java.util.logging.Level.WARNING;
 
 import java.util.Collection;
-import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Executor;
 import java.util.logging.Logger;
 
@@ -122,10 +121,8 @@ OnClickListener, OnItemClickListener {
 					long duration = System.currentTimeMillis() - now;
 					if(LOG.isLoggable(INFO))
 						LOG.info("Load took " + duration + " ms");
-					// Wait for the headers to be displayed in the UI
-					CountDownLatch latch = new CountDownLatch(1);
-					displayHeaders(latch, headers);
-					latch.await();
+					// Display the headers in the UI
+					displayHeaders(headers);
 				} catch(NoSuchSubscriptionException e) {
 					if(LOG.isLoggable(INFO)) LOG.info("Subscription removed");
 					finishOnUiThread();
@@ -134,25 +131,20 @@ OnClickListener, OnItemClickListener {
 						LOG.log(WARNING, e.toString(), e);
 				} catch(InterruptedException e) {
 					if(LOG.isLoggable(INFO))
-						LOG.info("Interrupted while loading headers");
+						LOG.info("Interrupted while waiting for service");
 					Thread.currentThread().interrupt();
 				}
 			}
 		});
 	}
 
-	private void displayHeaders(final CountDownLatch latch,
-			final Collection<GroupMessageHeader> headers) {
+	private void displayHeaders(final Collection<GroupMessageHeader> headers) {
 		runOnUiThread(new Runnable() {
 			public void run() {
-				try {
-					adapter.clear();
-					for(GroupMessageHeader h : headers) adapter.add(h);
-					adapter.sort(AscendingHeaderComparator.INSTANCE);
-					selectFirstUnread();
-				} finally {
-					latch.countDown();
-				}
+				adapter.clear();
+				for(GroupMessageHeader h : headers) adapter.add(h);
+				adapter.sort(AscendingHeaderComparator.INSTANCE);
+				selectFirstUnread();
 			}
 		});
 	}
@@ -203,7 +195,7 @@ OnClickListener, OnItemClickListener {
 			}
 		} else if(e instanceof MessageExpiredEvent) {
 			if(LOG.isLoggable(INFO)) LOG.info("Message expired, reloading");
-			loadHeaders(); // FIXME: Don't reload everything
+			loadHeaders();
 		} else if(e instanceof RatingChangedEvent) {
 			if(LOG.isLoggable(INFO)) LOG.info("Rating changed, reloading");
 			loadHeaders();
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 f173274fa882f27e3e2296acc42cbcfe97f6420d..baa57973b4c1189f0a46ea121ede3bbc3941dee8 100644
--- a/briar-android/src/net/sf/briar/android/groups/GroupListActivity.java
+++ b/briar-android/src/net/sf/briar/android/groups/GroupListActivity.java
@@ -11,7 +11,6 @@ import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Comparator;
 import java.util.List;
-import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Executor;
 import java.util.logging.Logger;
 
@@ -130,8 +129,6 @@ implements OnClickListener, DatabaseListener {
 					// Wait for the service to be bound and started
 					serviceConnection.waitForStartup();
 					// Load the subscribed groups from the DB
-					Collection<CountDownLatch> latches =
-							new ArrayList<CountDownLatch>();
 					long now = System.currentTimeMillis();
 					for(Group g : db.getSubscriptions()) {
 						// Filter out restricted/unrestricted groups
@@ -141,9 +138,7 @@ implements OnClickListener, DatabaseListener {
 							Collection<GroupMessageHeader> headers =
 									db.getMessageHeaders(g.getId());
 							// Display the headers in the UI
-							CountDownLatch latch = new CountDownLatch(1);
-							displayHeaders(latch, g, headers);
-							latches.add(latch);
+							displayHeaders(g, headers);
 						} catch(NoSuchSubscriptionException e) {
 							if(LOG.isLoggable(INFO))
 								LOG.info("Subscription removed");
@@ -152,39 +147,33 @@ implements OnClickListener, DatabaseListener {
 					long duration = System.currentTimeMillis() - now;
 					if(LOG.isLoggable(INFO))
 						LOG.info("Full load took " + duration + " ms");
-					// Wait for the headers to be displayed in the UI
-					for(CountDownLatch latch : latches) latch.await();
 				} catch(DbException e) {
 					if(LOG.isLoggable(WARNING))
 						LOG.log(WARNING, e.toString(), e);
 				} catch(InterruptedException e) {
 					if(LOG.isLoggable(INFO))
-						LOG.info("Interrupted while loading headers");
+						LOG.info("Interrupted while waiting for service");
 					Thread.currentThread().interrupt();
 				}
 			}
 		});
 	}
 
-	private void displayHeaders(final CountDownLatch latch, final Group g,
+	private void displayHeaders(final Group g,
 			final Collection<GroupMessageHeader> headers) {
 		runOnUiThread(new Runnable() {
 			public void run() {
-				try {
-					// Remove the old item, if any
-					GroupListItem item = findGroup(g.getId());
-					if(item != null) adapter.remove(item);
-					// Add a new item if there are any headers to display
-					if(!headers.isEmpty()) {
-						List<GroupMessageHeader> headerList =
-								new ArrayList<GroupMessageHeader>(headers);
-						adapter.add(new GroupListItem(g, headerList));
-						adapter.sort(GroupComparator.INSTANCE);
-					}
-					selectFirstUnread();
-				} finally {
-					latch.countDown();
+				// Remove the old item, if any
+				GroupListItem item = findGroup(g.getId());
+				if(item != null) adapter.remove(item);
+				// Add a new item if there are any headers to display
+				if(!headers.isEmpty()) {
+					List<GroupMessageHeader> headerList =
+							new ArrayList<GroupMessageHeader>(headers);
+					adapter.add(new GroupListItem(g, headerList));
+					adapter.sort(GroupComparator.INSTANCE);
 				}
+				selectFirstUnread();
 			} 
 		});
 	}
@@ -241,7 +230,7 @@ implements OnClickListener, DatabaseListener {
 			}
 		} else if(e instanceof MessageExpiredEvent) {
 			if(LOG.isLoggable(INFO)) LOG.info("Message expired, reloading");
-			loadHeaders(); // FIXME: Don't reload everything
+			loadHeaders();
 		} else if(e instanceof SubscriptionRemovedEvent) {
 			// Reload the group, expecting NoSuchSubscriptionException
 			Group g = ((SubscriptionRemovedEvent) e).getGroup();
@@ -263,9 +252,7 @@ implements OnClickListener, DatabaseListener {
 					long duration = System.currentTimeMillis() - now;
 					if(LOG.isLoggable(INFO))
 						LOG.info("Partial load took " + duration + " ms");
-					CountDownLatch latch = new CountDownLatch(1);
-					displayHeaders(latch, g, headers);
-					latch.await();
+					displayHeaders(g, headers);
 				} catch(NoSuchSubscriptionException e) {
 					if(LOG.isLoggable(INFO)) LOG.info("Subscription removed");
 					removeGroup(g.getId());
@@ -274,7 +261,7 @@ implements OnClickListener, DatabaseListener {
 						LOG.log(WARNING, e.toString(), e);
 				} catch(InterruptedException e) {
 					if(LOG.isLoggable(INFO))
-						LOG.info("Interrupted while loading headers");
+						LOG.info("Interrupted while waiting for service");
 					Thread.currentThread().interrupt();
 				}
 			}
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 b8b33637ff9f4768df2bff11c97782d4f7bd56f8..48b925723c4078766e186bf102c64d7b7486e8dd 100644
--- a/briar-android/src/net/sf/briar/android/groups/ReadGroupMessageActivity.java
+++ b/briar-android/src/net/sf/briar/android/groups/ReadGroupMessageActivity.java
@@ -26,8 +26,8 @@ import net.sf.briar.android.widgets.HorizontalBorder;
 import net.sf.briar.android.widgets.HorizontalSpace;
 import net.sf.briar.api.AuthorId;
 import net.sf.briar.api.android.BundleEncrypter;
+import net.sf.briar.api.android.DatabaseUiExecutor;
 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.GroupId;
@@ -72,7 +72,7 @@ implements OnClickListener {
 
 	// Fields that are accessed from DB threads must be volatile
 	@Inject private volatile DatabaseComponent db;
-	@Inject @DatabaseExecutor private volatile Executor dbExecutor;
+	@Inject @DatabaseUiExecutor private volatile Executor dbUiExecutor;
 	private volatile MessageId messageId = null;
 	private volatile AuthorId authorId = null;
 
@@ -234,7 +234,7 @@ implements OnClickListener {
 	}
 
 	private void setReadInDatabase(final boolean read) {
-		dbExecutor.execute(new Runnable() {
+		dbUiExecutor.execute(new Runnable() {
 			public void run() {
 				try {
 					serviceConnection.waitForStartup();
@@ -263,7 +263,7 @@ implements OnClickListener {
 	}
 
 	private void loadMessageBody() {
-		dbExecutor.execute(new Runnable() {
+		dbUiExecutor.execute(new Runnable() {
 			public void run() {
 				try {
 					serviceConnection.waitForStartup();
@@ -329,7 +329,7 @@ implements OnClickListener {
 	}
 
 	private void setRatingInDatabase(final Rating r) {
-		dbExecutor.execute(new Runnable() {
+		dbUiExecutor.execute(new Runnable() {
 			public void run() {
 				try {
 					serviceConnection.waitForStartup();
diff --git a/briar-android/src/net/sf/briar/android/groups/WriteGroupMessageActivity.java b/briar-android/src/net/sf/briar/android/groups/WriteGroupMessageActivity.java
index 1668c92eda187bb02e03c871878c5877da762519..1b1be08a0c0279a742b344d9a7c957dcc3bca80a 100644
--- a/briar-android/src/net/sf/briar/android/groups/WriteGroupMessageActivity.java
+++ b/briar-android/src/net/sf/briar/android/groups/WriteGroupMessageActivity.java
@@ -17,14 +17,17 @@ import java.util.concurrent.Executor;
 import java.util.logging.Logger;
 
 import net.sf.briar.R;
+import net.sf.briar.android.AuthorNameComparator;
 import net.sf.briar.android.BriarActivity;
 import net.sf.briar.android.BriarService;
 import net.sf.briar.android.BriarService.BriarServiceConnection;
+import net.sf.briar.android.LocalAuthorNameSpinnerAdapter;
 import net.sf.briar.android.widgets.CommonLayoutParams;
 import net.sf.briar.android.widgets.HorizontalSpace;
+import net.sf.briar.api.LocalAuthor;
 import net.sf.briar.api.android.BundleEncrypter;
+import net.sf.briar.api.android.DatabaseUiExecutor;
 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.messaging.Group;
 import net.sf.briar.api.messaging.GroupId;
@@ -47,7 +50,7 @@ import android.widget.TextView;
 import com.google.inject.Inject;
 
 public class WriteGroupMessageActivity extends BriarActivity
-implements OnClickListener, OnItemSelectedListener {
+implements OnItemSelectedListener, OnClickListener {
 
 	private static final Logger LOG =
 			Logger.getLogger(WriteGroupMessageActivity.class.getName());
@@ -56,16 +59,18 @@ implements OnClickListener, OnItemSelectedListener {
 			new BriarServiceConnection();
 
 	@Inject private BundleEncrypter bundleEncrypter;
-	private GroupNameSpinnerAdapter adapter = null;
-	private Spinner spinner = null;
+	private LocalAuthorNameSpinnerAdapter fromAdapter = null;
+	private GroupNameSpinnerAdapter toAdapter = null;
+	private Spinner fromSpinner = null, toSpinner = null;
 	private ImageButton sendButton = null;
 	private EditText content = null;
 
 	// Fields that are accessed from DB threads must be volatile
 	@Inject private volatile DatabaseComponent db;
-	@Inject @DatabaseExecutor private volatile Executor dbExecutor;
+	@Inject @DatabaseUiExecutor private volatile Executor dbUiExecutor;
 	@Inject private volatile MessageFactory messageFactory;
 	private volatile boolean restricted = false;
+	private volatile LocalAuthor localAuthor = null;
 	private volatile Group group = null;
 	private volatile GroupId groupId = null;
 	private volatile MessageId parentId = null;
@@ -85,33 +90,51 @@ implements OnClickListener, OnItemSelectedListener {
 		layout.setLayoutParams(CommonLayoutParams.MATCH_WRAP);
 		layout.setOrientation(VERTICAL);
 
-		LinearLayout actionBar = new LinearLayout(this);
-		actionBar.setLayoutParams(CommonLayoutParams.MATCH_WRAP);
-		actionBar.setOrientation(HORIZONTAL);
-		actionBar.setGravity(CENTER_VERTICAL);
+		LinearLayout header = new LinearLayout(this);
+		header.setLayoutParams(CommonLayoutParams.MATCH_WRAP);
+		header.setOrientation(HORIZONTAL);
+		header.setGravity(CENTER_VERTICAL);
 
-		TextView to = new TextView(this);
-		to.setTextSize(18);
-		to.setPadding(10, 10, 10, 10);
-		to.setText(R.string.to);
-		actionBar.addView(to);
+		TextView from = new TextView(this);
+		from.setTextSize(18);
+		from.setPadding(10, 10, 10, 10);
+		from.setText(R.string.from);
+		header.addView(from);
 
-		adapter = new GroupNameSpinnerAdapter(this);
-		spinner = new Spinner(this);
-		spinner.setAdapter(adapter);
-		spinner.setOnItemSelectedListener(this);
-		loadGroupList();
-		actionBar.addView(spinner);
+		fromAdapter = new LocalAuthorNameSpinnerAdapter(this);
+		fromSpinner = new Spinner(this);
+		fromSpinner.setOnItemSelectedListener(this);
+		loadLocalAuthorList();
+		header.addView(fromSpinner);
 
-		actionBar.addView(new HorizontalSpace(this));
+		header.addView(new HorizontalSpace(this));
 
 		sendButton = new ImageButton(this);
 		sendButton.setBackgroundResource(0);
 		sendButton.setImageResource(R.drawable.social_send_now);
-		sendButton.setEnabled(false);
+		sendButton.setEnabled(false); // Enabled when a group is selected
 		sendButton.setOnClickListener(this);
-		actionBar.addView(sendButton);
-		layout.addView(actionBar);
+		header.addView(sendButton);
+		layout.addView(header);
+
+		header = new LinearLayout(this);
+		header.setLayoutParams(CommonLayoutParams.MATCH_WRAP);
+		header.setOrientation(HORIZONTAL);
+		header.setGravity(CENTER_VERTICAL);
+
+		TextView to = new TextView(this);
+		to.setTextSize(18);
+		to.setPadding(10, 10, 10, 10);
+		to.setText(R.string.to);
+		header.addView(to);
+
+		toAdapter = new GroupNameSpinnerAdapter(this);
+		toSpinner = new Spinner(this);
+		toSpinner.setAdapter(toAdapter);
+		toSpinner.setOnItemSelectedListener(this);
+		loadGroupList();
+		header.addView(toSpinner);
+		layout.addView(header);
 
 		content = new EditText(this);
 		content.setPadding(10, 10, 10, 10);
@@ -128,20 +151,48 @@ implements OnClickListener, OnItemSelectedListener {
 				serviceConnection, 0);
 	}
 
-	// FIXME: If restricted, only load groups the user can post to
+	private void loadLocalAuthorList() {
+		dbUiExecutor.execute(new Runnable() {
+			public void run() {
+				try {
+					serviceConnection.waitForStartup();
+					updateLocalAuthorList(db.getLocalAuthors());
+				} catch(DbException e) {
+					if(LOG.isLoggable(WARNING))
+						LOG.log(WARNING, e.toString(), e);
+				} catch(InterruptedException e) {
+					LOG.info("Interrupted while waiting for service");
+					Thread.currentThread().interrupt();
+				}
+			}
+		});
+	}
+
+	private void updateLocalAuthorList(
+			final Collection<LocalAuthor> localAuthors) {
+		runOnUiThread(new Runnable() {
+			public void run() {
+				fromAdapter.clear();
+				for(LocalAuthor a : localAuthors) fromAdapter.add(a);
+				fromAdapter.sort(AuthorNameComparator.INSTANCE);
+			}
+		});
+	}
+
 	private void loadGroupList() {
-		dbExecutor.execute(new Runnable() {
+		dbUiExecutor.execute(new Runnable() {
 			public void run() {
 				try {
 					serviceConnection.waitForStartup();
-					List<Group> postable = new ArrayList<Group>();
+					List<Group> groups = new ArrayList<Group>();
 					if(restricted) {
-						postable.addAll(db.getLocalGroups());
+						groups.addAll(db.getLocalGroups());
 					} else {
 						for(Group g : db.getSubscriptions())
-							if(!g.isRestricted()) postable.add(g);
+							if(!g.isRestricted()) groups.add(g);
 					}
-					updateGroupList(Collections.unmodifiableList(postable));
+					groups = Collections.unmodifiableList(groups);
+					updateGroupList(groups);
 				} catch(DbException e) {
 					if(LOG.isLoggable(WARNING))
 						LOG.log(WARNING, e.toString(), e);
@@ -156,13 +207,15 @@ implements OnClickListener, OnItemSelectedListener {
 	private void updateGroupList(final Collection<Group> groups) {
 		runOnUiThread(new Runnable() {
 			public void run() {
+				int index = -1;
 				for(Group g : groups) {
 					if(g.getId().equals(groupId)) {
 						group = g;
-						spinner.setSelection(adapter.getCount());
+						index = toAdapter.getCount();
 					}
-					adapter.add(g);
+					toAdapter.add(g);
 				}
+				if(index != -1) toSpinner.setSelection(index);
 			}
 		});
 	}
@@ -180,21 +233,45 @@ implements OnClickListener, OnItemSelectedListener {
 		unbindService(serviceConnection);
 	}
 
+	public void onItemSelected(AdapterView<?> parent, View view, int position,
+			long id) {
+		if(parent == fromSpinner) {
+			localAuthor = fromAdapter.getItem(position);
+		} else if(parent == toSpinner) {
+			group = toAdapter.getItem(position);
+			groupId = group.getId();
+			sendButton.setEnabled(true);
+		}
+	}
+
+	public void onNothingSelected(AdapterView<?> parent) {
+		if(parent == fromSpinner) {
+			localAuthor = null;
+		} else if(parent == toSpinner) {
+			group = null;
+			groupId = null;
+			sendButton.setEnabled(false);
+		}
+	}
+
 	public void onClick(View view) {
 		if(group == null) throw new IllegalStateException();
 		try {
-			storeMessage(content.getText().toString().getBytes("UTF-8"));
+			storeMessage(localAuthor, group,
+					content.getText().toString().getBytes("UTF-8"));
 		} catch(UnsupportedEncodingException e) {
 			throw new RuntimeException(e);
 		}
 		finish();
 	}
 
-	private void storeMessage(final byte[] body) {
-		dbExecutor.execute(new Runnable() {
+	private void storeMessage(final LocalAuthor localAuthor, final Group group,
+			final byte[] body) {
+		dbUiExecutor.execute(new Runnable() {
 			public void run() {
 				try {
 					serviceConnection.waitForStartup();
+					// FIXME: Anonymous/pseudonymous, restricted/unrestricted
 					Message m = messageFactory.createAnonymousMessage(parentId,
 							group, "text/plain", body);
 					db.addLocalGroupMessage(m);
@@ -213,17 +290,4 @@ implements OnClickListener, OnItemSelectedListener {
 			}
 		});
 	}
-
-	public void onItemSelected(AdapterView<?> parent, View view, int position,
-			long id) {
-		group = adapter.getItem(position);
-		groupId = group.getId();
-		sendButton.setEnabled(true);
-	}
-
-	public void onNothingSelected(AdapterView<?> parent) {
-		group = null;
-		groupId = null;
-		sendButton.setEnabled(false);
-	}
 }
diff --git a/briar-android/src/net/sf/briar/android/invitation/AddContactActivity.java b/briar-android/src/net/sf/briar/android/invitation/AddContactActivity.java
index 5d2fb02ad9401fc633984fb3816931fd84218386..efc7251aa682c514ddc5d7cc78da76cba8d9476a 100644
--- a/briar-android/src/net/sf/briar/android/invitation/AddContactActivity.java
+++ b/briar-android/src/net/sf/briar/android/invitation/AddContactActivity.java
@@ -1,21 +1,42 @@
 package net.sf.briar.android.invitation;
 
+import static java.util.logging.Level.WARNING;
+
+import java.util.Collection;
+import java.util.concurrent.Executor;
+import java.util.logging.Logger;
+
+import net.sf.briar.android.AuthorNameComparator;
 import net.sf.briar.android.BriarActivity;
+import net.sf.briar.android.BriarService;
+import net.sf.briar.android.BriarService.BriarServiceConnection;
 import net.sf.briar.api.AuthorId;
+import net.sf.briar.api.LocalAuthor;
 import net.sf.briar.api.android.BundleEncrypter;
+import net.sf.briar.api.android.DatabaseUiExecutor;
 import net.sf.briar.api.android.ReferenceManager;
 import net.sf.briar.api.crypto.CryptoComponent;
+import net.sf.briar.api.db.DatabaseComponent;
+import net.sf.briar.api.db.DbException;
 import net.sf.briar.api.invitation.InvitationListener;
 import net.sf.briar.api.invitation.InvitationState;
 import net.sf.briar.api.invitation.InvitationTask;
 import net.sf.briar.api.invitation.InvitationTaskFactory;
+import android.content.Intent;
 import android.os.Bundle;
+import android.widget.ArrayAdapter;
 
 import com.google.inject.Inject;
 
 public class AddContactActivity extends BriarActivity
 implements InvitationListener {
 
+	private static final Logger LOG =
+			Logger.getLogger(AddContactActivity.class.getName());
+
+	private final BriarServiceConnection serviceConnection =
+			new BriarServiceConnection();
+
 	@Inject private BundleEncrypter bundleEncrypter;
 	@Inject private CryptoComponent crypto;
 	@Inject private InvitationTaskFactory invitationTaskFactory;
@@ -33,6 +54,10 @@ implements InvitationListener {
 	private boolean localMatched = false, remoteMatched = false;
 	private String contactName = null;
 
+	// Fields that are accessed from DB threads must be volatile
+	@Inject private volatile DatabaseComponent db;
+	@Inject @DatabaseUiExecutor private volatile Executor dbUiExecutor;
+
 	@Override
 	public void onCreate(Bundle state) {
 		super.onCreate(null);
@@ -105,6 +130,10 @@ implements InvitationListener {
 				}
 			}
 		}
+
+		// Bind to the service so we can wait for the DB to be opened
+		bindService(new Intent(BriarService.class.getName()),
+				serviceConnection, 0);
 	}
 
 	@Override
@@ -156,6 +185,34 @@ implements InvitationListener {
 		setView(view);
 	}
 
+	void loadLocalAuthorList(final ArrayAdapter<LocalAuthor> adapter) {
+		dbUiExecutor.execute(new Runnable() {
+			public void run() {
+				try {
+					serviceConnection.waitForStartup();
+					updateLocalAuthorList(adapter, db.getLocalAuthors());
+				} catch(DbException e) {
+					if(LOG.isLoggable(WARNING))
+						LOG.log(WARNING, e.toString(), e);
+				} catch(InterruptedException e) {
+					LOG.info("Interrupted while waiting for service");
+					Thread.currentThread().interrupt();
+				}
+			}
+		});
+	}
+
+	private void updateLocalAuthorList(final ArrayAdapter<LocalAuthor> adapter,
+			final Collection<LocalAuthor> localAuthors) {
+		runOnUiThread(new Runnable() {
+			public void run() {
+				adapter.clear();
+				for(LocalAuthor a : localAuthors) adapter.add(a);
+				adapter.sort(AuthorNameComparator.INSTANCE);
+			}
+		});
+	}
+
 	void setLocalAuthorId(AuthorId localAuthorId) {
 		this.localAuthorId = localAuthorId;
 	}
diff --git a/briar-android/src/net/sf/briar/android/invitation/AddContactView.java b/briar-android/src/net/sf/briar/android/invitation/AddContactView.java
index aaa6b1bc97d4dc883ae576f5a4c9808cc38824d5..221998aab17df01a724ab10cace742c31bac560d 100644
--- a/briar-android/src/net/sf/briar/android/invitation/AddContactView.java
+++ b/briar-android/src/net/sf/briar/android/invitation/AddContactView.java
@@ -9,8 +9,8 @@ abstract class AddContactView extends LinearLayout {
 
 	protected AddContactActivity container = null;
 
-	AddContactView(Context context) {
-		super(context);
+	AddContactView(Context ctx) {
+		super(ctx);
 	}
 
 	void init(AddContactActivity container) {
diff --git a/briar-android/src/net/sf/briar/android/invitation/NetworkSetupView.java b/briar-android/src/net/sf/briar/android/invitation/NetworkSetupView.java
index aa37cac92c17d77ac0b67a86477dc9f967ea823a..94f53297239c9f032d172a521e52efb670a2dd6a 100644
--- a/briar-android/src/net/sf/briar/android/invitation/NetworkSetupView.java
+++ b/briar-android/src/net/sf/briar/android/invitation/NetworkSetupView.java
@@ -1,16 +1,25 @@
 package net.sf.briar.android.invitation;
 
+import static android.view.Gravity.CENTER;
 import net.sf.briar.R;
+import net.sf.briar.android.LocalAuthorNameSpinnerAdapter;
 import net.sf.briar.android.widgets.CommonLayoutParams;
 import android.content.Context;
 import android.view.View;
 import android.view.View.OnClickListener;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemSelectedListener;
 import android.widget.Button;
+import android.widget.LinearLayout;
+import android.widget.Spinner;
 import android.widget.TextView;
 
 public class NetworkSetupView extends AddContactView
-implements WifiStateListener, BluetoothStateListener, OnClickListener {
+implements WifiStateListener, BluetoothStateListener, OnItemSelectedListener,
+OnClickListener {
 
+	private LocalAuthorNameSpinnerAdapter adapter = null;
+	private Spinner spinner = null;
 	private Button continueButton = null;
 
 	NetworkSetupView(Context ctx) {
@@ -20,13 +29,25 @@ implements WifiStateListener, BluetoothStateListener, OnClickListener {
 	void populate() {
 		removeAllViews();
 		Context ctx = getContext();
-		TextView chooseIdentity = new TextView(ctx);
-		chooseIdentity.setTextSize(14);
-		chooseIdentity.setPadding(10, 10, 10, 10);
-		chooseIdentity.setText(R.string.choose_identity);
-		addView(chooseIdentity);
 
-		// FIXME: Add a spinner for choosing which identity to use
+		LinearLayout innerLayout = new LinearLayout(ctx);
+		innerLayout.setLayoutParams(CommonLayoutParams.MATCH_WRAP);
+		innerLayout.setOrientation(HORIZONTAL);
+		innerLayout.setGravity(CENTER);
+
+		TextView yourIdentity = new TextView(ctx);
+		yourIdentity.setTextSize(18);
+		yourIdentity.setPadding(10, 10, 10, 10);
+		yourIdentity.setText(R.string.your_identity);
+		innerLayout.addView(yourIdentity);
+
+		adapter = new LocalAuthorNameSpinnerAdapter(ctx);
+		spinner = new Spinner(ctx);
+		spinner.setAdapter(adapter);
+		spinner.setOnItemSelectedListener(this);
+		container.loadLocalAuthorList(adapter);
+		innerLayout.addView(spinner);
+		addView(innerLayout);
 
 		WifiWidget wifi = new WifiWidget(ctx);
 		wifi.init(this);
@@ -70,8 +91,16 @@ implements WifiStateListener, BluetoothStateListener, OnClickListener {
 		else continueButton.setEnabled(false);
 	}
 
+	public void onItemSelected(AdapterView<?> parent, View view, int position,
+			long id) {
+		container.setLocalAuthorId(adapter.getItem(position).getId());		
+	}
+
+	public void onNothingSelected(AdapterView<?> parent) {
+		container.setLocalAuthorId(null);
+	}
+
 	public void onClick(View view) {
-		// Continue
 		container.setView(new InvitationCodeView(container));
 	}
 }
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 5e2cbacc1d14adeb5f25e68d41ba36ad692a220a..a84117d80bd1d7efb01b12c2b2521bd5b9fc661b 100644
--- a/briar-android/src/net/sf/briar/android/messages/ConversationActivity.java
+++ b/briar-android/src/net/sf/briar/android/messages/ConversationActivity.java
@@ -6,7 +6,6 @@ import static java.util.logging.Level.INFO;
 import static java.util.logging.Level.WARNING;
 
 import java.util.Collection;
-import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Executor;
 import java.util.logging.Logger;
 
@@ -118,10 +117,8 @@ implements DatabaseListener, OnClickListener, OnItemClickListener {
 					long duration = System.currentTimeMillis() - now;
 					if(LOG.isLoggable(INFO))
 						LOG.info("Load took " + duration + " ms");
-					// Wait for the headers to be displayed in the UI
-					CountDownLatch latch = new CountDownLatch(1);
-					displayHeaders(latch, headers);
-					latch.await();
+					// Display the headers in the UI
+					displayHeaders(headers);
 				} catch(NoSuchContactException e) {
 					if(LOG.isLoggable(INFO)) LOG.info("Contact removed");
 					finishOnUiThread();
@@ -130,25 +127,21 @@ implements DatabaseListener, OnClickListener, OnItemClickListener {
 						LOG.log(WARNING, e.toString(), e);
 				} catch(InterruptedException e) {
 					if(LOG.isLoggable(INFO))
-						LOG.info("Interrupted while loading headers");
+						LOG.info("Interrupted while waiting for service");
 					Thread.currentThread().interrupt();
 				}
 			}
 		});
 	}
 
-	private void displayHeaders(final CountDownLatch latch,
+	private void displayHeaders(
 			final Collection<PrivateMessageHeader> headers) {
 		runOnUiThread(new Runnable() {
 			public void run() {
-				try {
-					adapter.clear();
-					for(PrivateMessageHeader h : headers) adapter.add(h);
-					adapter.sort(AscendingHeaderComparator.INSTANCE);
-					selectFirstUnread();
-				} finally {
-					latch.countDown();
-				}
+				adapter.clear();
+				for(PrivateMessageHeader h : headers) adapter.add(h);
+				adapter.sort(AscendingHeaderComparator.INSTANCE);
+				selectFirstUnread();
 			}
 		});
 	}
@@ -199,7 +192,7 @@ implements DatabaseListener, OnClickListener, OnItemClickListener {
 			}
 		} else if(e instanceof MessageExpiredEvent) {
 			if(LOG.isLoggable(INFO)) LOG.info("Message expired, reloading");
-			loadHeaders(); // FIXME: Don't reload everything
+			loadHeaders();
 		} else if(e instanceof PrivateMessageAddedEvent) {
 			PrivateMessageAddedEvent p = (PrivateMessageAddedEvent) e;
 			if(p.getContactId().equals(contactId)) {
diff --git a/briar-android/src/net/sf/briar/android/messages/ConversationListActivity.java b/briar-android/src/net/sf/briar/android/messages/ConversationListActivity.java
index 3c2938da420510c94cf01914094fcddc4244df48..dfb5f76064d6f6a9d8bcfa6432678554098a6225 100644
--- a/briar-android/src/net/sf/briar/android/messages/ConversationListActivity.java
+++ b/briar-android/src/net/sf/briar/android/messages/ConversationListActivity.java
@@ -9,7 +9,6 @@ import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Comparator;
 import java.util.List;
-import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Executor;
 import java.util.logging.Logger;
 
@@ -102,8 +101,6 @@ implements OnClickListener, DatabaseListener {
 					// Wait for the service to be bound and started
 					serviceConnection.waitForStartup();
 					// Load the contact list from the database
-					Collection<CountDownLatch> latches =
-							new ArrayList<CountDownLatch>();
 					long now = System.currentTimeMillis();
 					for(Contact c : db.getContacts()) {
 						try {
@@ -111,9 +108,7 @@ implements OnClickListener, DatabaseListener {
 							Collection<PrivateMessageHeader> headers =
 									db.getPrivateMessageHeaders(c.getId());
 							// Display the headers in the UI
-							CountDownLatch latch = new CountDownLatch(1);
-							displayHeaders(latch, c, headers);
-							latches.add(latch);
+							displayHeaders(c, headers);
 						} catch(NoSuchContactException e) {
 							if(LOG.isLoggable(INFO))
 								LOG.info("Contact removed");
@@ -122,39 +117,33 @@ implements OnClickListener, DatabaseListener {
 					long duration = System.currentTimeMillis() - now;
 					if(LOG.isLoggable(INFO))
 						LOG.info("Full load took " + duration + " ms");
-					// Wait for the headers to be displayed in the UI
-					for(CountDownLatch latch : latches) latch.await();
 				} catch(DbException e) {
 					if(LOG.isLoggable(WARNING))
 						LOG.log(WARNING, e.toString(), e);
 				} catch(InterruptedException e) {
 					if(LOG.isLoggable(INFO))
-						LOG.info("Interrupted while loading headers");
+						LOG.info("Interrupted while waiting for service");
 					Thread.currentThread().interrupt();
 				}
 			}
 		});
 	}
 
-	private void displayHeaders(final CountDownLatch latch, final Contact c,
+	private void displayHeaders(final Contact c,
 			final Collection<PrivateMessageHeader> headers) {
 		runOnUiThread(new Runnable() {
 			public void run() {
-				try {
-					// Remove the old item, if any
-					ConversationListItem item = findConversation(c.getId());
-					if(item != null) adapter.remove(item);
-					// Add a new item if there are any headers to display
-					if(!headers.isEmpty()) {
-						List<PrivateMessageHeader> headerList =
-								new ArrayList<PrivateMessageHeader>(headers);
-						adapter.add(new ConversationListItem(c, headerList));
-						adapter.sort(ConversationComparator.INSTANCE);
-					}
-					selectFirstUnread();
-				} finally {
-					latch.countDown();
+				// Remove the old item, if any
+				ConversationListItem item = findConversation(c.getId());
+				if(item != null) adapter.remove(item);
+				// Add a new item if there are any headers to display
+				if(!headers.isEmpty()) {
+					List<PrivateMessageHeader> headerList =
+							new ArrayList<PrivateMessageHeader>(headers);
+					adapter.add(new ConversationListItem(c, headerList));
+					adapter.sort(ConversationComparator.INSTANCE);
 				}
+				selectFirstUnread();
 			}
 		});
 	}
@@ -203,7 +192,7 @@ implements OnClickListener, DatabaseListener {
 			loadHeaders(((ContactRemovedEvent) e).getContactId());
 		} else if(e instanceof MessageExpiredEvent) {
 			if(LOG.isLoggable(INFO)) LOG.info("Message expired, reloading");
-			loadHeaders(); // FIXME: Don't reload everything
+			loadHeaders();
 		} else if(e instanceof PrivateMessageAddedEvent) {
 			if(LOG.isLoggable(INFO)) LOG.info("Message added, reloading");
 			loadHeaders(((PrivateMessageAddedEvent) e).getContactId());
@@ -222,9 +211,7 @@ implements OnClickListener, DatabaseListener {
 					long duration = System.currentTimeMillis() - now;
 					if(LOG.isLoggable(INFO))
 						LOG.info("Partial load took " + duration + " ms");
-					CountDownLatch latch = new CountDownLatch(1);
-					displayHeaders(latch, contact, headers);
-					latch.await();
+					displayHeaders(contact, headers);
 				} catch(NoSuchContactException e) {
 					if(LOG.isLoggable(INFO)) LOG.info("Contact removed");
 					removeConversation(c);
@@ -233,7 +220,7 @@ implements OnClickListener, DatabaseListener {
 						LOG.log(WARNING, e.toString(), e);
 				} catch(InterruptedException e) {
 					if(LOG.isLoggable(INFO))
-						LOG.info("Interrupted while loading headers");
+						LOG.info("Interrupted while waiting for service");
 					Thread.currentThread().interrupt();
 				}
 			}
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 30a17a264bafe5526a1c83a8eaa79bcf622deb4a..52c01ee4fb9afcc173770b3bc9d13480a45469e6 100644
--- a/briar-android/src/net/sf/briar/android/messages/ReadPrivateMessageActivity.java
+++ b/briar-android/src/net/sf/briar/android/messages/ReadPrivateMessageActivity.java
@@ -21,8 +21,8 @@ import net.sf.briar.android.widgets.HorizontalBorder;
 import net.sf.briar.android.widgets.HorizontalSpace;
 import net.sf.briar.api.ContactId;
 import net.sf.briar.api.android.BundleEncrypter;
+import net.sf.briar.api.android.DatabaseUiExecutor;
 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;
@@ -61,7 +61,7 @@ implements OnClickListener {
 
 	// Fields that are accessed from DB threads must be volatile
 	@Inject private volatile DatabaseComponent db;
-	@Inject @DatabaseExecutor private volatile Executor dbExecutor;
+	@Inject @DatabaseUiExecutor private volatile Executor dbUiExecutor;
 	private volatile MessageId messageId = null;
 
 	@Override
@@ -187,7 +187,7 @@ implements OnClickListener {
 	}
 
 	private void setReadInDatabase(final boolean read) {
-		dbExecutor.execute(new Runnable() {
+		dbUiExecutor.execute(new Runnable() {
 			public void run() {
 				try {
 					serviceConnection.waitForStartup();
@@ -216,7 +216,7 @@ implements OnClickListener {
 	}
 
 	private void loadMessageBody() {
-		dbExecutor.execute(new Runnable() {
+		dbUiExecutor.execute(new Runnable() {
 			public void run() {
 				try {
 					serviceConnection.waitForStartup();
diff --git a/briar-android/src/net/sf/briar/android/messages/WritePrivateMessageActivity.java b/briar-android/src/net/sf/briar/android/messages/WritePrivateMessageActivity.java
index 589cdc31ef974b6dbb0673f5fff611d0b0c75445..838acb20d8f53d76759bbd84fb4128b21a5eb777 100644
--- a/briar-android/src/net/sf/briar/android/messages/WritePrivateMessageActivity.java
+++ b/briar-android/src/net/sf/briar/android/messages/WritePrivateMessageActivity.java
@@ -19,11 +19,13 @@ 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.HorizontalSpace;
+import net.sf.briar.api.AuthorId;
 import net.sf.briar.api.Contact;
 import net.sf.briar.api.ContactId;
+import net.sf.briar.api.LocalAuthor;
 import net.sf.briar.api.android.BundleEncrypter;
+import net.sf.briar.api.android.DatabaseUiExecutor;
 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.messaging.Message;
 import net.sf.briar.api.messaging.MessageFactory;
@@ -44,7 +46,7 @@ import android.widget.TextView;
 import com.google.inject.Inject;
 
 public class WritePrivateMessageActivity extends BriarActivity
-implements OnClickListener, OnItemSelectedListener {
+implements OnItemSelectedListener, OnClickListener {
 
 	private static final Logger LOG =
 			Logger.getLogger(WritePrivateMessageActivity.class.getName());
@@ -53,6 +55,7 @@ implements OnClickListener, OnItemSelectedListener {
 			new BriarServiceConnection();
 
 	@Inject private BundleEncrypter bundleEncrypter;
+	private TextView from = null;
 	private ContactNameSpinnerAdapter adapter = null;
 	private Spinner spinner = null;
 	private ImageButton sendButton = null;
@@ -60,8 +63,9 @@ implements OnClickListener, OnItemSelectedListener {
 
 	// Fields that are accessed from DB threads must be volatile
 	@Inject private volatile DatabaseComponent db;
-	@Inject @DatabaseExecutor private volatile Executor dbExecutor;
+	@Inject @DatabaseUiExecutor private volatile Executor dbUiExecutor;
 	@Inject private volatile MessageFactory messageFactory;
+	private volatile LocalAuthor localAuthor = null;
 	private volatile ContactId contactId = null;
 	private volatile MessageId parentId = null;
 
@@ -79,33 +83,46 @@ implements OnClickListener, OnItemSelectedListener {
 		layout.setLayoutParams(CommonLayoutParams.MATCH_WRAP);
 		layout.setOrientation(VERTICAL);
 
-		LinearLayout actionBar = new LinearLayout(this);
-		actionBar.setLayoutParams(CommonLayoutParams.MATCH_WRAP);
-		actionBar.setOrientation(HORIZONTAL);
-		actionBar.setGravity(CENTER_VERTICAL);
+		LinearLayout header = new LinearLayout(this);
+		header.setLayoutParams(CommonLayoutParams.MATCH_WRAP);
+		header.setOrientation(HORIZONTAL);
+		header.setGravity(CENTER_VERTICAL);
+
+		from = new TextView(this);
+		from.setTextSize(18);
+		from.setMaxLines(1);
+		from.setPadding(10, 10, 10, 10);
+		from.setText(R.string.from);
+		header.addView(from);
+
+		header.addView(new HorizontalSpace(this));
+
+		sendButton = new ImageButton(this);
+		sendButton.setBackgroundResource(0);
+		sendButton.setImageResource(R.drawable.social_send_now);
+		sendButton.setEnabled(false); // Enabled after loading the local author
+		sendButton.setOnClickListener(this);
+		header.addView(sendButton);
+		layout.addView(header);
+
+		header = new LinearLayout(this);
+		header.setLayoutParams(CommonLayoutParams.MATCH_WRAP);
+		header.setOrientation(HORIZONTAL);
+		header.setGravity(CENTER_VERTICAL);
 
 		TextView to = new TextView(this);
 		to.setTextSize(18);
 		to.setPadding(10, 10, 10, 10);
 		to.setText(R.string.to);
-		actionBar.addView(to);
+		header.addView(to);
 
 		adapter = new ContactNameSpinnerAdapter(this);
 		spinner = new Spinner(this);
 		spinner.setAdapter(adapter);
 		spinner.setOnItemSelectedListener(this);
 		loadContactList();
-		actionBar.addView(spinner);
-
-		actionBar.addView(new HorizontalSpace(this));
-
-		sendButton = new ImageButton(this);
-		sendButton.setBackgroundResource(0);
-		sendButton.setImageResource(R.drawable.social_send_now);
-		sendButton.setEnabled(false);
-		sendButton.setOnClickListener(this);
-		actionBar.addView(sendButton);
-		layout.addView(actionBar);
+		header.addView(spinner);
+		layout.addView(header);
 
 		content = new EditText(this);
 		content.setPadding(10, 10, 10, 10);
@@ -123,7 +140,7 @@ implements OnClickListener, OnItemSelectedListener {
 	}
 
 	private void loadContactList() {
-		dbExecutor.execute(new Runnable() {
+		dbUiExecutor.execute(new Runnable() {
 			public void run() {
 				try {
 					serviceConnection.waitForStartup();
@@ -142,11 +159,12 @@ implements OnClickListener, OnItemSelectedListener {
 	private void updateContactList(final Collection<Contact> contacts) {
 		runOnUiThread(new Runnable() {
 			public void run() {
+				int index = -1;
 				for(Contact c : contacts) {
-					if(c.getId().equals(contactId))
-						spinner.setSelection(adapter.getCount());
+					if(c.getId().equals(contactId)) index = adapter.getCount();
 					adapter.add(c);
 				}
+				if(index != -1) spinner.setSelection(index);
 			}
 		});
 	}
@@ -164,18 +182,55 @@ implements OnClickListener, OnItemSelectedListener {
 		unbindService(serviceConnection);
 	}
 
+	public void onItemSelected(AdapterView<?> parent, View view, int position,
+			long id) {
+		Contact c = adapter.getItem(position);
+		loadLocalAuthor(c.getLocalAuthorId());
+		contactId = c.getId();
+	}
+
+	public void onNothingSelected(AdapterView<?> parent) {
+		contactId = null;
+		sendButton.setEnabled(false);
+	}
+
+	private void loadLocalAuthor(final AuthorId a) {
+		dbUiExecutor.execute(new Runnable() {
+			public void run() {
+				try {
+					serviceConnection.waitForStartup();
+					localAuthor = db.getLocalAuthor(a);
+					runOnUiThread(new Runnable() {
+						public void run() {
+							sendButton.setEnabled(true);
+						}
+					});
+				} catch(DbException e) {
+					if(LOG.isLoggable(WARNING))
+						LOG.log(WARNING, e.toString(), e);
+				} catch(InterruptedException e) {
+					LOG.info("Interrupted while waiting for service");
+					Thread.currentThread().interrupt();
+				}
+			}
+		});
+	}
+	
 	public void onClick(View view) {
-		if(contactId == null) throw new IllegalStateException();
+		if(localAuthor == null || contactId == null)
+			throw new IllegalStateException();
 		try {
-			storeMessage(content.getText().toString().getBytes("UTF-8"));
+			storeMessage(localAuthor, contactId,
+					content.getText().toString().getBytes("UTF-8"));
 		} catch(UnsupportedEncodingException e) {
 			throw new RuntimeException(e);
 		}
 		finish();
 	}
 
-	private void storeMessage(final byte[] body) {
-		dbExecutor.execute(new Runnable() {
+	private void storeMessage(final LocalAuthor localAuthor,
+			final ContactId contactId, final byte[] body) {
+		dbUiExecutor.execute(new Runnable() {
 			public void run() {
 				try {
 					serviceConnection.waitForStartup();
@@ -197,15 +252,4 @@ implements OnClickListener, OnItemSelectedListener {
 			}
 		});
 	}
-
-	public void onItemSelected(AdapterView<?> parent, View view, int position,
-			long id) {
-		contactId = adapter.getItem(position).getId();
-		sendButton.setEnabled(true);
-	}
-
-	public void onNothingSelected(AdapterView<?> parent) {
-		contactId = null;
-		sendButton.setEnabled(false);
-	}
 }
diff --git a/briar-api/src/net/sf/briar/api/Contact.java b/briar-api/src/net/sf/briar/api/Contact.java
index c86708276ed5f68d3096ad24943bfe42fe855b4d..4d9f558d6d37d1f041793b6dc9eab31171c734f5 100644
--- a/briar-api/src/net/sf/briar/api/Contact.java
+++ b/briar-api/src/net/sf/briar/api/Contact.java
@@ -4,11 +4,14 @@ public class Contact {
 
 	private final ContactId id;
 	private final Author author;
+	private final AuthorId localAuthorId;
 	private final long lastConnected;
 
-	public Contact(ContactId id, Author author, long lastConnected) {
+	public Contact(ContactId id, Author author, AuthorId localAuthorId,
+			long lastConnected) {
 		this.id = id;
 		this.author = author;
+		this.localAuthorId = localAuthorId;
 		this.lastConnected = lastConnected;
 	}
 
@@ -20,6 +23,10 @@ public class Contact {
 		return author;
 	}
 
+	public AuthorId getLocalAuthorId() {
+		return localAuthorId;
+	}
+
 	public long getLastConnected() {
 		return lastConnected;
 	}
diff --git a/briar-api/src/net/sf/briar/api/db/event/MessageExpiredEvent.java b/briar-api/src/net/sf/briar/api/db/event/MessageExpiredEvent.java
index 1c1e11ef914f2582d1369bf744f27f744ab45999..5f130f78a7f5fb00f80c0197549168c2d69c2e02 100644
--- a/briar-api/src/net/sf/briar/api/db/event/MessageExpiredEvent.java
+++ b/briar-api/src/net/sf/briar/api/db/event/MessageExpiredEvent.java
@@ -1,22 +1,9 @@
 package net.sf.briar.api.db.event;
 
-import java.util.Collection;
-
-import net.sf.briar.api.messaging.MessageId;
-
 /**
  * An event that is broadcast when one or messages expire from the database,
  * potentially changing the database's retention time.
  */
 public class MessageExpiredEvent extends DatabaseEvent {
 
-	private final Collection<MessageId> expired;
-
-	public MessageExpiredEvent(Collection<MessageId> expired) {
-		this.expired = expired;
-	}
-
-	public Collection<MessageId> getMessageIds() {
-		return expired;
-	}
 }
diff --git a/briar-core/src/net/sf/briar/db/DatabaseComponentImpl.java b/briar-core/src/net/sf/briar/db/DatabaseComponentImpl.java
index eb4da283ddcd2971533631b64c548a958012362c..d65eb6f0010690f92127898bbf26a3ac75bb2fbf 100644
--- a/briar-core/src/net/sf/briar/db/DatabaseComponentImpl.java
+++ b/briar-core/src/net/sf/briar/db/DatabaseComponentImpl.java
@@ -2045,9 +2045,8 @@ DatabaseCleaner.Callback {
 		while(freeSpace < MIN_FREE_SPACE) {
 			boolean expired = expireMessages(BYTES_PER_SWEEP);
 			if(freeSpace < CRITICAL_FREE_SPACE && !expired) {
-				// FIXME: Work out what to do here - the amount of free space
-				// is critically low and there are no messages left to expire
-				throw new Error("Disk space is critical");
+				// FIXME: Work out what to do here
+				throw new Error("Disk space is critically low");
 			}
 			Thread.yield();
 			freeSpace = db.getFreeSpace();
@@ -2086,7 +2085,7 @@ DatabaseCleaner.Callback {
 			messageLock.writeLock().unlock();
 		}
 		if(expired.isEmpty()) return false;
-		callListeners(new MessageExpiredEvent(expired));
+		callListeners(new MessageExpiredEvent());
 		return true;
 	}
 
diff --git a/briar-core/src/net/sf/briar/db/JdbcDatabase.java b/briar-core/src/net/sf/briar/db/JdbcDatabase.java
index 59747daba35d71ef1b1a6b630867933179838c45..3abb7883e408596e1ca717c21ee52399577c058c 100644
--- a/briar-core/src/net/sf/briar/db/JdbcDatabase.java
+++ b/briar-core/src/net/sf/briar/db/JdbcDatabase.java
@@ -1155,7 +1155,8 @@ abstract class JdbcDatabase implements Database<Connection> {
 		PreparedStatement ps = null;
 		ResultSet rs = null;
 		try {
-			String sql = "SELECT authorId, name, publicKey, lastConnected"
+			String sql = "SELECT authorId, name, publicKey, localAuthorId,"
+					+ " lastConnected"
 					+ " FROM contacts AS c"
 					+ " JOIN connectionTimes AS ct"
 					+ " ON c.contactId = ct.contactId"
@@ -1167,11 +1168,12 @@ abstract class JdbcDatabase implements Database<Connection> {
 			AuthorId authorId = new AuthorId(rs.getBytes(1));
 			String name = rs.getString(2);
 			byte[] publicKey = rs.getBytes(3);
-			long lastConnected = rs.getLong(4);
+			AuthorId localAuthorId = new AuthorId(rs.getBytes(4));
+			long lastConnected = rs.getLong(5);
 			rs.close();
 			ps.close();
 			Author author = new Author(authorId, name, publicKey);
-			return new Contact(c, author, lastConnected);
+			return new Contact(c, author, localAuthorId, lastConnected);
 		} catch(SQLException e) {
 			tryToClose(rs);
 			tryToClose(ps);
@@ -1205,7 +1207,7 @@ abstract class JdbcDatabase implements Database<Connection> {
 		ResultSet rs = null;
 		try {
 			String sql = "SELECT c.contactId, authorId, name, publicKey,"
-					+ " lastConnected"
+					+ " localAuthorId, lastConnected"
 					+ " FROM contacts AS c"
 					+ " JOIN connectionTimes AS ct"
 					+ " ON c.contactId = ct.contactId";
@@ -1217,9 +1219,11 @@ abstract class JdbcDatabase implements Database<Connection> {
 				AuthorId authorId = new AuthorId(rs.getBytes(2));
 				String name = rs.getString(3);
 				byte[] publicKey = rs.getBytes(4);
-				long lastConnected = rs.getLong(5);
+				AuthorId localAuthorId = new AuthorId(rs.getBytes(5));
+				long lastConnected = rs.getLong(6);
 				Author author = new Author(authorId, name, publicKey);
-				contacts.add(new Contact(contactId, author, lastConnected));
+				contacts.add(new Contact(contactId, author, localAuthorId,
+						lastConnected));
 			}
 			rs.close();
 			ps.close();
diff --git a/briar-core/src/net/sf/briar/transport/KeyManagerImpl.java b/briar-core/src/net/sf/briar/transport/KeyManagerImpl.java
index 9c51a19181576b85249ab4b97d489204b4d79a5d..d72f05d1650fe43e4f014901a3457b72e9b2b147 100644
--- a/briar-core/src/net/sf/briar/transport/KeyManagerImpl.java
+++ b/briar-core/src/net/sf/briar/transport/KeyManagerImpl.java
@@ -134,6 +134,7 @@ class KeyManagerImpl extends TimerTask implements KeyManager, DatabaseListener {
 			} else if(now >= creationTime) {
 				incomingNew.put(k, s);
 			} else {
+				// FIXME: Work out what to do here
 				throw new Error("Clock has moved backwards");
 			}
 		}
diff --git a/briar-tests/src/net/sf/briar/db/DatabaseComponentTest.java b/briar-tests/src/net/sf/briar/db/DatabaseComponentTest.java
index 7eeb1021a5823c6b59088a7fc896ecbe035fccf5..d0a7f7299de0d47ba2ff89947a0570a2e5da7088 100644
--- a/briar-tests/src/net/sf/briar/db/DatabaseComponentTest.java
+++ b/briar-tests/src/net/sf/briar/db/DatabaseComponentTest.java
@@ -102,7 +102,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 		transportProperties = new TransportProperties(Collections.singletonMap(
 				"foo", "bar"));
 		contactId = new ContactId(234);
-		contact = new Contact(contactId, author, timestamp);
+		contact = new Contact(contactId, author, localAuthorId, timestamp);
 		endpoint = new Endpoint(contactId, transportId, 123, true);
 		temporarySecret = new TemporarySecret(contactId, transportId, 123,
 				false, 234, new byte[32], 345, 456, new byte[4]);