diff --git a/briar-android/AndroidManifest.xml b/briar-android/AndroidManifest.xml
index 44027595bfa1cdb6c2e4867484687dff76cc21d9..8dcdb7163d7ee18e4a19a2b196d4175e7ebc96f4 100644
--- a/briar-android/AndroidManifest.xml
+++ b/briar-android/AndroidManifest.xml
@@ -40,5 +40,9 @@
 			android:name=".android.invitation.AddContactActivity"
 			android:label="@string/add_contact_title" >
 		</activity>
+		<activity
+			android:name=".android.messages.ConversationListActivity"
+			android:label="@string/messages_title" >
+		</activity>
 	</application>
 </manifest>
diff --git a/briar-android/res/drawable-hdpi/rating_important.png b/briar-android/res/drawable-hdpi/rating_important.png
new file mode 100644
index 0000000000000000000000000000000000000000..7c25f351188f98caa89792889ae3e27960c16da7
Binary files /dev/null and b/briar-android/res/drawable-hdpi/rating_important.png differ
diff --git a/briar-android/res/drawable-hdpi/rating_not_important.png b/briar-android/res/drawable-hdpi/rating_not_important.png
new file mode 100644
index 0000000000000000000000000000000000000000..c10325fe114dd69e75297bc427d5d1530466ff20
Binary files /dev/null and b/briar-android/res/drawable-hdpi/rating_not_important.png differ
diff --git a/briar-android/res/drawable-mdpi/rating_important.png b/briar-android/res/drawable-mdpi/rating_important.png
new file mode 100644
index 0000000000000000000000000000000000000000..7b2e1d5649aff4c8adb60e454b4bd77275dc071d
Binary files /dev/null and b/briar-android/res/drawable-mdpi/rating_important.png differ
diff --git a/briar-android/res/drawable-mdpi/rating_not_important.png b/briar-android/res/drawable-mdpi/rating_not_important.png
new file mode 100644
index 0000000000000000000000000000000000000000..392eeb00c565a513412e8cf90a208c2fe8826e2c
Binary files /dev/null and b/briar-android/res/drawable-mdpi/rating_not_important.png differ
diff --git a/briar-android/res/drawable-xhdpi/content_new_email.png b/briar-android/res/drawable-xhdpi/content_new_email.png
new file mode 100644
index 0000000000000000000000000000000000000000..674b69b08715b9978186818d728ce4133d9f1ae2
Binary files /dev/null and b/briar-android/res/drawable-xhdpi/content_new_email.png differ
diff --git a/briar-android/res/drawable-xhdpi/rating_important.png b/briar-android/res/drawable-xhdpi/rating_important.png
new file mode 100644
index 0000000000000000000000000000000000000000..da44dd82c3ab07009b3b45b638baceb90fcff514
Binary files /dev/null and b/briar-android/res/drawable-xhdpi/rating_important.png differ
diff --git a/briar-android/res/drawable-xhdpi/rating_not_important.png b/briar-android/res/drawable-xhdpi/rating_not_important.png
new file mode 100644
index 0000000000000000000000000000000000000000..7ff6c8d0e68f491e420faa9a955c9a6c19f8ea1e
Binary files /dev/null and b/briar-android/res/drawable-xhdpi/rating_not_important.png differ
diff --git a/briar-android/res/values/strings.xml b/briar-android/res/values/strings.xml
index 75b51e2dbd556eb642e19017bc30c30d06723209..79236a39603272358460928e4e20ae5da0d13a27 100644
--- a/briar-android/res/values/strings.xml
+++ b/briar-android/res/values/strings.xml
@@ -12,7 +12,7 @@
 	<string name="contact_list_title">Contacts</string>
 	<string name="contact_connected">Connected</string>
 	<string name="contact_last_connected">Last connected &lt;br /&gt; %s</string>
-	<string name="add_contact_button">Add a contact</string>
+	<string name="add_contact_button">New Contact</string>
 	<string name="add_contact_title">Add a Contact</string>
 	<string name="same_network">Briar can add contacts via Wi-Fi or Bluetooth. For security reasons, you must be face-to-face to add someone as a contact. To use Wi-Fi you must both be connected to the same network.</string>
 	<string name="wifi_not_available">Wi-Fi is not available on this device</string>
