diff --git a/briar-android/res/values/color.xml b/briar-android/res/values/color.xml
index c7cc015fe1b363703b9d5a35ac101718e30b519f..90b7b6e69693a473d941c46649d930d570b0dd62 100644
--- a/briar-android/res/values/color.xml
+++ b/briar-android/res/values/color.xml
@@ -3,6 +3,7 @@
     <color name="home_screen_background">#FFFFFF</color>
     <color name="content_background">#FFFFFF</color>
     <color name="unread_background">#FFFFFF</color>
+    <color name="read_background">#EEEEEE</color>
 	<color name="horizontal_border">#CCCCCC</color>
 	<color name="no_posts">#AAAAAA</color>
 	<color name="no_messages">#AAAAAA</color>
diff --git a/briar-android/src/org/briarproject/android/contact/ConversationActivity.java b/briar-android/src/org/briarproject/android/contact/ConversationActivity.java
index b2d4ea85b5e5c4ff4a2573e95b8ae811a6c4b614..de9f61fd06a6cacc0e38d29cc3e5028893e0a118 100644
--- a/briar-android/src/org/briarproject/android/contact/ConversationActivity.java
+++ b/briar-android/src/org/briarproject/android/contact/ConversationActivity.java
@@ -27,6 +27,7 @@ import org.briarproject.api.db.DatabaseComponent;
 import org.briarproject.api.db.DbException;
 import org.briarproject.api.db.MessageHeader;
 import org.briarproject.api.db.NoSuchContactException;
+import org.briarproject.api.db.NoSuchMessageException;
 import org.briarproject.api.event.ContactRemovedEvent;
 import org.briarproject.api.event.Event;
 import org.briarproject.api.event.EventListener;
@@ -34,6 +35,7 @@ import org.briarproject.api.event.MessageAddedEvent;
 import org.briarproject.api.event.MessageExpiredEvent;
 import org.briarproject.api.lifecycle.LifecycleManager;
 import org.briarproject.api.messaging.GroupId;
+import org.briarproject.api.messaging.MessageId;
 
 import android.content.Intent;
 import android.os.Bundle;
@@ -131,15 +133,11 @@ implements EventListener, OnClickListener, OnItemClickListener {
 							db.getInboxMessageHeaders(contactId);
 					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) {
 					if(LOG.isLoggable(INFO)) LOG.info("Contact removed");
-					runOnUiThread(new Runnable() {
-						public void run() {
-							finish();
-						}
-					});
+					finishOnUiThread();
 				} catch(DbException e) {
 					if(LOG.isLoggable(WARNING))
 						LOG.log(WARNING, e.toString(), e);
@@ -163,21 +161,83 @@ implements EventListener, OnClickListener, OnItemClickListener {
 					adapter.add(new ConversationItem(h));
 				adapter.sort(ConversationItemComparator.INSTANCE);
 				adapter.notifyDataSetChanged();
-				selectFirstUnread();
+				expandMessages();
 			}
 		});
 	}
 
