From c5fa3d184108c910656f0eba1383a761cc3b57b2 Mon Sep 17 00:00:00 2001
From: akwizgran <michael@briarproject.org>
Date: Mon, 15 Apr 2013 14:44:42 +0100
Subject: [PATCH] Removed last connection time from Contact class, tightened up
 layouts.

---
 briar-android/res/values/strings.xml          |  2 +-
 .../sf/briar/android/blogs/BlogAdapter.java   |  4 +-
 .../briar/android/blogs/BlogListAdapter.java  |  1 -
 .../android/blogs/ReadBlogPostActivity.java   | 19 +++++----
 .../android/contact/ContactListActivity.java  | 17 +++++---
 .../android/contact/ContactListAdapter.java   |  8 ++--
 .../android/contact/ContactListItem.java      | 10 ++++-
 .../sf/briar/android/groups/GroupAdapter.java |  4 +-
 .../android/groups/GroupListAdapter.java      |  1 -
 .../android/groups/ReadGroupPostActivity.java | 19 +++++----
 .../android/invitation/BluetoothWidget.java   |  4 --
 .../invitation/CodesDoNotMatchView.java       |  3 +-
 .../invitation/ConfirmationCodeView.java      |  3 +-
 .../invitation/ConnectionFailedView.java      |  3 +-
 .../android/invitation/ContactAddedView.java  |  3 +-
 .../invitation/WaitForContactView.java        | 11 +++--
 .../briar/android/invitation/WifiWidget.java  |  4 --
 .../android/messages/ConversationAdapter.java |  4 +-
 .../messages/ReadPrivateMessageActivity.java  | 15 ++++---
 briar-api/src/net/sf/briar/api/Contact.java   |  9 +----
 .../sf/briar/api/db/DatabaseComponent.java    |  6 +++
 briar-core/src/net/sf/briar/db/Database.java  |  8 ++--
 .../sf/briar/db/DatabaseComponentImpl.java    | 40 ++++++++++++-------
 .../src/net/sf/briar/db/JdbcDatabase.java     | 40 +++++++------------
 .../sf/briar/db/DatabaseComponentTest.java    |  2 +-
 25 files changed, 115 insertions(+), 125 deletions(-)

diff --git a/briar-android/res/values/strings.xml b/briar-android/res/values/strings.xml
index 09fe117937..3fda7ccda5 100644
--- a/briar-android/res/values/strings.xml
+++ b/briar-android/res/values/strings.xml
@@ -12,7 +12,7 @@
 	<string name="new_identity_item">New identity\u2026</string>
 	<string name="contact_list_title">Contacts</string>
 	<string name="contact_connected">Connected</string>
-	<string name="format_contact_last_connected">Last connected &lt;br /&gt; %1$s</string>
+	<string name="format_last_connected">Last connected &lt;br /&gt; %1$s</string>
 	<string name="add_contact_title">Add a Contact</string>
 	<string name="your_nickname">Your nickname: </string>
 	<string name="wifi_not_available">Wi-Fi is NOT AVAILABLE</string>