@@ -39,6 +39,7 @@
 	<string name="interfering">This could mean that someone is trying to interfere with your connection.</string>
 	<string name="contact_added">Contact added</string>
 	<string name="enter_nickname">Please enter a nickname for this contact:</string>
-	<string name="add_another_contact_button">Add another contact</string>
+	<string name="messages_title">Messages</string>
+	<string name="compose_button">New Message</string>
 	<string name="done_button">Done</string>
 </resources>
diff --git a/briar-android/src/net/sf/briar/android/HomeScreenActivity.java b/briar-android/src/net/sf/briar/android/HomeScreenActivity.java
index 65b8fd01d7be51c2b93f53f5a7fd0cbf8e29c89f..07fe3d0b8c0ea586c7247044e6d999aea23dde84 100644
--- a/briar-android/src/net/sf/briar/android/HomeScreenActivity.java
+++ b/briar-android/src/net/sf/briar/android/HomeScreenActivity.java
@@ -12,6 +12,7 @@ import net.sf.briar.R;
 import net.sf.briar.android.BriarService.BriarBinder;
 import net.sf.briar.android.BriarService.BriarServiceConnection;
 import net.sf.briar.android.contact.ContactListActivity;
+import net.sf.briar.android.messages.ConversationListActivity;
 import android.content.Intent;
 import android.os.Bundle;
 import android.os.IBinder;