-	private void selectFirstUnread() {
-		int firstUnread = -1, count = adapter.getCount();
+	private void expandMessages() {
+		// Expand unread messages and the last three messages
+		int firstExpanded = -1, count = adapter.getCount();
+		if(count == 0) return;
 		for(int i = 0; i < count; i++) {
-			if(!adapter.getItem(i).getHeader().isRead()) {
-				firstUnread = i;
-				break;
+			ConversationItem item = adapter.getItem(i);
+			if(!item.getHeader().isRead() || i >= count - 3) {
+				if(firstExpanded == -1) firstExpanded = i;
+				item.setExpanded(true);
+				loadMessage(item.getHeader());
 			}
 		}
-		if(firstUnread == -1) list.setSelection(count - 1);
-		else list.setSelection(firstUnread);
+		// Scroll to the first expanded message
+		if(firstExpanded == -1) list.setSelection(count - 1);
+		else list.setSelection(firstExpanded);
+	}
+
+	private void loadMessage(final MessageHeader h) {
+		dbUiExecutor.execute(new Runnable() {
+			public void run() {
+				try {
+					lifecycleManager.waitForDatabase();
+					long now = System.currentTimeMillis();
+					byte[] body = db.getMessageBody(h.getId());
+					long duration = System.currentTimeMillis() - now;
+					if(LOG.isLoggable(INFO))
+						LOG.info("Loading message took " + duration + " ms");
+					displayMessage(h.getId(), body);
+					if(!h.isRead()) {
+						now = System.currentTimeMillis();
+						db.setReadFlag(h.getId(), true);
+						duration = System.currentTimeMillis() - now;
+						if(LOG.isLoggable(INFO))
+							LOG.info("Setting read took " + duration + " ms");
+					}
+				} catch(NoSuchMessageException e) {
+					if(LOG.isLoggable(INFO)) LOG.info("Message expired");
+					// The item will be removed when we get the event
+				} 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 database");
+					Thread.currentThread().interrupt();
+				}
+			}
+		});
+	}
+
+	private void displayMessage(final MessageId m, final byte[] body) {
+		runOnUiThread(new Runnable() {
+			public void run() {
+				int count = adapter.getCount();
+				for(int i = 0; i < count; i++) {
+					ConversationItem item = adapter.getItem(i);
+					if(item.getHeader().getId().equals(m)) {
+						item.setBody(body);
+						adapter.notifyDataSetChanged();
+						return;
+					}
+				}
+			}
+		});
+	}
+
+	private void finishOnUiThread() {
+		runOnUiThread(new Runnable() {
+			public void run() {
+				finish();
+			}
+		});
 	}
 
 	@Override
@@ -201,11 +261,7 @@ implements EventListener, OnClickListener, OnItemClickListener {
 			ContactRemovedEvent c = (ContactRemovedEvent) e;
 			if(c.getContactId().equals(contactId)) {
 				if(LOG.isLoggable(INFO)) LOG.info("Contact removed");
-				runOnUiThread(new Runnable() {
-					public void run() {
-						finish();
-					}
-				});
+				finishOnUiThread();
 			}
 		} else if(e instanceof MessageAddedEvent) {
 			GroupId g = ((MessageAddedEvent) e).getGroup().getId();
diff --git a/briar-android/src/org/briarproject/android/contact/ConversationAdapter.java b/briar-android/src/org/briarproject/android/contact/ConversationAdapter.java
index f22216ec71a10f81d4a8c53eddf2767cd2c8094c..62f4eebc5383979180ecbae425e8dba7b82b3501 100644
--- a/briar-android/src/org/briarproject/android/contact/ConversationAdapter.java
+++ b/briar-android/src/org/briarproject/android/contact/ConversationAdapter.java
@@ -1,7 +1,9 @@
 package org.briarproject.android.contact;
 
+import static android.view.Gravity.CENTER_HORIZONTAL;
 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 org.briarproject.android.util.CommonLayoutParams.WRAP_WRAP_1;
 import static org.briarproject.api.Author.Status.VERIFIED;
@@ -12,6 +14,7 @@ import org.briarproject.R;
 import org.briarproject.android.util.AuthorView;
 import org.briarproject.android.util.LayoutUtils;
 import org.briarproject.api.db.MessageHeader;
+import org.briarproject.util.StringUtils;
 
 import android.content.Context;
 import android.content.res.Resources;
@@ -20,6 +23,7 @@ import android.view.View;
 import android.view.ViewGroup;
 import android.widget.ArrayAdapter;
 import android.widget.LinearLayout;
+import android.widget.ProgressBar;
 import android.widget.TextView;
 
 class ConversationAdapter extends ArrayAdapter<ConversationItem> {
@@ -34,29 +38,53 @@ class ConversationAdapter extends ArrayAdapter<ConversationItem> {
 
 	@Override
 	public View getView(int position, View convertView, ViewGroup parent) {
-		MessageHeader header = getItem(position).getHeader();
+		ConversationItem item = getItem(position);
+		MessageHeader header = item.getHeader();
 		Context ctx = getContext();
+		Resources res = ctx.getResources();
 
-		LinearLayout layout = new LinearLayout(ctx);
-		layout.setOrientation(HORIZONTAL);
-		layout.setGravity(CENTER_VERTICAL);
-		if(!header.isRead()) {
-			Resources res = ctx.getResources();
-			layout.setBackgroundColor(res.getColor(R.color.unread_background));
-		}
+		LinearLayout headerLayout = new LinearLayout(ctx);
+		headerLayout.setOrientation(HORIZONTAL);
+		headerLayout.setGravity(CENTER_VERTICAL);
+		int background;
+		if(header.isRead()) background = res.getColor(R.color.read_background);
+		else background = res.getColor(R.color.unread_background);
+		headerLayout.setBackgroundColor(background);
 
 		AuthorView authorView = new AuthorView(ctx);
 		authorView.setLayoutParams(WRAP_WRAP_1);
 		authorView.init(header.getAuthor().getName(), VERIFIED);
-		layout.addView(authorView);
+		headerLayout.addView(authorView);
 
 		TextView date = new TextView(ctx);
 		date.setTextSize(14);
 		date.setPadding(0, pad, pad, pad);
 		long then = header.getTimestamp(), now = System.currentTimeMillis();
 		date.setText(DateUtils.formatSameDayTime(then, now, SHORT, SHORT));
-		layout.addView(date);
+		headerLayout.addView(date);
+
+		if(!item.isExpanded()) return headerLayout;
+
+		LinearLayout expanded = new LinearLayout(ctx);
+		expanded.setOrientation(VERTICAL);
+		expanded.setGravity(CENTER_HORIZONTAL);
+		expanded.setBackgroundColor(background);
+		expanded.addView(headerLayout);
+
+		byte[] body = item.getBody();
+		if(body == null) {
+			ProgressBar progress = new ProgressBar(ctx);
+			progress.setPadding(pad, 0, pad, pad);
+			progress.setIndeterminate(true);
+			expanded.addView(progress);
+		} else if(header.getContentType().equals("text/plain")) {
+			TextView text = new TextView(ctx);
+			text.setPadding(pad, 0, pad, pad);
+			text.setBackgroundColor(background);
+			text.setText(StringUtils.fromUtf8(body));
+			expanded.addView(text);
+		}
 
-		return layout;
+		return expanded;
 	}
-}
+}
\ No newline at end of file
diff --git a/briar-android/src/org/briarproject/android/contact/ReadPrivateMessageActivity.java b/briar-android/src/org/briarproject/android/contact/ReadPrivateMessageActivity.java
index 9cb224fcf5a7d9eceb1d28492e73ab9dc419f05d..717c682d63e76b3b0fcee5320f79281dd36ec220 100644
--- a/briar-android/src/org/briarproject/android/contact/ReadPrivateMessageActivity.java
+++ b/briar-android/src/org/briarproject/android/contact/ReadPrivateMessageActivity.java
@@ -12,7 +12,6 @@ import static org.briarproject.android.util.CommonLayoutParams.MATCH_WRAP_1;
 import static org.briarproject.android.util.CommonLayoutParams.WRAP_WRAP_1;
 import static org.briarproject.api.Author.Status.VERIFIED;
 
-import java.io.UnsupportedEncodingException;
 import java.util.concurrent.Executor;
 import java.util.logging.Logger;
 
@@ -32,6 +31,7 @@ import org.briarproject.api.db.NoSuchMessageException;
 import org.briarproject.api.lifecycle.LifecycleManager;
 import org.briarproject.api.messaging.GroupId;
 import org.briarproject.api.messaging.MessageId;
+import org.briarproject.util.StringUtils;
 
 import android.content.Intent;
 import android.content.res.Resources;
@@ -227,7 +227,7 @@ implements OnClickListener {
 					long duration = System.currentTimeMillis() - now;
 					if(LOG.isLoggable(INFO))
 						LOG.info("Loading message took " + duration + " ms");
-					final String text = new String(body, "UTF-8");
+					final String text = StringUtils.fromUtf8(body);
 					runOnUiThread(new Runnable() {
 						public void run() {
 							content.setText(text);
@@ -247,8 +247,6 @@ implements OnClickListener {
 					if(LOG.isLoggable(INFO))
 						LOG.info("Interrupted while waiting for database");
 					Thread.currentThread().interrupt();
-				} catch(UnsupportedEncodingException e) {
-					throw new RuntimeException(e);
 				}
 			}
 		});
diff --git a/briar-android/src/org/briarproject/android/contact/WritePrivateMessageActivity.java b/briar-android/src/org/briarproject/android/contact/WritePrivateMessageActivity.java
index 743cc6d31c34c0f8268ac026d9487166950ef072..4f44edd91899dc9ca1948363d943b658aeb89337 100644
--- a/briar-android/src/org/briarproject/android/contact/WritePrivateMessageActivity.java
+++ b/briar-android/src/org/briarproject/android/contact/WritePrivateMessageActivity.java
@@ -14,7 +14,6 @@ import static java.util.logging.Level.WARNING;
 import static org.briarproject.android.util.CommonLayoutParams.MATCH_WRAP;
 
 import java.io.IOException;
-import java.io.UnsupportedEncodingException;
 import java.security.GeneralSecurityException;
 import java.util.concurrent.Executor;
 import java.util.logging.Logger;
@@ -39,6 +38,7 @@ import org.briarproject.api.messaging.GroupId;
 import org.briarproject.api.messaging.Message;
 import org.briarproject.api.messaging.MessageFactory;
 import org.briarproject.api.messaging.MessageId;
+import org.briarproject.util.StringUtils;
 
 import android.content.Intent;
 import android.content.res.Resources;
@@ -194,11 +194,7 @@ implements OnClickListener {
 
 	public void onClick(View view) {
 		if(localAuthor == null) throw new IllegalStateException();
-		try {
-			createMessage(content.getText().toString().getBytes("UTF-8"));
-		} catch(UnsupportedEncodingException e) {
-			throw new RuntimeException(e);
-		}
+		createMessage(StringUtils.toUtf8(content.getText().toString()));
 		Toast.makeText(this, R.string.message_sent_toast, LENGTH_LONG).show();
 		finish();
 	}
diff --git a/briar-android/src/org/briarproject/android/groups/CreateGroupActivity.java b/briar-android/src/org/briarproject/android/groups/CreateGroupActivity.java
index ac56f116b5db0317c6d51a99109f54aaaeed5d7b..132663dc78e1cd867b385eb19ed2422c9199e8fd 100644
--- a/briar-android/src/org/briarproject/android/groups/CreateGroupActivity.java
+++ b/briar-android/src/org/briarproject/android/groups/CreateGroupActivity.java
@@ -14,7 +14,6 @@ import static org.briarproject.android.util.CommonLayoutParams.MATCH_MATCH;
 import static org.briarproject.android.util.CommonLayoutParams.WRAP_WRAP;
 import static org.briarproject.api.messaging.MessagingConstants.MAX_GROUP_NAME_LENGTH;
 
-import java.io.UnsupportedEncodingException;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.concurrent.Executor;
@@ -35,6 +34,7 @@ import org.briarproject.api.db.DbException;
 import org.briarproject.api.lifecycle.LifecycleManager;
 import org.briarproject.api.messaging.Group;
 import org.briarproject.api.messaging.GroupFactory;
+import org.briarproject.util.StringUtils;
 
 import android.content.Intent;
 import android.os.Bundle;
@@ -245,12 +245,8 @@ SelectContactsDialog.Listener {
 
 	private boolean validateName() {
 		if(nameEntry.getText().length() == 0) return false;
-		try {
-			byte[] b = nameEntry.getText().toString().getBytes("UTF-8");
-			if(b.length > MAX_GROUP_NAME_LENGTH) return false;
-		} catch(UnsupportedEncodingException e) {
-			throw new RuntimeException(e);
-		}
+		byte[] b = StringUtils.toUtf8(nameEntry.getText().toString());
+		if(b.length > MAX_GROUP_NAME_LENGTH) return false;
 		// Hide the soft keyboard
 		Object o = getSystemService(INPUT_METHOD_SERVICE);
 		((InputMethodManager) o).toggleSoftInput(HIDE_IMPLICIT_ONLY, 0);
diff --git a/briar-android/src/org/briarproject/android/groups/ReadGroupPostActivity.java b/briar-android/src/org/briarproject/android/groups/ReadGroupPostActivity.java
index 4ce3bba7c5205255eaecedd9563062f1647e23a3..41480c2bc43326e22a8c6c8d11a0aea3691c739c 100644
--- a/briar-android/src/org/briarproject/android/groups/ReadGroupPostActivity.java
+++ b/briar-android/src/org/briarproject/android/groups/ReadGroupPostActivity.java
@@ -11,7 +11,6 @@ import static org.briarproject.android.util.CommonLayoutParams.MATCH_WRAP;
 import static org.briarproject.android.util.CommonLayoutParams.MATCH_WRAP_1;
 import static org.briarproject.android.util.CommonLayoutParams.WRAP_WRAP_1;
 
-import java.io.UnsupportedEncodingException;
 import java.util.concurrent.Executor;
 import java.util.logging.Logger;
 
@@ -31,6 +30,7 @@ import org.briarproject.api.db.NoSuchMessageException;
 import org.briarproject.api.lifecycle.LifecycleManager;
 import org.briarproject.api.messaging.GroupId;
 import org.briarproject.api.messaging.MessageId;
+import org.briarproject.util.StringUtils;
 
 import android.content.Intent;
 import android.content.res.Resources;
@@ -223,7 +223,7 @@ implements OnClickListener {
 					long duration = System.currentTimeMillis() - now;
 					if(LOG.isLoggable(INFO))
 						LOG.info("Loading message took " + duration + " ms");
-					final String text = new String(body, "UTF-8");
+					final String text = StringUtils.fromUtf8(body);
 					runOnUiThread(new Runnable() {
 						public void run() {
 							content.setText(text);
@@ -243,8 +243,6 @@ implements OnClickListener {
 					if(LOG.isLoggable(INFO))
 						LOG.info("Interrupted while waiting for database");
 					Thread.currentThread().interrupt();
-				} catch(UnsupportedEncodingException e) {
-					throw new RuntimeException(e);
 				}
 			}
 		});
diff --git a/briar-android/src/org/briarproject/android/groups/WriteGroupPostActivity.java b/briar-android/src/org/briarproject/android/groups/WriteGroupPostActivity.java
index badcb0587c2caf157184465fd8a035d4fa155dfe..071195d23f86e6fef887acaf9a2e6f85c4f93713 100644
--- a/briar-android/src/org/briarproject/android/groups/WriteGroupPostActivity.java
+++ b/briar-android/src/org/briarproject/android/groups/WriteGroupPostActivity.java
@@ -15,7 +15,6 @@ import static java.util.logging.Level.WARNING;
 import static org.briarproject.android.util.CommonLayoutParams.MATCH_WRAP;
 
 import java.io.IOException;
-import java.io.UnsupportedEncodingException;
 import java.security.GeneralSecurityException;
 import java.util.Collection;
 import java.util.concurrent.Executor;
@@ -46,6 +45,7 @@ import org.briarproject.api.messaging.GroupId;
 import org.briarproject.api.messaging.Message;
 import org.briarproject.api.messaging.MessageFactory;
 import org.briarproject.api.messaging.MessageId;
+import org.briarproject.util.StringUtils;
 
 import android.content.Intent;
 import android.content.res.Resources;
@@ -269,11 +269,7 @@ implements OnItemSelectedListener, OnClickListener {
 
 	public void onClick(View view) {
 		if(group == null) throw new IllegalStateException();
-		try {
-			createMessage(content.getText().toString().getBytes("UTF-8"));
-		} catch(UnsupportedEncodingException e) {
-			throw new RuntimeException(e);
-		}
+		createMessage(StringUtils.toUtf8(content.getText().toString()));
 		Toast.makeText(this, R.string.post_sent_toast, LENGTH_LONG).show();
 		finish();
 	}
diff --git a/briar-android/src/org/briarproject/android/identity/CreateIdentityActivity.java b/briar-android/src/org/briarproject/android/identity/CreateIdentityActivity.java
index 9a5b8321145ab4eef42870fcf0e019f58ae40308..815e5e1fb232ae0b3e6cd2ad1454d4e52cf1d1e0 100644
--- a/briar-android/src/org/briarproject/android/identity/CreateIdentityActivity.java
+++ b/briar-android/src/org/briarproject/android/identity/CreateIdentityActivity.java
@@ -14,7 +14,6 @@ import static org.briarproject.android.util.CommonLayoutParams.MATCH_MATCH;
 import static org.briarproject.android.util.CommonLayoutParams.WRAP_WRAP;
 import static org.briarproject.api.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
 
-import java.io.UnsupportedEncodingException;
 import java.util.concurrent.Executor;
 import java.util.logging.Logger;
 
@@ -32,6 +31,7 @@ import org.briarproject.api.crypto.KeyPair;
 import org.briarproject.api.db.DatabaseComponent;
 import org.briarproject.api.db.DbException;
 import org.briarproject.api.lifecycle.LifecycleManager;
+import org.briarproject.util.StringUtils;
 
 import android.content.Intent;
 import android.os.Bundle;
@@ -138,12 +138,8 @@ implements OnEditorActionListener, OnClickListener {
 
 	private boolean validateNickname() {
 		if(nicknameEntry.getText().length() == 0) return false;
-		try {
-			byte[] b = nicknameEntry.getText().toString().getBytes("UTF-8");
-			if(b.length > MAX_AUTHOR_NAME_LENGTH) return false;
-		} catch(UnsupportedEncodingException e) {
-			throw new RuntimeException(e);
-		}
+		byte[] b = StringUtils.toUtf8(nicknameEntry.getText().toString());
+		if(b.length > MAX_AUTHOR_NAME_LENGTH) return false;
 		// Hide the soft keyboard
 		Object o = getSystemService(INPUT_METHOD_SERVICE);
 		((InputMethodManager) o).toggleSoftInput(HIDE_IMPLICIT_ONLY, 0);