diff --git a/briar-android/src/net/sf/briar/android/blogs/BlogAdapter.java b/briar-android/src/net/sf/briar/android/blogs/BlogAdapter.java
index 0a9529a624..3141bf4af6 100644
--- a/briar-android/src/net/sf/briar/android/blogs/BlogAdapter.java
+++ b/briar-android/src/net/sf/briar/android/blogs/BlogAdapter.java
@@ -54,7 +54,6 @@ class BlogAdapter extends ArrayAdapter<GroupMessageHeader> {
 		authorLayout.setGravity(CENTER_VERTICAL);
 
 		ImageView thumb = new ImageView(ctx);
-		thumb.setPadding(10, 10, 10, 10);
 		Rating rating = item.getRating();
 		if(rating == GOOD) thumb.setImageResource(R.drawable.rating_good);
 		else if(rating == BAD) thumb.setImageResource(R.drawable.rating_bad);
@@ -66,7 +65,7 @@ class BlogAdapter extends ArrayAdapter<GroupMessageHeader> {
 		name.setLayoutParams(WRAP_WRAP_1);
 		name.setTextSize(18);
 		name.setMaxLines(1);
-		name.setPadding(0, 10, 10, 10);
+		name.setPadding(10, 10, 10, 10);
 		Author author = item.getAuthor();
 		if(author == null) {
 			name.setTextColor(res.getColor(R.color.anonymous_author));
@@ -90,7 +89,6 @@ class BlogAdapter extends ArrayAdapter<GroupMessageHeader> {
 			LinearLayout attachmentLayout = new LinearLayout(ctx);
 			attachmentLayout.setOrientation(HORIZONTAL);
 			ImageView attachment = new ImageView(ctx);
-			attachment.setPadding(10, 0, 10, 10);
 			attachment.setImageResource(R.drawable.content_attachment);
 			attachmentLayout.addView(attachment);
 			attachmentLayout.addView(new HorizontalSpace(ctx));
diff --git a/briar-android/src/net/sf/briar/android/blogs/BlogListAdapter.java b/briar-android/src/net/sf/briar/android/blogs/BlogListAdapter.java
index af935b7fa3..7d177e9813 100644
--- a/briar-android/src/net/sf/briar/android/blogs/BlogListAdapter.java
+++ b/briar-android/src/net/sf/briar/android/blogs/BlogListAdapter.java
@@ -78,7 +78,6 @@ implements OnItemClickListener {
 				LinearLayout attachmentLayout = new LinearLayout(ctx);
 				attachmentLayout.setOrientation(HORIZONTAL);
 				ImageView attachment = new ImageView(ctx);
-				attachment.setPadding(10, 0, 10, 10);
 				attachment.setImageResource(R.drawable.content_attachment);
 				attachmentLayout.addView(attachment);
 				attachmentLayout.addView(new HorizontalSpace(ctx));
diff --git a/briar-android/src/net/sf/briar/android/blogs/ReadBlogPostActivity.java b/briar-android/src/net/sf/briar/android/blogs/ReadBlogPostActivity.java
index f1cf62ac8f..07c24e7454 100644
--- a/briar-android/src/net/sf/briar/android/blogs/ReadBlogPostActivity.java
+++ b/briar-android/src/net/sf/briar/android/blogs/ReadBlogPostActivity.java
@@ -123,25 +123,24 @@ implements OnClickListener {
 		header.setGravity(CENTER_VERTICAL);
 
 		thumb = new ImageView(this);
-		thumb.setPadding(10, 10, 10, 10);
 		if(rating == GOOD) thumb.setImageResource(R.drawable.rating_good);
 		else if(rating == BAD) thumb.setImageResource(R.drawable.rating_bad);
 		else thumb.setImageResource(R.drawable.rating_unrated);
 		header.addView(thumb);
 
-		TextView author = new TextView(this);
+		TextView name = new TextView(this);
 		// Give me all the unused width
-		author.setLayoutParams(WRAP_WRAP_1);
-		author.setTextSize(18);
-		author.setMaxLines(1);
-		author.setPadding(0, 10, 10, 10);
+		name.setLayoutParams(WRAP_WRAP_1);
+		name.setTextSize(18);
+		name.setMaxLines(1);
+		name.setPadding(10, 10, 10, 10);
 		if(authorName == null) {
-			author.setTextColor(res.getColor(R.color.anonymous_author));
-			author.setText(R.string.anonymous);
+			name.setTextColor(res.getColor(R.color.anonymous_author));
+			name.setText(R.string.anonymous);
 		} else {
-			author.setText(authorName);
+			name.setText(authorName);
 		}
-		header.addView(author);
+		header.addView(name);
 
 		TextView date = new TextView(this);
 		date.setTextSize(14);
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 b5f83046a1..b6efc8ea78 100644
--- a/briar-android/src/net/sf/briar/android/contact/ContactListActivity.java
+++ b/briar-android/src/net/sf/briar/android/contact/ContactListActivity.java
@@ -12,6 +12,7 @@ import static net.sf.briar.android.widgets.CommonLayoutParams.MATCH_WRAP_1;
 
 import java.util.Collection;
 import java.util.Comparator;
+import java.util.Map;
 import java.util.concurrent.Executor;
 import java.util.logging.Logger;
 
@@ -114,10 +115,11 @@ implements OnClickListener, DatabaseListener, ConnectionListener {
 					serviceConnection.waitForStartup();
 					long now = System.currentTimeMillis();
 					Collection<Contact> contacts = db.getContacts();
+					Map<ContactId, Long> times = db.getLastConnected();
 					long duration = System.currentTimeMillis() - now;
 					if(LOG.isLoggable(INFO))
 						LOG.info("Load took " + duration + " ms");
-					displayContacts(contacts);
+					displayContacts(contacts, times);
 				} catch(DbException e) {
 					if(LOG.isLoggable(WARNING))
 						LOG.log(WARNING, e.toString(), e);
@@ -130,13 +132,16 @@ implements OnClickListener, DatabaseListener, ConnectionListener {
 		});
 	}
 
-	private void displayContacts(final Collection<Contact> contacts) {
+	private void displayContacts(final Collection<Contact> contacts,
+			final Map<ContactId, Long> times) {
 		runOnUiThread(new Runnable() {
 			public void run() {
 				adapter.clear();
 				for(Contact c : contacts) {
-					boolean conn = connectionRegistry.isConnected(c.getId());
-					adapter.add(new ContactListItem(c, conn));
+					boolean now = connectionRegistry.isConnected(c.getId());
+					Long last = times.get(c.getId());
+					if(last != null)
+						adapter.add(new ContactListItem(c, now, last));
 				}
 				adapter.sort(ContactComparator.INSTANCE);
 				adapter.notifyDataSetChanged();
@@ -181,8 +186,10 @@ implements OnClickListener, DatabaseListener, ConnectionListener {
 				for(int i = 0; i < count; i++) {
 					ContactListItem item = adapter.getItem(i);
 					if(item.getContactId().equals(c)) {
+						if(LOG.isLoggable(INFO))
+							LOG.info("Updating connection time");
 						item.setConnected(connected);
-						// FIXME: Item is not redrawn
+						item.setLastConnected(System.currentTimeMillis());
 						list.invalidate();
 						return;
 					}
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 8e7a60d24f..52763e97cb 100644
--- a/briar-android/src/net/sf/briar/android/contact/ContactListAdapter.java
+++ b/briar-android/src/net/sf/briar/android/contact/ContactListAdapter.java
@@ -8,6 +8,7 @@ import java.util.ArrayList;
 
 import net.sf.briar.R;
 import android.content.Context;
+import android.content.res.Resources;
 import android.text.Html;
 import android.text.format.DateUtils;
 import android.view.View;
@@ -36,7 +37,6 @@ implements OnItemClickListener {
 		layout.setGravity(CENTER_VERTICAL);
 
 		ImageView bulb = new ImageView(ctx);
-		bulb.setPadding(10, 10, 10, 10);
 		if(item.isConnected())
 			bulb.setImageResource(R.drawable.contact_connected);
 		else bulb.setImageResource(R.drawable.contact_disconnected);
@@ -47,7 +47,7 @@ implements OnItemClickListener {
 		name.setLayoutParams(WRAP_WRAP_1);
 		name.setTextSize(18);
 		name.setMaxLines(1);
-		name.setPadding(0, 10, 10, 10);
+		name.setPadding(10, 10, 10, 10);
 		name.setText(item.getContactName());
 		layout.addView(name);
 
@@ -57,8 +57,8 @@ implements OnItemClickListener {
 		if(item.isConnected()) {
 			connected.setText(R.string.contact_connected);
 		} else {
-			String format = ctx.getResources().getString(
-					R.string.format_contact_last_connected);
+			Resources res = ctx.getResources();
+			String format = res.getString(R.string.format_last_connected);
 			long then = item.getLastConnected();
 			CharSequence ago = DateUtils.getRelativeTimeSpanString(then);
 			connected.setText(Html.fromHtml(String.format(format, ago)));
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 de867e7dca..72b9d17e67 100644
--- a/briar-android/src/net/sf/briar/android/contact/ContactListItem.java
+++ b/briar-android/src/net/sf/briar/android/contact/ContactListItem.java
@@ -8,10 +8,12 @@ class ContactListItem {
 
 	private final Contact contact;
 	private boolean connected;
+	private long lastConnected;
 
-	ContactListItem(Contact contact, boolean connected) {
+	ContactListItem(Contact contact, boolean connected, long lastConnected) {
 		this.contact = contact;
 		this.connected = connected;
+		this.lastConnected = lastConnected;
 	}
 
 	ContactId getContactId() {
@@ -23,7 +25,11 @@ class ContactListItem {
 	}
 
 	long getLastConnected() {
-		return contact.getLastConnected();
+		return lastConnected;
+	}
+
+	void setLastConnected(long lastConnected) {
+		this.lastConnected = lastConnected;
 	}
 
 	boolean isConnected() {
diff --git a/briar-android/src/net/sf/briar/android/groups/GroupAdapter.java b/briar-android/src/net/sf/briar/android/groups/GroupAdapter.java
index 8e13a48d67..bf3ca47397 100644
--- a/briar-android/src/net/sf/briar/android/groups/GroupAdapter.java
+++ b/briar-android/src/net/sf/briar/android/groups/GroupAdapter.java
@@ -54,7 +54,6 @@ class GroupAdapter extends ArrayAdapter<GroupMessageHeader> {
 		authorLayout.setGravity(CENTER_VERTICAL);
 
 		ImageView thumb = new ImageView(ctx);
-		thumb.setPadding(10, 10, 10, 10);
 		Rating rating = item.getRating();
 		if(rating == GOOD) thumb.setImageResource(R.drawable.rating_good);
 		else if(rating == BAD) thumb.setImageResource(R.drawable.rating_bad);
@@ -66,7 +65,7 @@ class GroupAdapter extends ArrayAdapter<GroupMessageHeader> {
 		name.setLayoutParams(WRAP_WRAP_1);
 		name.setTextSize(18);
 		name.setMaxLines(1);
-		name.setPadding(0, 10, 10, 10);
+		name.setPadding(10, 10, 10, 10);
 		Author author = item.getAuthor();
 		if(author == null) {
 			name.setTextColor(res.getColor(R.color.anonymous_author));
@@ -90,7 +89,6 @@ class GroupAdapter extends ArrayAdapter<GroupMessageHeader> {
 			LinearLayout attachmentLayout = new LinearLayout(ctx);
 			attachmentLayout.setOrientation(HORIZONTAL);
 			ImageView attachment = new ImageView(ctx);
-			attachment.setPadding(10, 0, 10, 10);
 			attachment.setImageResource(R.drawable.content_attachment);
 			attachmentLayout.addView(attachment);
 			attachmentLayout.addView(new HorizontalSpace(ctx));
diff --git a/briar-android/src/net/sf/briar/android/groups/GroupListAdapter.java b/briar-android/src/net/sf/briar/android/groups/GroupListAdapter.java
index 0d6478856f..56b9ef3691 100644
--- a/briar-android/src/net/sf/briar/android/groups/GroupListAdapter.java
+++ b/briar-android/src/net/sf/briar/android/groups/GroupListAdapter.java
@@ -78,7 +78,6 @@ implements OnItemClickListener {
 				LinearLayout attachmentLayout = new LinearLayout(ctx);
 				attachmentLayout.setOrientation(HORIZONTAL);
 				ImageView attachment = new ImageView(ctx);
-				attachment.setPadding(10, 0, 10, 10);
 				attachment.setImageResource(R.drawable.content_attachment);
 				attachmentLayout.addView(attachment);
 				attachmentLayout.addView(new HorizontalSpace(ctx));
diff --git a/briar-android/src/net/sf/briar/android/groups/ReadGroupPostActivity.java b/briar-android/src/net/sf/briar/android/groups/ReadGroupPostActivity.java
index de801ccb53..42c45bbe52 100644
--- a/briar-android/src/net/sf/briar/android/groups/ReadGroupPostActivity.java
+++ b/briar-android/src/net/sf/briar/android/groups/ReadGroupPostActivity.java
@@ -130,25 +130,24 @@ implements OnClickListener {
 		header.setGravity(CENTER_VERTICAL);
 
 		thumb = new ImageView(this);
-		thumb.setPadding(10, 10, 10, 10);
 		if(rating == GOOD) thumb.setImageResource(R.drawable.rating_good);
 		else if(rating == BAD) thumb.setImageResource(R.drawable.rating_bad);
 		else thumb.setImageResource(R.drawable.rating_unrated);
 		header.addView(thumb);
 
-		TextView author = new TextView(this);
+		TextView name = new TextView(this);
 		// Give me all the unused width
-		author.setLayoutParams(WRAP_WRAP_1);
-		author.setTextSize(18);
-		author.setMaxLines(1);
-		author.setPadding(0, 10, 10, 10);
+		name.setLayoutParams(WRAP_WRAP_1);
+		name.setTextSize(18);
+		name.setMaxLines(1);
+		name.setPadding(10, 10, 10, 10);
 		if(authorName == null) {
-			author.setTextColor(res.getColor(R.color.anonymous_author));
-			author.setText(R.string.anonymous);
+			name.setTextColor(res.getColor(R.color.anonymous_author));
+			name.setText(R.string.anonymous);
 		} else {
-			author.setText(authorName);
+			name.setText(authorName);
 		}
-		header.addView(author);
+		header.addView(name);
 
 		TextView date = new TextView(this);
 		date.setTextSize(14);
diff --git a/briar-android/src/net/sf/briar/android/invitation/BluetoothWidget.java b/briar-android/src/net/sf/briar/android/invitation/BluetoothWidget.java
index 861e7f230c..0b72d68715 100644
--- a/briar-android/src/net/sf/briar/android/invitation/BluetoothWidget.java
+++ b/briar-android/src/net/sf/briar/android/invitation/BluetoothWidget.java
@@ -42,7 +42,6 @@ public class BluetoothWidget extends LinearLayout implements OnClickListener {
 			listener.bluetoothStateChanged(false);
 			ImageView warning = new ImageView(ctx);
 			warning.setImageResource(R.drawable.alerts_and_states_warning);
-			warning.setPadding(10, 10, 10, 10);
 			addView(warning);
 			status.setText(R.string.bluetooth_not_available);
 			addView(status);
@@ -50,7 +49,6 @@ public class BluetoothWidget extends LinearLayout implements OnClickListener {
 			listener.bluetoothStateChanged(true);
 			ImageView ok = new ImageView(ctx);
 			ok.setImageResource(R.drawable.navigation_accept);
-			ok.setPadding(10, 10, 10, 10);
 			addView(ok);
 			status.setText(R.string.bluetooth_enabled);
 			addView(status);
@@ -62,7 +60,6 @@ public class BluetoothWidget extends LinearLayout implements OnClickListener {
 			listener.bluetoothStateChanged(true);
 			ImageView warning = new ImageView(ctx);
 			warning.setImageResource(R.drawable.alerts_and_states_warning);
-			warning.setPadding(10, 10, 10, 10);
 			addView(warning);
 			status.setText(R.string.bluetooth_not_discoverable);
 			addView(status);
@@ -74,7 +71,6 @@ public class BluetoothWidget extends LinearLayout implements OnClickListener {
 			listener.bluetoothStateChanged(false);
 			ImageView warning = new ImageView(ctx);
 			warning.setImageResource(R.drawable.alerts_and_states_warning);
-			warning.setPadding(10, 10, 10, 10);
 			addView(warning);
 			status.setText(R.string.bluetooth_disabled);
 			addView(status);
diff --git a/briar-android/src/net/sf/briar/android/invitation/CodesDoNotMatchView.java b/briar-android/src/net/sf/briar/android/invitation/CodesDoNotMatchView.java
index f74854b7a1..c3e1ffb4a0 100644
--- a/briar-android/src/net/sf/briar/android/invitation/CodesDoNotMatchView.java
+++ b/briar-android/src/net/sf/briar/android/invitation/CodesDoNotMatchView.java
@@ -26,13 +26,12 @@ implements OnClickListener {
 		innerLayout.setGravity(CENTER);
 
 		ImageView icon = new ImageView(ctx);
-		icon.setPadding(10, 10, 10, 10);
 		icon.setImageResource(R.drawable.alerts_and_states_error);
 		innerLayout.addView(icon);
 
 		TextView failed = new TextView(ctx);
 		failed.setTextSize(22);
-		failed.setPadding(0, 10, 10, 10);
+		failed.setPadding(10, 10, 10, 10);
 		failed.setText(R.string.codes_do_not_match);
 		innerLayout.addView(failed);
 		addView(innerLayout);
diff --git a/briar-android/src/net/sf/briar/android/invitation/ConfirmationCodeView.java b/briar-android/src/net/sf/briar/android/invitation/ConfirmationCodeView.java
index 2db6a5dbee..6d000b32f3 100644
--- a/briar-android/src/net/sf/briar/android/invitation/ConfirmationCodeView.java
+++ b/briar-android/src/net/sf/briar/android/invitation/ConfirmationCodeView.java
@@ -24,13 +24,12 @@ implements CodeEntryListener {
 		innerLayout.setGravity(CENTER);
 
 		ImageView icon = new ImageView(ctx);
-		icon.setPadding(10, 10, 10, 10);
 		icon.setImageResource(R.drawable.navigation_accept);
 		innerLayout.addView(icon);
 
 		TextView connected = new TextView(ctx);
 		connected.setTextSize(22);
-		connected.setPadding(0, 10, 10, 10);
+		connected.setPadding(10, 10, 10, 10);
 		connected.setText(R.string.connected_to_contact);
 		innerLayout.addView(connected);
 		addView(innerLayout);
diff --git a/briar-android/src/net/sf/briar/android/invitation/ConnectionFailedView.java b/briar-android/src/net/sf/briar/android/invitation/ConnectionFailedView.java
index 9eb401636c..6367173dd4 100644
--- a/briar-android/src/net/sf/briar/android/invitation/ConnectionFailedView.java
+++ b/briar-android/src/net/sf/briar/android/invitation/ConnectionFailedView.java
@@ -28,13 +28,12 @@ implements WifiStateListener, BluetoothStateListener, OnClickListener {
 		innerLayout.setGravity(CENTER);
 
 		ImageView icon = new ImageView(ctx);
-		icon.setPadding(10, 10, 10, 10);
 		icon.setImageResource(R.drawable.alerts_and_states_error);
 		innerLayout.addView(icon);
 
 		TextView failed = new TextView(ctx);
 		failed.setTextSize(22);
-		failed.setPadding(0, 10, 10, 10);
+		failed.setPadding(10, 10, 10, 10);
 		failed.setText(R.string.connection_failed);
 		innerLayout.addView(failed);
 		addView(innerLayout);
diff --git a/briar-android/src/net/sf/briar/android/invitation/ContactAddedView.java b/briar-android/src/net/sf/briar/android/invitation/ContactAddedView.java
index 2cafa41eff..09a74dabe5 100644
--- a/briar-android/src/net/sf/briar/android/invitation/ContactAddedView.java
+++ b/briar-android/src/net/sf/briar/android/invitation/ContactAddedView.java
@@ -26,13 +26,12 @@ implements OnClickListener {
 		innerLayout.setGravity(CENTER);
 
 		ImageView icon = new ImageView(ctx);
-		icon.setPadding(10, 10, 10, 10);
 		icon.setImageResource(R.drawable.navigation_accept);
 		innerLayout.addView(icon);
 
 		TextView added = new TextView(ctx);
 		added.setTextSize(22);
-		added.setPadding(0, 10, 10, 10);
+		added.setPadding(10, 10, 10, 10);
 		added.setText(R.string.contact_added);
 		innerLayout.addView(added);
 		addView(innerLayout);
diff --git a/briar-android/src/net/sf/briar/android/invitation/WaitForContactView.java b/briar-android/src/net/sf/briar/android/invitation/WaitForContactView.java
index 6079150719..c4df2de231 100644
--- a/briar-android/src/net/sf/briar/android/invitation/WaitForContactView.java
+++ b/briar-android/src/net/sf/briar/android/invitation/WaitForContactView.java
@@ -23,15 +23,14 @@ public class WaitForContactView extends AddContactView {
 		innerLayout.setGravity(CENTER);
 
 		ImageView icon = new ImageView(ctx);
-		icon.setPadding(10, 10, 10, 10);
 		icon.setImageResource(R.drawable.navigation_accept);
 		innerLayout.addView(icon);
 
-		TextView failed = new TextView(ctx);
-		failed.setTextSize(22);
-		failed.setPadding(0, 10, 10, 10);
-		failed.setText(R.string.connected_to_contact);
-		innerLayout.addView(failed);
+		TextView connected = new TextView(ctx);
+		connected.setTextSize(22);
+		connected.setPadding(10, 10, 10, 10);
+		connected.setText(R.string.connected_to_contact);
+		innerLayout.addView(connected);
 		addView(innerLayout);
 
 		TextView yourCode = new TextView(ctx);
diff --git a/briar-android/src/net/sf/briar/android/invitation/WifiWidget.java b/briar-android/src/net/sf/briar/android/invitation/WifiWidget.java
index 0f2820e9da..82b652386a 100644
--- a/briar-android/src/net/sf/briar/android/invitation/WifiWidget.java
+++ b/briar-android/src/net/sf/briar/android/invitation/WifiWidget.java
@@ -43,7 +43,6 @@ public class WifiWidget extends LinearLayout implements OnClickListener {
 			wifiStateChanged(null);
 			ImageView warning = new ImageView(ctx);
 			warning.setImageResource(R.drawable.alerts_and_states_warning);
-			warning.setPadding(10, 10, 10, 10);
 			addView(warning);
 			status.setText(R.string.wifi_not_available);
 			addView(status);
@@ -55,7 +54,6 @@ public class WifiWidget extends LinearLayout implements OnClickListener {
 				wifiStateChanged(null);
 				ImageView warning = new ImageView(ctx);
 				warning.setImageResource(R.drawable.alerts_and_states_warning);
-				warning.setPadding(10, 10, 10, 10);
 				addView(warning);
 				status.setText(R.string.wifi_disconnected);
 				addView(status);
@@ -67,7 +65,6 @@ public class WifiWidget extends LinearLayout implements OnClickListener {
 				wifiStateChanged(networkName);
 				ImageView ok = new ImageView(ctx);
 				ok.setImageResource(R.drawable.navigation_accept);
-				ok.setPadding(10, 10, 10, 10);
 				addView(ok);
 				String format = getResources().getString(
 						R.string.format_wifi_connected);
@@ -82,7 +79,6 @@ public class WifiWidget extends LinearLayout implements OnClickListener {
 			wifiStateChanged(null);
 			ImageView warning = new ImageView(ctx);
 			warning.setImageResource(R.drawable.alerts_and_states_warning);
-			warning.setPadding(10, 10, 10, 10);
 			addView(warning);
 			status.setText(R.string.wifi_disabled);
 			addView(status);
diff --git a/briar-android/src/net/sf/briar/android/messages/ConversationAdapter.java b/briar-android/src/net/sf/briar/android/messages/ConversationAdapter.java
index efbce219bc..867593d40b 100644
--- a/briar-android/src/net/sf/briar/android/messages/ConversationAdapter.java
+++ b/briar-android/src/net/sf/briar/android/messages/ConversationAdapter.java
@@ -54,7 +54,6 @@ class ConversationAdapter extends ArrayAdapter<PrivateMessageHeader> {
 		authorLayout.setGravity(CENTER_VERTICAL);
 
 		ImageView thumb = new ImageView(ctx);
-		thumb.setPadding(10, 10, 10, 10);
 		Rating rating = item.getRating();
 		if(rating == GOOD) thumb.setImageResource(R.drawable.rating_good);
 		else if(rating == BAD) thumb.setImageResource(R.drawable.rating_bad);
@@ -66,7 +65,7 @@ class ConversationAdapter extends ArrayAdapter<PrivateMessageHeader> {
 		name.setLayoutParams(WRAP_WRAP_1);
 		name.setTextSize(18);
 		name.setMaxLines(1);
-		name.setPadding(0, 10, 10, 10);
+		name.setPadding(10, 10, 10, 10);
 		name.setText(item.getAuthor().getName());
 		authorLayout.addView(name);
 		innerLayout.addView(authorLayout);
@@ -84,7 +83,6 @@ class ConversationAdapter extends ArrayAdapter<PrivateMessageHeader> {
 			LinearLayout attachmentLayout = new LinearLayout(ctx);
 			attachmentLayout.setOrientation(HORIZONTAL);
 			ImageView attachment = new ImageView(ctx);
-			attachment.setPadding(10, 0, 10, 10);
 			attachment.setImageResource(R.drawable.content_attachment);
 			attachmentLayout.addView(attachment);
 			attachmentLayout.addView(new HorizontalSpace(ctx));
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 f90e9e57a2..b50870f38f 100644
--- a/briar-android/src/net/sf/briar/android/messages/ReadPrivateMessageActivity.java
+++ b/briar-android/src/net/sf/briar/android/messages/ReadPrivateMessageActivity.java
@@ -122,20 +122,19 @@ implements OnClickListener {
 		header.setGravity(CENTER_VERTICAL);
 
 		ImageView thumb = new ImageView(this);
-		thumb.setPadding(10, 10, 10, 10);
 		if(rating == GOOD) thumb.setImageResource(R.drawable.rating_good);
 		else if(rating == BAD) thumb.setImageResource(R.drawable.rating_bad);
 		else thumb.setImageResource(R.drawable.rating_unrated);
 		header.addView(thumb);
 
-		TextView author = new TextView(this);
+		TextView name = new TextView(this);
 		// Give me all the unused width
-		author.setLayoutParams(WRAP_WRAP_1);
-		author.setTextSize(18);
-		author.setMaxLines(1);
-		author.setPadding(0, 10, 10, 10);
-		author.setText(authorName);
-		header.addView(author);
+		name.setLayoutParams(WRAP_WRAP_1);
+		name.setTextSize(18);
+		name.setMaxLines(1);
+		name.setPadding(10, 10, 10, 10);
+		name.setText(authorName);
+		header.addView(name);
 
 		TextView date = new TextView(this);
 		date.setTextSize(14);
diff --git a/briar-api/src/net/sf/briar/api/Contact.java b/briar-api/src/net/sf/briar/api/Contact.java
index 4d9f558d6d..99a3d3edac 100644
--- a/briar-api/src/net/sf/briar/api/Contact.java
+++ b/briar-api/src/net/sf/briar/api/Contact.java
@@ -5,14 +5,11 @@ 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, AuthorId localAuthorId,
-			long lastConnected) {
+	public Contact(ContactId id, Author author, AuthorId localAuthorId) {
 		this.id = id;
 		this.author = author;
 		this.localAuthorId = localAuthorId;
-		this.lastConnected = lastConnected;
 	}
 
 	public ContactId getId() {
@@ -27,10 +24,6 @@ public class Contact {
 		return localAuthorId;
 	}
 
-	public long getLastConnected() {
-		return lastConnected;
-	}
-
 	@Override
 	public int hashCode() {
 		return id.hashCode();
diff --git a/briar-api/src/net/sf/briar/api/db/DatabaseComponent.java b/briar-api/src/net/sf/briar/api/db/DatabaseComponent.java
index bcd11a7e67..96c48cc7a9 100644
--- a/briar-api/src/net/sf/briar/api/db/DatabaseComponent.java
+++ b/briar-api/src/net/sf/briar/api/db/DatabaseComponent.java
@@ -184,6 +184,12 @@ public interface DatabaseComponent {
 	Collection<GroupMessageHeader> getGroupMessageHeaders(GroupId g)
 			throws DbException;
 
+	/**
+	 * Returns the time at which a connection to each contact was last opened
+	 * or closed.
+	 */
+	Map<ContactId, Long> getLastConnected() throws DbException;
+
 	/** Returns the pseudonym with the given ID. */
 	LocalAuthor getLocalAuthor(AuthorId a) throws DbException;
 
diff --git a/briar-core/src/net/sf/briar/db/Database.java b/briar-core/src/net/sf/briar/db/Database.java
index 6d24c05584..3af45362f3 100644
--- a/briar-core/src/net/sf/briar/db/Database.java
+++ b/briar-core/src/net/sf/briar/db/Database.java
@@ -237,7 +237,7 @@ interface Database<T> {
 	/**
 	 * Returns the contact with the given ID.
 	 * <p>
-	 * Locking: contact read, window read.
+	 * Locking: contact read.
 	 */
 	Contact getContact(T txn, ContactId c) throws DbException;
 
@@ -292,12 +292,12 @@ interface Database<T> {
 	MessageId getGroupMessageParent(T txn, MessageId m) throws DbException;
 
 	/**
-	 * Returns the time at which a connection to the given contact was last
-	 * made.
+	 * Returns the time at which a connection to each contact was last opened
+	 * or closed.
 	 * <p>
 	 * Locking: window read.
 	 */
-	long getLastConnected(T txn, ContactId c) throws DbException;
+	Map<ContactId, Long> getLastConnected(T txn) throws DbException;
 
 	/**
 	 * Returns the pseudonym with the given ID.
diff --git a/briar-core/src/net/sf/briar/db/DatabaseComponentImpl.java b/briar-core/src/net/sf/briar/db/DatabaseComponentImpl.java
index 3432df3801..70aa0adc91 100644
--- a/briar-core/src/net/sf/briar/db/DatabaseComponentImpl.java
+++ b/briar-core/src/net/sf/briar/db/DatabaseComponentImpl.java
@@ -926,21 +926,16 @@ DatabaseCleaner.Callback {
 	public Contact getContact(ContactId c) throws DbException {
 		contactLock.readLock().lock();
 		try {
-			windowLock.readLock().lock();
+			T txn = db.startTransaction();
 			try {
-				T txn = db.startTransaction();
-				try {
-					if(!db.containsContact(txn, c))
-						throw new NoSuchContactException();
-					Contact contact = db.getContact(txn, c);
-					db.commitTransaction(txn);
-					return contact;
-				} catch(DbException e) {
-					db.abortTransaction(txn);
-					throw e;
-				}
-			} finally {
-				windowLock.readLock().unlock();
+				if(!db.containsContact(txn, c))
+					throw new NoSuchContactException();
+				Contact contact = db.getContact(txn, c);
+				db.commitTransaction(txn);
+				return contact;
+			} catch(DbException e) {
+				db.abortTransaction(txn);
+				throw e;
 			}
 		} finally {
 			contactLock.readLock().unlock();
@@ -1019,6 +1014,23 @@ DatabaseCleaner.Callback {
 		}
 	}
 
+	public Map<ContactId, Long> getLastConnected() throws DbException {
+		windowLock.readLock().lock();
+		try {
+			T txn = db.startTransaction();
+			try {
+				Map<ContactId, Long> times = db.getLastConnected(txn);
+				db.commitTransaction(txn);
+				return times;
+			} catch(DbException e) {
+				db.abortTransaction(txn);
+				throw e;
+			}
+		} finally {
+			windowLock.readLock().unlock();
+		}
+	}
+
 	public LocalAuthor getLocalAuthor(AuthorId a) throws DbException {
 		identityLock.readLock().lock();
 		try {
diff --git a/briar-core/src/net/sf/briar/db/JdbcDatabase.java b/briar-core/src/net/sf/briar/db/JdbcDatabase.java
index 4bfaa978f3..742cb6cdb1 100644
--- a/briar-core/src/net/sf/briar/db/JdbcDatabase.java
+++ b/briar-core/src/net/sf/briar/db/JdbcDatabase.java
@@ -1200,12 +1200,9 @@ abstract class JdbcDatabase implements Database<Connection> {
 		PreparedStatement ps = null;
 		ResultSet rs = null;
 		try {
-			String sql = "SELECT authorId, name, publicKey, localAuthorId,"
-					+ " lastConnected"
-					+ " FROM contacts AS c"
-					+ " JOIN connectionTimes AS ct"
-					+ " ON c.contactId = ct.contactId"
-					+ " WHERE c.contactId = ?";
+			String sql = "SELECT authorId, name, publicKey, localAuthorId"
+					+ " FROM contacts"
+					+ " WHERE contactId = ?";
 			ps = txn.prepareStatement(sql);
 			ps.setInt(1, c.getInt());
 			rs = ps.executeQuery();
@@ -1214,11 +1211,10 @@ abstract class JdbcDatabase implements Database<Connection> {
 			String name = rs.getString(2);
 			byte[] publicKey = rs.getBytes(3);
 			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, localAuthorId, lastConnected);
+			return new Contact(c, author, localAuthorId);
 		} catch(SQLException e) {
 			tryToClose(rs);
 			tryToClose(ps);
@@ -1251,11 +1247,9 @@ abstract class JdbcDatabase implements Database<Connection> {
 		PreparedStatement ps = null;
 		ResultSet rs = null;
 		try {
-			String sql = "SELECT c.contactId, authorId, name, publicKey,"
-					+ " localAuthorId, lastConnected"
-					+ " FROM contacts AS c"
-					+ " JOIN connectionTimes AS ct"
-					+ " ON c.contactId = ct.contactId";
+			String sql = "SELECT contactId, authorId, name, publicKey,"
+					+ " localAuthorId"
+					+ " FROM contacts";
 			ps = txn.prepareStatement(sql);
 			rs = ps.executeQuery();
 			List<Contact> contacts = new ArrayList<Contact>();
@@ -1264,11 +1258,9 @@ abstract class JdbcDatabase implements Database<Connection> {
 				AuthorId authorId = new AuthorId(rs.getBytes(2));
 				String name = rs.getString(3);
 				byte[] publicKey = rs.getBytes(4);
-				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, localAuthorId,
-						lastConnected));
+				AuthorId localAuthorId = new AuthorId(rs.getBytes(5));
+				contacts.add(new Contact(contactId, author, localAuthorId));
 			}
 			rs.close();
 			ps.close();
@@ -1409,22 +1401,20 @@ abstract class JdbcDatabase implements Database<Connection> {
 		}
 	}
 
-	public long getLastConnected(Connection txn, ContactId c)
+	public Map<ContactId, Long> getLastConnected(Connection txn)
 			throws DbException {
 		PreparedStatement ps = null;
 		ResultSet rs = null;
 		try {
-			String sql = "SELECT lastConnected FROM connectionTimes"
-					+ " WHERE contactId = ?";
+			String sql = "SELECT contactId, lastConnected FROM connectionTimes";
 			ps = txn.prepareStatement(sql);
-			ps.setInt(1, c.getInt());
 			rs = ps.executeQuery();
-			if(!rs.next()) throw new DbStateException();
-			long lastConnected = rs.getLong(1);
-			if(rs.next()) throw new DbStateException();
+			Map<ContactId, Long> times = new HashMap<ContactId, Long>();
+			while(rs.next())
+				times.put(new ContactId(rs.getInt(1)), rs.getLong(2));
 			rs.close();
 			ps.close();
-			return lastConnected;
+			return Collections.unmodifiableMap(times);
 		} catch(SQLException e) {
 			tryToClose(rs);
 			tryToClose(ps);
diff --git a/briar-tests/src/net/sf/briar/db/DatabaseComponentTest.java b/briar-tests/src/net/sf/briar/db/DatabaseComponentTest.java
index a06a3c81ee..b1e8cfcf6b 100644
--- a/briar-tests/src/net/sf/briar/db/DatabaseComponentTest.java
+++ b/briar-tests/src/net/sf/briar/db/DatabaseComponentTest.java
@@ -106,7 +106,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 		transportProperties = new TransportProperties(Collections.singletonMap(
 				"foo", "bar"));
 		contactId = new ContactId(234);
-		contact = new Contact(contactId, author, localAuthorId, timestamp);
+		contact = new Contact(contactId, author, localAuthorId);
 		endpoint = new Endpoint(contactId, transportId, 123, true);
 		temporarySecret = new TemporarySecret(contactId, transportId, 123,
 				false, 234, new byte[32], 345, 456, new byte[4]);
-- 
GitLab