@@ -78,7 +79,8 @@ public class HomeScreenActivity extends BriarActivity {
 			messagesButton.setText(R.string.messages_button);
 			messagesButton.setOnClickListener(new OnClickListener() {
 				public void onClick(View view) {
-					// FIXME: Hook this button up to an activity
+					startActivity(new Intent(HomeScreenActivity.this,
+							ConversationListActivity.class));
 				}
 			});
 			buttons.add(messagesButton);
diff --git a/briar-android/src/net/sf/briar/android/contact/ContactListActivity.java b/briar-android/src/net/sf/briar/android/contact/ContactListActivity.java
index b12ba8db66b37edf321abe476acae876e24dc2bf..53143108ea60ce1c2313bcf5b42e283177c71b53 100644
--- a/briar-android/src/net/sf/briar/android/contact/ContactListActivity.java
+++ b/briar-android/src/net/sf/briar/android/contact/ContactListActivity.java
@@ -59,26 +59,25 @@ implements OnClickListener, DatabaseListener, ConnectionListener {
 	@Override
 	public void onCreate(Bundle state) {
 		super.onCreate(null);
-		if(LOG.isLoggable(INFO)) LOG.info("Created");
 		LinearLayout layout = new LinearLayout(this);
 		layout.setLayoutParams(new LayoutParams(MATCH_PARENT, MATCH_PARENT));
 		layout.setOrientation(VERTICAL);
 		layout.setGravity(CENTER_HORIZONTAL);
 
 		adapter = new ContactListAdapter(this);
-		ListView listView = new ListView(this);
+		ListView list = new ListView(this);
 		// Give me all the width and all the unused height
-		listView.setLayoutParams(new LayoutParams(MATCH_PARENT, WRAP_CONTENT,
-				1f));
-		listView.setAdapter(adapter);
-		layout.addView(listView);
+		list.setLayoutParams(new LayoutParams(MATCH_PARENT, WRAP_CONTENT, 1f));
+		list.setAdapter(adapter);
+		layout.addView(list);
 
 		Button addContactButton = new Button(this);
-		LayoutParams lp = new LayoutParams(WRAP_CONTENT, WRAP_CONTENT);
-		addContactButton.setLayoutParams(lp);
+		addContactButton.setBackgroundResource(0);
+		addContactButton.setLayoutParams(new LayoutParams(MATCH_PARENT,
+				WRAP_CONTENT));
+		addContactButton.setCompoundDrawablesWithIntrinsicBounds(0,
+				R.drawable.social_add_person, 0, 0);
 		addContactButton.setText(R.string.add_contact_button);
-		addContactButton.setCompoundDrawablesWithIntrinsicBounds(
-				R.drawable.social_add_person, 0, 0, 0);
 		addContactButton.setOnClickListener(this);
 		layout.addView(addContactButton);
 
@@ -146,7 +145,7 @@ implements OnClickListener, DatabaseListener, ConnectionListener {
 					IBinder binder = serviceConnection.waitForBinder();
 					((BriarBinder) binder).getService().waitForStartup();
 					// Load the contacts from the database
-					final Collection<Contact> contacts = db.getContacts();
+					Collection<Contact> contacts = db.getContacts();
 					if(LOG.isLoggable(INFO))
 						LOG.info("Loaded " + contacts.size() + " contacts");
 					// Update the contact list
diff --git a/briar-android/src/net/sf/briar/android/contact/ContactListAdapter.java b/briar-android/src/net/sf/briar/android/contact/ContactListAdapter.java
index 69dcecfd8eb2c5e583a06a9aac32e271c12d6f00..1849d60ede19774a6eab6c344d97a04a568bcd9f 100644
--- a/briar-android/src/net/sf/briar/android/contact/ContactListAdapter.java
+++ b/briar-android/src/net/sf/briar/android/contact/ContactListAdapter.java
@@ -20,8 +20,8 @@ import android.widget.TextView;
 
 class ContactListAdapter extends ArrayAdapter<ContactListItem> {
 
-	ContactListAdapter(Context context) {
-		super(context, android.R.layout.simple_expandable_list_item_1,
+	ContactListAdapter(Context ctx) {
+		super(ctx, android.R.layout.simple_expandable_list_item_1,
 				new ArrayList<ContactListItem>());
 	}
 
@@ -34,14 +34,14 @@ class ContactListAdapter extends ArrayAdapter<ContactListItem> {
 		layout.setGravity(CENTER);
 
 		ImageView bulb = new ImageView(ctx);
-		if(item.getConnected()) bulb.setImageResource(R.drawable.green_bulb);
-		else bulb.setImageResource(R.drawable.grey_bulb);
 		bulb.setPadding(5, 5, 5, 5);
+		if(item.isConnected()) bulb.setImageResource(R.drawable.green_bulb);
+		else bulb.setImageResource(R.drawable.grey_bulb);
 		layout.addView(bulb);
 
 		TextView name = new TextView(ctx);
 		// Give me all the unused width
-		name.setLayoutParams(new LayoutParams(WRAP_CONTENT, WRAP_CONTENT, 1f));
+		name.setLayoutParams(new LayoutParams(WRAP_CONTENT, WRAP_CONTENT, 1));
 		name.setTextSize(18);
 		name.setText(item.getName());
 		layout.addView(name);
@@ -49,7 +49,7 @@ class ContactListAdapter extends ArrayAdapter<ContactListItem> {
 		TextView connected = new TextView(ctx);
 		connected.setTextSize(12);
 		connected.setPadding(5, 0, 5, 0);
-		if(item.getConnected()) {
+		if(item.isConnected()) {
 			connected.setText(R.string.contact_connected);
 		} else {
 			String format = ctx.getResources().getString(
diff --git a/briar-android/src/net/sf/briar/android/contact/ContactListItem.java b/briar-android/src/net/sf/briar/android/contact/ContactListItem.java
index 99d2a79fcc707dfc4b6be43eaf24ee4d86f44eac..47747e3a1bc267c2e85d4aace6af8129294f2766 100644
--- a/briar-android/src/net/sf/briar/android/contact/ContactListItem.java
+++ b/briar-android/src/net/sf/briar/android/contact/ContactListItem.java
@@ -30,7 +30,7 @@ class ContactListItem {
 		return contact.getLastConnected();
 	}
 
-	boolean getConnected() {
+	boolean isConnected() {
 		return connected;
 	}
 
@@ -40,7 +40,6 @@ class ContactListItem {
 
 	private static class ItemComparator implements Comparator<ContactListItem> {
 
-		@Override
 		public int compare(ContactListItem a, ContactListItem b) {
 			return String.CASE_INSENSITIVE_ORDER.compare(a.contact.getName(),
 					b.contact.getName());
diff --git a/briar-android/src/net/sf/briar/android/messages/ConversationListActivity.java b/briar-android/src/net/sf/briar/android/messages/ConversationListActivity.java
new file mode 100644
index 0000000000000000000000000000000000000000..a45ac9c8bfcb23608c6425a34531014a9660ed87
--- /dev/null
+++ b/briar-android/src/net/sf/briar/android/messages/ConversationListActivity.java
@@ -0,0 +1,225 @@
+package net.sf.briar.android.messages;
+
+import static android.view.Gravity.CENTER_HORIZONTAL;
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+import static android.widget.LinearLayout.VERTICAL;
+import static java.util.logging.Level.INFO;
+import static java.util.logging.Level.WARNING;
+
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Executor;
+import java.util.logging.Logger;
+
+import net.sf.briar.R;
+import net.sf.briar.android.BriarActivity;
+import net.sf.briar.android.BriarService;
+import net.sf.briar.android.BriarService.BriarBinder;
+import net.sf.briar.android.BriarService.BriarServiceConnection;
+import net.sf.briar.api.Contact;
+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.PrivateMessageHeader;
+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.messaging.Message;
+import net.sf.briar.api.messaging.MessageFactory;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.LinearLayout;
+import android.widget.LinearLayout.LayoutParams;
+import android.widget.ListView;
+
+import com.google.inject.Inject;
+
+public class ConversationListActivity extends BriarActivity
+implements OnClickListener, DatabaseListener {
+
+	private static final Logger LOG =
+			Logger.getLogger(ConversationListActivity.class.getName());
+
+	private final BriarServiceConnection serviceConnection =
+			new BriarServiceConnection();
+
+	@Inject private DatabaseComponent db;
+	@Inject @DatabaseExecutor private Executor dbExecutor;
+	@Inject private MessageFactory messageFactory;
+
+	private ArrayAdapter<ConversationListItem> adapter = null;
+
+	@Override
+	public void onCreate(Bundle state) {
+		super.onCreate(null);
+		LinearLayout layout = new LinearLayout(this);
+		layout.setLayoutParams(new LayoutParams(MATCH_PARENT, MATCH_PARENT));
+		layout.setOrientation(VERTICAL);
+		layout.setGravity(CENTER_HORIZONTAL);
+
+		adapter = new ConversationListAdapter(this);
+		ListView list = new ListView(this);
+		// Give me all the width and all the unused height
+		list.setLayoutParams(new LayoutParams(MATCH_PARENT, WRAP_CONTENT, 1f));
+		list.setAdapter(adapter);
+		layout.addView(list);
+
+		Button composeButton = new Button(this);
+		composeButton.setBackgroundResource(0);
+		composeButton.setLayoutParams(new LayoutParams(MATCH_PARENT,
+				WRAP_CONTENT));
+		composeButton.setCompoundDrawablesWithIntrinsicBounds(0,
+				R.drawable.content_new_email, 0, 0);
+		composeButton.setText(R.string.compose_button);
+		composeButton.setOnClickListener(this);
+		layout.addView(composeButton);
+
+		setContentView(layout);
+
+		// Listen for messages being added or removed
+		db.addListener(this);
+		// Bind to the service so we can wait for the DB to be opened
+		bindService(new Intent(BriarService.class.getName()),
+				serviceConnection, 0);
+		// Load the message headers from the DB
+		reloadMessageHeaders();
+
+		// Add some fake messages to the database in a background thread
+		// FIXME: Remove this
+		dbExecutor.execute(new Runnable() {
+			public void run() {
+				try {
+					// Wait for the service to be bound and started
+					IBinder binder = serviceConnection.waitForBinder();
+					((BriarBinder) binder).getService().waitForStartup();
+					if(LOG.isLoggable(INFO)) LOG.info("Service started");
+					Collection<PrivateMessageHeader> headers =
+							db.getPrivateMessageHeaders();
+					if(headers.isEmpty()) {
+						// Insert a fake contact
+						ContactId contactId = db.addContact("Carol");
+						// Insert some messages to the contact
+						Message m = messageFactory.createPrivateMessage(null,
+								"First message's subject",
+								"First message's body".getBytes("UTF-8"));
+						db.addLocalPrivateMessage(m, contactId);
+						db.setStarredFlag(m.getId(), true);
+						Thread.sleep(2000);
+						m = messageFactory.createPrivateMessage(m.getId(),
+								"Second message's subject",
+								"Second message's body".getBytes("UTF-8"));
+						db.addLocalPrivateMessage(m, contactId);
+						db.setReadFlag(m.getId(), true);
+					}
+				} catch(DbException e) {
+					if(LOG.isLoggable(WARNING))
+						LOG.log(WARNING, e.toString(), e);
+				} catch(GeneralSecurityException 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();
+				} catch(IOException e) {
+					if(LOG.isLoggable(WARNING))
+						LOG.log(WARNING, e.toString(), e);
+				}
+			}
+		});
+	}
+
+	@Override
+	public void onDestroy() {
+		super.onDestroy();
+		db.removeListener(this);
+		unbindService(serviceConnection);
+	}
+
+	public void onClick(View view) {
+		// FIXME: Hook this button up to an activity
+	}
+
+	public void eventOccurred(DatabaseEvent e) {
+		if(e instanceof MessageAddedEvent) reloadMessageHeaders();
+		else if(e instanceof MessageExpiredEvent) reloadMessageHeaders();
+	}
+
+	private void reloadMessageHeaders() {
+		dbExecutor.execute(new Runnable() {
+			public void run() {
+				try {
+					// Wait for the service to be bound and started
+					IBinder binder = serviceConnection.waitForBinder();
+					((BriarBinder) binder).getService().waitForStartup();
+					// Load the contact list from the database
+					Collection<Contact> contacts = db.getContacts();
+					if(LOG.isLoggable(INFO))
+						LOG.info("Loaded " + contacts.size() + " contacts");
+					// Load the message headers from the database
+					Collection<PrivateMessageHeader> headers =
+							db.getPrivateMessageHeaders();
+					if(LOG.isLoggable(INFO))
+						LOG.info("Loaded " + headers.size() + " headers");
+					// Update the conversation list
+					updateConversationList(contacts, headers);
+				} 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 updateConversationList(final Collection<Contact> contacts,
+			final Collection<PrivateMessageHeader> headers) {
+		runOnUiThread(new Runnable() {
+			public void run() {
+				adapter.clear();
+				for(ConversationListItem i : sortHeaders(contacts, headers))
+					adapter.add(i);
+				adapter.sort(ConversationListItem.COMPARATOR);
+			}
+		});
+	}
+
+	private List<ConversationListItem> sortHeaders(Collection<Contact> contacts,
+			Collection<PrivateMessageHeader> headers) {
+		// Group the headers into conversations, one per contact
+		Map<ContactId, List<PrivateMessageHeader>> map =
+				new HashMap<ContactId, List<PrivateMessageHeader>>();
+		for(Contact c : contacts)
+			map.put(c.getId(), new ArrayList<PrivateMessageHeader>());
+		for(PrivateMessageHeader h : headers) {
+			ContactId id = h.getContactId();
+			List<PrivateMessageHeader> conversation = map.get(id);
+			// Ignore header if the contact was added after db.getContacts()
+			if(conversation != null) conversation.add(h);
+		}
+		// Create a list item for each non-empty conversation
+		List<ConversationListItem> list = new ArrayList<ConversationListItem>();
+		for(Contact c : contacts) {
+			List<PrivateMessageHeader> conversation = map.get(c.getId());
+			if(!conversation.isEmpty())
+				list.add(new ConversationListItem(c, conversation));
+		}
+		return list;
+	}
+}
diff --git a/briar-android/src/net/sf/briar/android/messages/ConversationListAdapter.java b/briar-android/src/net/sf/briar/android/messages/ConversationListAdapter.java
new file mode 100644
index 0000000000000000000000000000000000000000..f0ef7ec79ff4f41a1b4c1bf01caae5a464813c78
--- /dev/null
+++ b/briar-android/src/net/sf/briar/android/messages/ConversationListAdapter.java
@@ -0,0 +1,75 @@
+package net.sf.briar.android.messages;
+
+import static android.graphics.Typeface.BOLD;
+import static android.view.Gravity.CENTER;
+import static android.view.Gravity.LEFT;
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+import static android.widget.LinearLayout.HORIZONTAL;
+import static android.widget.LinearLayout.VERTICAL;
+import static java.text.DateFormat.SHORT;
+
+import java.util.ArrayList;
+
+import net.sf.briar.R;
+import android.content.Context;
+import android.text.format.DateUtils;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.LinearLayout.LayoutParams;
+import android.widget.TextView;
+
+class ConversationListAdapter extends ArrayAdapter<ConversationListItem> {
+
+	ConversationListAdapter(Context ctx) {
+		super(ctx, android.R.layout.simple_expandable_list_item_1,
+				new ArrayList<ConversationListItem>());
+	}
+
+	@Override
+	public View getView(int position, View convertView, ViewGroup parent) {
+		ConversationListItem item = getItem(position);
+		Context ctx = getContext();
+		LinearLayout layout = new LinearLayout(ctx);
+		layout.setOrientation(HORIZONTAL);
+		layout.setGravity(CENTER);
+
+		ImageView star = new ImageView(ctx);
+		star.setPadding(5, 5, 5, 5);
+		if(item.getStarred())
+			star.setImageResource(R.drawable.rating_important);
+		else star.setImageResource(R.drawable.rating_not_important);
+		layout.addView(star);
+
+		LinearLayout innerLayout = new LinearLayout(ctx);
+		// Give me all the unused width
+		innerLayout.setLayoutParams(new LayoutParams(WRAP_CONTENT,
+				WRAP_CONTENT, 1));
+		innerLayout.setOrientation(VERTICAL);
+		innerLayout.setGravity(LEFT);
+		innerLayout.setPadding(0, 5, 0, 5);
+
+		TextView name = new TextView(ctx);
+		name.setTextSize(18);
+		name.setText(item.getName() + " (" + item.getLength() + ")");
+		innerLayout.addView(name);
+
+		TextView subject = new TextView(ctx);
+		subject.setTextSize(14);
+		if(!item.getRead()) subject.setTypeface(null, BOLD);
+		subject.setText(item.getSubject());
+		innerLayout.addView(subject);
+		layout.addView(innerLayout);
+
+		TextView date = new TextView(ctx);
+		date.setTextSize(14);
+		date.setPadding(5, 0, 10, 0);
+		long then = item.getTimestamp(), now = System.currentTimeMillis();
+		date.setText(DateUtils.formatSameDayTime(then, now, SHORT, SHORT));
+		layout.addView(date);
+
+		return layout;
+	}
+}
diff --git a/briar-android/src/net/sf/briar/android/messages/ConversationListItem.java b/briar-android/src/net/sf/briar/android/messages/ConversationListItem.java
new file mode 100644
index 0000000000000000000000000000000000000000..711329ec3bfdb302121854bb4411f0c1899e0513
--- /dev/null
+++ b/briar-android/src/net/sf/briar/android/messages/ConversationListItem.java
@@ -0,0 +1,81 @@
+package net.sf.briar.android.messages;
+
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+import net.sf.briar.api.Contact;
+import net.sf.briar.api.db.PrivateMessageHeader;
+
+class ConversationListItem {
+
+	static final Comparator<ConversationListItem> COMPARATOR =
+			new ItemComparator();
+
+	private static final Comparator<PrivateMessageHeader> HEADER_COMPARATOR =
+			new HeaderComparator();
+
+	private final Contact contact;
+	private final List<PrivateMessageHeader> headers;
+	private final boolean read, starred;
+
+	ConversationListItem(Contact contact, List<PrivateMessageHeader> headers) {
+		if(headers.isEmpty()) throw new IllegalArgumentException();
+		Collections.sort(headers, HEADER_COMPARATOR);
+		boolean read = false, starred = false;
+		for(PrivateMessageHeader h : headers) {
+			read &= h.getRead();
+			starred |= h.getStarred();
+		}
+		this.contact = contact;
+		this.headers = headers;
+		this.read = read;
+		this.starred = starred;
+	}
+
+	String getName() {
+		return contact.getName();
+	}
+
+	String getSubject() {
+		return headers.get(0).getSubject();
+	}
+
+	long getTimestamp() {
+		return headers.get(0).getTimestamp();
+	}
+
+	boolean getRead() {
+		return read;
+	}
+
+	boolean getStarred() {
+		return starred;
+	}
+
+	int getLength() {
+		return headers.size();
+	}
+
+	private static class HeaderComparator
+	implements Comparator<PrivateMessageHeader> {
+
+		public int compare(PrivateMessageHeader a, PrivateMessageHeader b) {
+			// The newest message comes first
+			long aTime = a.getTimestamp(), bTime = b.getTimestamp();
+			if(aTime > bTime) return -1;
+			if(aTime < bTime) return 1;
+			return 0;
+		}
+	}
+
+	private static class ItemComparator
+	implements Comparator<ConversationListItem> {
+
+		public int compare(ConversationListItem a, ConversationListItem b) {
+			// The item with the newest message comes first
+			return HEADER_COMPARATOR.compare(a.headers.get(0),
+					b.headers.get(0));
+		}
+	}
+}