From 044c10e89f7caa09006e7b004864fa58e67194d4 Mon Sep 17 00:00:00 2001
From: akwizgran <akwizgran@users.sourceforge.net>
Date: Mon, 10 Feb 2014 14:00:34 +0000
Subject: [PATCH] Bluetooth-only invitations: simpler and more reliable.

Of course, not all devices support Bluetooth...
---
 briar-android/AndroidManifest.xml             |   1 -
 briar-android/res/values/strings.xml          |  15 +-
 .../invitation/AddContactActivity.java        |  86 ++----
 .../android/invitation/AddContactView.java    |   4 -
 .../invitation/BluetoothStatusView.java       |  90 -------
 ...SetupView.java => ChooseIdentityView.java} |  45 +---
 .../invitation/CodesDoNotMatchView.java       |   9 +-
 .../invitation/ConnectionFailedView.java      |  47 +---
 .../android/invitation/ConnectionView.java    |  52 ++--
 .../android/invitation/WifiStatusView.java    |  98 -------
 .../plugins/AndroidPluginsModule.java         |   5 +-
 .../plugins/tcp/DroidLanTcpPlugin.java        |  42 ---
 .../plugins/tcp/DroidLanTcpPluginFactory.java |  39 ---
 .../plugins/tcp/LanTcpPlugin.java             | 248 +-----------------
 .../plugins/tcp/LanTcpPluginFactory.java      |   8 +-
 .../briarproject/plugins/tcp/TcpPlugin.java   |  10 +
 .../plugins/tcp/WanTcpPlugin.java             |  11 -
 .../plugins/tcp/LanTcpClientTest.java         |   7 +-
 .../plugins/tcp/LanTcpPluginTest.java         |  11 +-
 .../plugins/tcp/LanTcpServerTest.java         |   5 +-
 20 files changed, 83 insertions(+), 750 deletions(-)
 delete mode 100644 briar-android/src/org/briarproject/android/invitation/BluetoothStatusView.java
 rename briar-android/src/org/briarproject/android/invitation/{NetworkSetupView.java => ChooseIdentityView.java} (68%)
 delete mode 100644 briar-android/src/org/briarproject/android/invitation/WifiStatusView.java
 delete mode 100644 briar-android/src/org/briarproject/plugins/tcp/DroidLanTcpPlugin.java
 delete mode 100644 briar-android/src/org/briarproject/plugins/tcp/DroidLanTcpPluginFactory.java

diff --git a/briar-android/AndroidManifest.xml b/briar-android/AndroidManifest.xml
index 75eb3e9125..38fedeeccd 100644
--- a/briar-android/AndroidManifest.xml
+++ b/briar-android/AndroidManifest.xml
@@ -11,7 +11,6 @@
 	<uses-permission android:name="android.permission.INTERNET" />
 	<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
 	<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
-	<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />
 	<uses-permission android:name="android.permission.VIBRATE" />
 
 	<application
diff --git a/briar-android/res/values/strings.xml b/briar-android/res/values/strings.xml
index 5574bd3751..97b2ca3534 100644
--- a/briar-android/res/values/strings.xml
+++ b/briar-android/res/values/strings.xml
@@ -28,22 +28,13 @@
     <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>
-    <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</string>
-    <string name="bluetooth_disabled">Bluetooth is OFF</string>
-    <string name="bluetooth_not_discoverable">Bluetooth is NOT DISCOVERABLE</string>
-    <string name="bluetooth_discoverable">Bluetooth is discoverable</string>
-    <string name="fact_to_face">For security reasons you must be face-to-face with someone to add them as a contact</string>
+    <string name="face_to_face">For security reasons you must be face-to-face with the person you want to add as a contact.\n\nThis will prevent anyone from impersonating you or reading your messages in future.</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>
-    <string name="format_connecting_wifi">Searching via %1$s\u2026</string>
-    <string name="connecting_bluetooth">Searching via Bluetooth\u2026</string>
+    <string name="searching_format">Searching for %1$s\u2026</string>
     <string name="connection_failed">Connection failed</string>
-    <string name="check_same_network">Please check that you are both using the same network</string>
+    <string name="could_not_find_contact">Briar could not find your contact nearby</string>
     <string name="try_again_button">Try again</string>
     <string name="connected_to_contact">Connected to contact</string>
     <string name="calculating_confirmation_code">Calculating confirmation code\u2026</string>
diff --git a/briar-android/src/org/briarproject/android/invitation/AddContactActivity.java b/briar-android/src/org/briarproject/android/invitation/AddContactActivity.java
index 6db6576079..ac321b93a0 100644
--- a/briar-android/src/org/briarproject/android/invitation/AddContactActivity.java
+++ b/briar-android/src/org/briarproject/android/invitation/AddContactActivity.java
@@ -1,10 +1,5 @@
 package org.briarproject.android.invitation;
 
-import static android.bluetooth.BluetoothAdapter.ACTION_SCAN_MODE_CHANGED;
-import static android.bluetooth.BluetoothAdapter.ACTION_STATE_CHANGED;
-import static android.bluetooth.BluetoothAdapter.EXTRA_STATE;
-import static android.bluetooth.BluetoothAdapter.STATE_ON;
-import static android.net.wifi.WifiManager.NETWORK_STATE_CHANGED_ACTION;
 import static android.widget.Toast.LENGTH_LONG;
 import static java.util.logging.Level.INFO;
 import static java.util.logging.Level.WARNING;
@@ -33,13 +28,7 @@ import org.briarproject.api.invitation.InvitationTask;
 import org.briarproject.api.invitation.InvitationTaskFactory;
 import org.briarproject.api.lifecycle.LifecycleManager;
 
-import android.bluetooth.BluetoothAdapter;
-import android.content.BroadcastReceiver;
-import android.content.Context;
 import android.content.Intent;
-import android.content.IntentFilter;
-import android.net.wifi.WifiInfo;
-import android.net.wifi.WifiManager;
 import android.os.Bundle;
 import android.widget.Toast;
 
@@ -56,9 +45,6 @@ implements InvitationListener {
 	private InvitationTask task = null;
 	private long taskHandle = -1;
 	private AuthorId localAuthorId = null;
-	private String networkName = null;
-	private boolean bluetoothEnabled = false;
-	private BluetoothWifiStateReceiver receiver = null;
 	private int localInvitationCode = -1, remoteInvitationCode = -1;
 	private int localConfirmationCode = -1, remoteConfirmationCode = -1;
 	private boolean connected = false, connectionFailed = false;
@@ -76,7 +62,7 @@ implements InvitationListener {
 		super.onCreate(state);
 		if(state == null) {
 			// This is a new activity
-			setView(new NetworkSetupView(this));
+			setView(new ChooseIdentityView(this));
 		} else {
 			// Restore the activity's state
 			byte[] b = state.getByteArray("briar.LOCAL_AUTHOR_ID");
@@ -96,7 +82,7 @@ implements InvitationListener {
 				}
 				// Set the appropriate view for the state
 				if(localInvitationCode == -1) {
-					setView(new NetworkSetupView(this));
+					setView(new ChooseIdentityView(this));
 				} else if(remoteInvitationCode == -1) {
 					setView(new InvitationCodeView(this));
 				} else if(connectionFailed) {
@@ -123,7 +109,7 @@ implements InvitationListener {
 				contactName = s.getContactName();
 				// Set the appropriate view for the state
 				if(localInvitationCode == -1) {
-					setView(new NetworkSetupView(this));
+					setView(new ChooseIdentityView(this));
 				} else if(remoteInvitationCode == -1) {
 					setView(new InvitationCodeView(this));
 				} else if(connectionFailed) {
@@ -148,25 +134,6 @@ implements InvitationListener {
 				}
 			}
 		}
-
-		// Listen for Bluetooth and WiFi state changes
-		IntentFilter filter = new IntentFilter();
-		filter.addAction(ACTION_STATE_CHANGED);
-		filter.addAction(ACTION_SCAN_MODE_CHANGED);
-		filter.addAction(NETWORK_STATE_CHANGED_ACTION);
-		receiver = new BluetoothWifiStateReceiver();
-		registerReceiver(receiver, filter);
-
-		// Get the current Bluetooth and WiFi state
-		BluetoothAdapter bluetooth = BluetoothAdapter.getDefaultAdapter();
-		if(bluetooth != null) bluetoothEnabled = bluetooth.isEnabled();
-		view.bluetoothStateChanged();
-		WifiManager wifi = (WifiManager) getSystemService(WIFI_SERVICE);
-		if(wifi != null && wifi.isWifiEnabled()) {
-			WifiInfo info = wifi.getConnectionInfo();
-			if(info.getNetworkId() != -1) networkName = info.getSSID();
-		}
-		view.wifiStateChanged();
 	}
 
 	private void showToastAndFinish() {
@@ -198,7 +165,13 @@ implements InvitationListener {
 	public void onDestroy() {
 		super.onDestroy();
 		if(task != null) task.removeListener(this);
-		if(receiver != null) unregisterReceiver(receiver);
+	}
+
+	@Override
+	public void onActivityResult(int request, int result, Intent data) {
+		// This is the result of enabling Bluetooth
+		if(result != RESULT_CANCELED)
+			reset(new InvitationCodeView(this));
 	}
 
 	void setView(AddContactView view) {
@@ -208,7 +181,7 @@ implements InvitationListener {
 	}
 
 	void reset(AddContactView view) {
-		// Don't reset localAuthorId, networkName or bluetoothEnabled
+		// Don't reset localAuthorId
 		task = null;
 		taskHandle = -1;
 		localInvitationCode = -1;
@@ -264,23 +237,20 @@ implements InvitationListener {
 		return localAuthorId;
 	}
 
-	String getNetworkName() {
-		return networkName;
-	}
-
-	boolean isBluetoothEnabled() {
-		return bluetoothEnabled;
-	}
-
 	int getLocalInvitationCode() {
 		if(localInvitationCode == -1)
 			localInvitationCode = crypto.generateInvitationCode();
 		return localInvitationCode;
 	}
 
+	int getRemoteInvitationCode() {
+		return remoteInvitationCode;
+	}
+
 	void remoteInvitationCodeEntered(int code) {
 		if(localAuthorId == null) throw new IllegalStateException();
 		if(localInvitationCode == -1) throw new IllegalStateException();
+		remoteInvitationCode = code;
 		setView(new ConnectionView(this));
 		task = invitationTaskFactory.createTask(localAuthorId,
 				localInvitationCode, code);
@@ -392,30 +362,6 @@ implements InvitationListener {
 		});
 	}
 
-	private class BluetoothWifiStateReceiver extends BroadcastReceiver {
-
-		public void onReceive(Context ctx, Intent intent) {
-			String action = intent.getAction();
-			if(action.equals(ACTION_STATE_CHANGED)) {
-				int state = intent.getIntExtra(EXTRA_STATE, 0);
-				bluetoothEnabled = state == STATE_ON;
-				view.bluetoothStateChanged();
-			} else if(action.equals(ACTION_SCAN_MODE_CHANGED)) {
-				view.bluetoothStateChanged();
-			} else if(action.equals(NETWORK_STATE_CHANGED_ACTION)) {
-				WifiManager wifi = (WifiManager) getSystemService(WIFI_SERVICE);
-				if(wifi == null || !wifi.isWifiEnabled()) {
-					networkName = null;
-				} else {
-					WifiInfo info = wifi.getConnectionInfo();
-					if(info.getNetworkId() == -1) networkName = null;
-					else networkName = info.getSSID();
-				}
-				view.wifiStateChanged();
-			}
-		}
-	}
-
 	/**
 	 * Cleans up the reference to the invitation task when the task completes.
 	 * This class is static to prevent memory leaks.
diff --git a/briar-android/src/org/briarproject/android/invitation/AddContactView.java b/briar-android/src/org/briarproject/android/invitation/AddContactView.java
index bd5cd38115..2a6b978493 100644
--- a/briar-android/src/org/briarproject/android/invitation/AddContactView.java
+++ b/briar-android/src/org/briarproject/android/invitation/AddContactView.java
@@ -28,8 +28,4 @@ abstract class AddContactView extends LinearLayout {
 	}
 
 	abstract void populate();
-
-	void wifiStateChanged() {}
-
-	void bluetoothStateChanged() {}
 }
diff --git a/briar-android/src/org/briarproject/android/invitation/BluetoothStatusView.java b/briar-android/src/org/briarproject/android/invitation/BluetoothStatusView.java
deleted file mode 100644
index a91110e5ea..0000000000
--- a/briar-android/src/org/briarproject/android/invitation/BluetoothStatusView.java
+++ /dev/null
@@ -1,90 +0,0 @@
-package org.briarproject.android.invitation;
-
-import static android.bluetooth.BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE;
-import static android.provider.Settings.ACTION_BLUETOOTH_SETTINGS;
-import static android.view.Gravity.CENTER;
-import static org.briarproject.android.util.CommonLayoutParams.WRAP_WRAP_1;
-
-import org.briarproject.R;
-import org.briarproject.android.util.LayoutUtils;
-
-import android.bluetooth.BluetoothAdapter;
-import android.content.Context;
-import android.content.Intent;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.widget.ImageButton;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-class BluetoothStatusView extends LinearLayout implements OnClickListener {
-
-	private final int pad;
-
-	public BluetoothStatusView(Context ctx) {
-		super(ctx);
-		pad = LayoutUtils.getPadding(ctx);
-	}
-
-	void init() {
-		setOrientation(HORIZONTAL);
-		setGravity(CENTER);
-		populate();
-	}
-
-	void populate() {
-		removeAllViews();
-		Context ctx = getContext();
-		TextView status = new TextView(ctx);
-		status.setLayoutParams(WRAP_WRAP_1);
-		status.setTextSize(14);
-		status.setPadding(pad, pad, pad, pad);
-		BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
-		if(adapter == null) {
-			ImageView warning = new ImageView(ctx);
-			warning.setPadding(pad, pad, pad, pad);
-			warning.setImageResource(R.drawable.alerts_and_states_warning);
-			addView(warning);
-			status.setText(R.string.bluetooth_not_available);
-			addView(status);
-		} else if(adapter.getScanMode() == SCAN_MODE_CONNECTABLE_DISCOVERABLE) {
-			ImageView ok = new ImageView(ctx);
-			ok.setPadding(pad, pad, pad, pad);
-			ok.setImageResource(R.drawable.navigation_accept);
-			addView(ok);
-			status.setText(R.string.bluetooth_discoverable);
-			addView(status);
-			ImageButton settings = new ImageButton(ctx);
-			settings.setImageResource(R.drawable.action_settings);
-			settings.setOnClickListener(this);
-			addView(settings);
-		} else if(adapter.isEnabled()) {
-			ImageView warning = new ImageView(ctx);
-			warning.setPadding(pad, pad, pad, pad);
-			warning.setImageResource(R.drawable.alerts_and_states_warning);
-			addView(warning);
-			status.setText(R.string.bluetooth_not_discoverable);
-			addView(status);
-			ImageButton settings = new ImageButton(ctx);
-			settings.setImageResource(R.drawable.action_settings);
-			settings.setOnClickListener(this);
-			addView(settings);
-		} else {
-			ImageView warning = new ImageView(ctx);
-			warning.setPadding(pad, pad, pad, pad);
-			warning.setImageResource(R.drawable.alerts_and_states_warning);
-			addView(warning);
-			status.setText(R.string.bluetooth_disabled);
-			addView(status);
-			ImageButton settings = new ImageButton(ctx);
-			settings.setImageResource(R.drawable.action_settings);
-			settings.setOnClickListener(this);
-			addView(settings);
-		}
-	}
-
-	public void onClick(View view) {
-		getContext().startActivity(new Intent(ACTION_BLUETOOTH_SETTINGS));
-	}
-}
diff --git a/briar-android/src/org/briarproject/android/invitation/NetworkSetupView.java b/briar-android/src/org/briarproject/android/invitation/ChooseIdentityView.java
similarity index 68%
rename from briar-android/src/org/briarproject/android/invitation/NetworkSetupView.java
rename to briar-android/src/org/briarproject/android/invitation/ChooseIdentityView.java
index c494dac92d..e6593fed07 100644
--- a/briar-android/src/org/briarproject/android/invitation/NetworkSetupView.java
+++ b/briar-android/src/org/briarproject/android/invitation/ChooseIdentityView.java
@@ -1,5 +1,7 @@
 package org.briarproject.android.invitation;
 
+import static android.bluetooth.BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE;
+import static android.bluetooth.BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION;
 import static android.view.Gravity.CENTER;
 import static org.briarproject.android.identity.LocalAuthorItem.NEW;
 import static org.briarproject.android.util.CommonLayoutParams.MATCH_WRAP;
@@ -9,7 +11,6 @@ import org.briarproject.R;
 import org.briarproject.android.identity.CreateIdentityActivity;
 import org.briarproject.android.identity.LocalAuthorItem;
 import org.briarproject.android.identity.LocalAuthorSpinnerAdapter;
-import org.briarproject.api.AuthorId;
 
 import android.content.Context;
 import android.content.Intent;
@@ -22,16 +23,14 @@ import android.widget.LinearLayout;
 import android.widget.Spinner;
 import android.widget.TextView;
 
-class NetworkSetupView extends AddContactView
+class ChooseIdentityView extends AddContactView
 implements OnItemSelectedListener, OnClickListener {
 
 	private LocalAuthorSpinnerAdapter adapter = null;
 	private Spinner spinner = null;
-	private WifiStatusView wifi = null;
-	private BluetoothStatusView bluetooth = null;
 	private Button continueButton = null;
 
-	NetworkSetupView(Context ctx) {
+	ChooseIdentityView(Context ctx) {
 		super(ctx);
 	}
 
@@ -58,48 +57,19 @@ implements OnItemSelectedListener, OnClickListener {
 		innerLayout.addView(spinner);
 		addView(innerLayout);
 
-		wifi = new WifiStatusView(ctx);
-		wifi.init();
-		addView(wifi);
-
-		bluetooth = new BluetoothStatusView(ctx);
-		bluetooth.init();
-		addView(bluetooth);
-
 		TextView faceToFace = new TextView(ctx);
-		faceToFace.setGravity(CENTER);
 		faceToFace.setTextSize(14);
 		faceToFace.setPadding(pad, pad, pad, pad);
-		faceToFace.setText(R.string.fact_to_face);
+		faceToFace.setText(R.string.face_to_face);
 		addView(faceToFace);
 
 		continueButton = new Button(ctx);
 		continueButton.setLayoutParams(WRAP_WRAP);
 		continueButton.setText(R.string.continue_button);
 		continueButton.setOnClickListener(this);
-		enableOrDisableContinueButton();
 		addView(continueButton);
 	}
 
-	void wifiStateChanged() {
-		if(wifi != null) wifi.populate();
-		enableOrDisableContinueButton();
-	}
-
-	void bluetoothStateChanged() {
-		if(bluetooth != null) bluetooth.populate();
-		enableOrDisableContinueButton();
-	}
-
-	private void enableOrDisableContinueButton() {
-		if(continueButton == null) return; // Activity not created yet
-		AuthorId localAuthorId = container.getLocalAuthorId();
-		boolean bluetoothEnabled = container.isBluetoothEnabled();
-		String networkName = container.getNetworkName();
-		boolean networkAvailable = bluetoothEnabled || networkName != null;
-		continueButton.setEnabled(localAuthorId != null && networkAvailable);
-	}
-
 	public void onItemSelected(AdapterView<?> parent, View view, int position,
 			long id) {
 		LocalAuthorItem item = adapter.getItem(position);
@@ -110,7 +80,6 @@ implements OnItemSelectedListener, OnClickListener {
 		} else {
 			container.setLocalAuthorId(item.getLocalAuthor().getId());
 		}
-		enableOrDisableContinueButton();
 	}
 
 	public void onNothingSelected(AdapterView<?> parent) {
@@ -118,6 +87,8 @@ implements OnItemSelectedListener, OnClickListener {
 	}
 
 	public void onClick(View view) {
-		container.setView(new InvitationCodeView(container));
+		Intent i = new Intent(ACTION_REQUEST_DISCOVERABLE);
+		i.putExtra(EXTRA_DISCOVERABLE_DURATION, 120);
+		container.startActivityForResult(i, 0);
 	}
 }
diff --git a/briar-android/src/org/briarproject/android/invitation/CodesDoNotMatchView.java b/briar-android/src/org/briarproject/android/invitation/CodesDoNotMatchView.java
index c62aa97479..fc7d1d0198 100644
--- a/briar-android/src/org/briarproject/android/invitation/CodesDoNotMatchView.java
+++ b/briar-android/src/org/briarproject/android/invitation/CodesDoNotMatchView.java
@@ -1,11 +1,14 @@
 package org.briarproject.android.invitation;
 
+import static android.bluetooth.BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE;
+import static android.bluetooth.BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION;
 import static android.view.Gravity.CENTER;
 import static org.briarproject.android.util.CommonLayoutParams.WRAP_WRAP;
 
 import org.briarproject.R;
 
 import android.content.Context;
+import android.content.Intent;
 import android.view.View;
 import android.view.View.OnClickListener;
 import android.widget.Button;
@@ -38,6 +41,7 @@ class CodesDoNotMatchView extends AddContactView implements OnClickListener {
 		addView(innerLayout);
 
 		TextView interfering = new TextView(ctx);
+		interfering.setGravity(CENTER);
 		interfering.setTextSize(14);
 		interfering.setPadding(pad, 0, pad, pad);
 		interfering.setText(R.string.interfering);
@@ -51,7 +55,8 @@ class CodesDoNotMatchView extends AddContactView implements OnClickListener {
 	}
 
 	public void onClick(View view) {
-		// Try again
-		container.reset(new NetworkSetupView(container));
+		Intent i = new Intent(ACTION_REQUEST_DISCOVERABLE);
+		i.putExtra(EXTRA_DISCOVERABLE_DURATION, 120);
+		container.startActivityForResult(i, 0);
 	}
 }
diff --git a/briar-android/src/org/briarproject/android/invitation/ConnectionFailedView.java b/briar-android/src/org/briarproject/android/invitation/ConnectionFailedView.java
index 768ca91bf7..b13f10297e 100644
--- a/briar-android/src/org/briarproject/android/invitation/ConnectionFailedView.java
+++ b/briar-android/src/org/briarproject/android/invitation/ConnectionFailedView.java
@@ -1,11 +1,14 @@
 package org.briarproject.android.invitation;
 
+import static android.bluetooth.BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE;
+import static android.bluetooth.BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION;
 import static android.view.Gravity.CENTER;
 import static org.briarproject.android.util.CommonLayoutParams.WRAP_WRAP;
 
 import org.briarproject.R;
 
 import android.content.Context;
+import android.content.Intent;
 import android.view.View;
 import android.view.View.OnClickListener;
 import android.widget.Button;
@@ -15,8 +18,6 @@ import android.widget.TextView;
 
 class ConnectionFailedView extends AddContactView implements OnClickListener {
 
-	private WifiStatusView wifi = null;
-	private BluetoothStatusView bluetooth = null;
 	private Button tryAgainButton = null;
 
 	ConnectionFailedView(Context ctx) {
@@ -41,47 +42,23 @@ class ConnectionFailedView extends AddContactView implements OnClickListener {
 		innerLayout.addView(failed);
 		addView(innerLayout);
 
-		TextView checkNetwork = new TextView(ctx);
-		checkNetwork.setTextSize(14);
-		checkNetwork.setPadding(pad, 0, pad, pad);
-		checkNetwork.setText(R.string.check_same_network);
-		addView(checkNetwork);
-
-		wifi = new WifiStatusView(ctx);
-		wifi.init();
-		addView(wifi);
-
-		bluetooth = new BluetoothStatusView(ctx);
-		bluetooth.init();
-		addView(bluetooth);
+		TextView couldNotFind = new TextView(ctx);
+		couldNotFind.setGravity(CENTER);
+		couldNotFind.setTextSize(14);
+		couldNotFind.setPadding(pad, 0, pad, pad);
+		couldNotFind.setText(R.string.could_not_find_contact);
+		addView(couldNotFind);
 
 		tryAgainButton = new Button(ctx);
 		tryAgainButton.setLayoutParams(WRAP_WRAP);
 		tryAgainButton.setText(R.string.try_again_button);
 		tryAgainButton.setOnClickListener(this);
-		enableOrDisableTryAgainButton();
 		addView(tryAgainButton);
 	}
 
-	void wifiStateChanged() {
-		if(wifi != null) wifi.populate();
-		enableOrDisableTryAgainButton();
-	}
-
-	void bluetoothStateChanged() {
-		if(bluetooth != null) bluetooth.populate();
-		enableOrDisableTryAgainButton();
-	}
-
-	private void enableOrDisableTryAgainButton() {
-		if(tryAgainButton == null) return; // Activity not created yet
-		boolean bluetoothEnabled = container.isBluetoothEnabled();
-		String networkName = container.getNetworkName();
-		tryAgainButton.setEnabled(bluetoothEnabled || networkName != null);
-	}
-
 	public void onClick(View view) {
-		// Try again
-		container.reset(new InvitationCodeView(container));
+		Intent i = new Intent(ACTION_REQUEST_DISCOVERABLE);
+		i.putExtra(EXTRA_DISCOVERABLE_DURATION, 120);
+		container.startActivityForResult(i, 0);
 	}
 }
diff --git a/briar-android/src/org/briarproject/android/invitation/ConnectionView.java b/briar-android/src/org/briarproject/android/invitation/ConnectionView.java
index 0762f200b1..5bc7584a08 100644
--- a/briar-android/src/org/briarproject/android/invitation/ConnectionView.java
+++ b/briar-android/src/org/briarproject/android/invitation/ConnectionView.java
@@ -34,41 +34,21 @@ class ConnectionView extends AddContactView {
 		code.setText(String.format("%06d", localCode));
 		addView(code);
 
-		String networkName = container.getNetworkName();
-		if(networkName != null) {
-			LinearLayout innerLayout = new LinearLayout(ctx);
-			innerLayout.setOrientation(HORIZONTAL);
-			innerLayout.setGravity(CENTER);
-
-			ProgressBar progress = new ProgressBar(ctx);
-			progress.setIndeterminate(true);
-			progress.setPadding(pad, pad, pad, pad);
-			innerLayout.addView(progress);
-
-			TextView connecting = new TextView(ctx);
-			String format = getResources().getString(
-					R.string.format_connecting_wifi);
-			connecting.setText(String.format(format, networkName));
-			innerLayout.addView(connecting);
-
-			addView(innerLayout);
-		}
-
-		if(container.isBluetoothEnabled()) {
-			LinearLayout innerLayout = new LinearLayout(ctx);
-			innerLayout.setOrientation(HORIZONTAL);
-			innerLayout.setGravity(CENTER);
-
-			ProgressBar progress = new ProgressBar(ctx);
-			progress.setPadding(pad, pad, pad, pad);
-			progress.setIndeterminate(true);
-			innerLayout.addView(progress);
-
-			TextView connecting = new TextView(ctx);
-			connecting.setText(R.string.connecting_bluetooth);
-			innerLayout.addView(connecting);
-
-			addView(innerLayout);
-		}
+		LinearLayout innerLayout = new LinearLayout(ctx);
+		innerLayout.setOrientation(HORIZONTAL);
+		innerLayout.setGravity(CENTER);
+
+		ProgressBar progress = new ProgressBar(ctx);
+		progress.setPadding(pad, pad, pad, pad);
+		progress.setIndeterminate(true);
+		innerLayout.addView(progress);
+
+		TextView connecting = new TextView(ctx);
+		int remoteCode = container.getRemoteInvitationCode();
+		String format = ctx.getResources().getString(R.string.searching_format);
+		connecting.setText(String.format(format, remoteCode));
+		innerLayout.addView(connecting);
+
+		addView(innerLayout);
 	}
 }
diff --git a/briar-android/src/org/briarproject/android/invitation/WifiStatusView.java b/briar-android/src/org/briarproject/android/invitation/WifiStatusView.java
deleted file mode 100644
index c55f48fa0b..0000000000
--- a/briar-android/src/org/briarproject/android/invitation/WifiStatusView.java
+++ /dev/null
@@ -1,98 +0,0 @@
-package org.briarproject.android.invitation;
-
-import static android.content.Context.WIFI_SERVICE;
-import static android.provider.Settings.ACTION_WIFI_SETTINGS;
-import static android.view.Gravity.CENTER;
-import static org.briarproject.android.util.CommonLayoutParams.WRAP_WRAP_1;
-
-import org.briarproject.R;
-import org.briarproject.android.util.LayoutUtils;
-
-import android.content.Context;
-import android.content.Intent;
-import android.net.wifi.WifiInfo;
-import android.net.wifi.WifiManager;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.widget.ImageButton;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-class WifiStatusView extends LinearLayout implements OnClickListener {
-
-	private final int pad;
-
-	public WifiStatusView(Context ctx) {
-		super(ctx);
-		pad = LayoutUtils.getPadding(ctx);
-	}
-
-	void init() {
-		setOrientation(HORIZONTAL);
-		setGravity(CENTER);
-		populate();
-	}
-
-	void populate() {
-		removeAllViews();
-		Context ctx = getContext();
-		TextView status = new TextView(ctx);
-		status.setTextSize(14);
-		status.setPadding(pad, pad, pad, pad);
-		status.setLayoutParams(WRAP_WRAP_1);
-		WifiManager wifi = (WifiManager) ctx.getSystemService(WIFI_SERVICE);
-		if(wifi == null) {
-			ImageView warning = new ImageView(ctx);
-			warning.setPadding(pad, pad, pad, pad);
-			warning.setImageResource(R.drawable.alerts_and_states_warning);
-			addView(warning);
-			status.setText(R.string.wifi_not_available);
-			addView(status);
-		} else if(wifi.isWifiEnabled()) { 
-			WifiInfo info = wifi.getConnectionInfo();
-			String networkName =  info.getSSID();
-			int networkId = info.getNetworkId();
-			if(networkName == null || networkId == -1) {
-				ImageView warning = new ImageView(ctx);
-				warning.setPadding(pad, pad, pad, pad);
-				warning.setImageResource(R.drawable.alerts_and_states_warning);
-				addView(warning);
-				status.setText(R.string.wifi_disconnected);
-				addView(status);
-				ImageButton settings = new ImageButton(ctx);
-				settings.setImageResource(R.drawable.action_settings);
-				settings.setOnClickListener(this);
-				addView(settings);
-			} else {
-				ImageView ok = new ImageView(ctx);
-				ok.setPadding(pad, pad, pad, pad);
-				ok.setImageResource(R.drawable.navigation_accept);
-				addView(ok);
-				String format = getResources().getString(
-						R.string.format_wifi_connected);
-				status.setText(String.format(format, networkName));
-				addView(status);
-				ImageButton settings = new ImageButton(ctx);
-				settings.setImageResource(R.drawable.action_settings);
-				settings.setOnClickListener(this);
-				addView(settings);
-			}
-		} else {
-			ImageView warning = new ImageView(ctx);
-			warning.setPadding(pad, pad, pad, pad);
-			warning.setImageResource(R.drawable.alerts_and_states_warning);
-			addView(warning);
-			status.setText(R.string.wifi_disabled);
-			addView(status);
-			ImageButton settings = new ImageButton(ctx);
-			settings.setImageResource(R.drawable.action_settings);
-			settings.setOnClickListener(this);
-			addView(settings);
-		}
-	}
-
-	public void onClick(View view) {
-		getContext().startActivity(new Intent(ACTION_WIFI_SETTINGS));
-	}
-}
diff --git a/briar-android/src/org/briarproject/plugins/AndroidPluginsModule.java b/briar-android/src/org/briarproject/plugins/AndroidPluginsModule.java
index 489ee32c00..a2f555b18b 100644
--- a/briar-android/src/org/briarproject/plugins/AndroidPluginsModule.java
+++ b/briar-android/src/org/briarproject/plugins/AndroidPluginsModule.java
@@ -14,7 +14,7 @@ import org.briarproject.api.plugins.duplex.DuplexPluginFactory;
 import org.briarproject.api.plugins.simplex.SimplexPluginConfig;
 import org.briarproject.api.plugins.simplex.SimplexPluginFactory;
 import org.briarproject.plugins.droidtooth.DroidtoothPluginFactory;
-import org.briarproject.plugins.tcp.DroidLanTcpPluginFactory;
+import org.briarproject.plugins.tcp.LanTcpPluginFactory;
 import org.briarproject.plugins.tor.TorPluginFactory;
 
 import android.content.Context;
@@ -45,8 +45,7 @@ public class AndroidPluginsModule extends AbstractModule {
 				crypto.getSecureRandom());
 		DuplexPluginFactory tor = new TorPluginFactory(pluginExecutor,
 				appContext, shutdownManager);
-		DuplexPluginFactory lan = new DroidLanTcpPluginFactory(pluginExecutor,
-				appContext);
+		DuplexPluginFactory lan = new LanTcpPluginFactory(pluginExecutor);
 		final Collection<DuplexPluginFactory> factories =
 				Arrays.asList(droidtooth, tor, lan);
 		return new DuplexPluginConfig() {
diff --git a/briar-android/src/org/briarproject/plugins/tcp/DroidLanTcpPlugin.java b/briar-android/src/org/briarproject/plugins/tcp/DroidLanTcpPlugin.java
deleted file mode 100644
index 064199dca6..0000000000
--- a/briar-android/src/org/briarproject/plugins/tcp/DroidLanTcpPlugin.java
+++ /dev/null
@@ -1,42 +0,0 @@
-package org.briarproject.plugins.tcp;
-
-import static android.content.Context.WIFI_SERVICE;
-
-import java.util.concurrent.Executor;
-
-import org.briarproject.api.crypto.PseudoRandom;
-import org.briarproject.api.plugins.duplex.DuplexPluginCallback;
-import org.briarproject.api.plugins.duplex.DuplexTransportConnection;
-import org.briarproject.api.system.Clock;
-
-import android.content.Context;
-import android.net.wifi.WifiManager;
-import android.net.wifi.WifiManager.MulticastLock;
-
-class DroidLanTcpPlugin extends LanTcpPlugin {
-
-	private final Context appContext;
-
-	DroidLanTcpPlugin(Executor pluginExecutor, Context appContext, Clock clock,
-			DuplexPluginCallback callback, int maxFrameLength, long maxLatency,
-			long pollingInterval) {
-		super(pluginExecutor, clock, callback, maxFrameLength, maxLatency,
-				pollingInterval);
-		this.appContext = appContext;
-	}
-
-	@Override
-	public DuplexTransportConnection createInvitationConnection(PseudoRandom r,
-			long timeout) {
-		Object o = appContext.getSystemService(WIFI_SERVICE);
-		WifiManager wifi = (WifiManager) o;
-		if(wifi == null || !wifi.isWifiEnabled()) return null;
-		MulticastLock lock = wifi.createMulticastLock("invitation");
-		lock.acquire();
-		try {
-			return super.createInvitationConnection(r, timeout);
-		} finally {
-			lock.release();
-		}
-	}
-}
diff --git a/briar-android/src/org/briarproject/plugins/tcp/DroidLanTcpPluginFactory.java b/briar-android/src/org/briarproject/plugins/tcp/DroidLanTcpPluginFactory.java
deleted file mode 100644
index 70ce255bcf..0000000000
--- a/briar-android/src/org/briarproject/plugins/tcp/DroidLanTcpPluginFactory.java
+++ /dev/null
@@ -1,39 +0,0 @@
-package org.briarproject.plugins.tcp;
-
-import java.util.concurrent.Executor;
-
-import org.briarproject.api.TransportId;
-import org.briarproject.api.plugins.duplex.DuplexPlugin;
-import org.briarproject.api.plugins.duplex.DuplexPluginCallback;
-import org.briarproject.api.plugins.duplex.DuplexPluginFactory;
-import org.briarproject.api.system.Clock;
-import org.briarproject.system.SystemClock;
-
-import android.content.Context;
-
-public class DroidLanTcpPluginFactory implements DuplexPluginFactory {
-
-	private static final int MAX_FRAME_LENGTH = 1024;
-	private static final long MAX_LATENCY = 60 * 1000; // 1 minute
-	private static final long POLLING_INTERVAL = 60 * 1000; // 1 minute
-
-	private final Executor pluginExecutor;
-	private final Context appContext;
-	private final Clock clock;
-
-	public DroidLanTcpPluginFactory(Executor pluginExecutor,
-			Context appContext) {
-		this.pluginExecutor = pluginExecutor;
-		this.appContext = appContext;
-		clock = new SystemClock();
-	}
-
-	public TransportId getId() {
-		return LanTcpPlugin.ID;
-	}
-
-	public DuplexPlugin createPlugin(DuplexPluginCallback callback) {
-		return new DroidLanTcpPlugin(pluginExecutor, appContext, clock,
-				callback, MAX_FRAME_LENGTH, MAX_LATENCY, POLLING_INTERVAL);
-	}
-}
diff --git a/briar-core/src/org/briarproject/plugins/tcp/LanTcpPlugin.java b/briar-core/src/org/briarproject/plugins/tcp/LanTcpPlugin.java
index 92ab68ef53..db44453280 100644
--- a/briar-core/src/org/briarproject/plugins/tcp/LanTcpPlugin.java
+++ b/briar-core/src/org/briarproject/plugins/tcp/LanTcpPlugin.java
@@ -1,17 +1,11 @@
 package org.briarproject.plugins.tcp;
 
-import static java.util.logging.Level.INFO;
 import static java.util.logging.Level.WARNING;
 
-import java.io.IOException;
-import java.net.DatagramPacket;
 import java.net.Inet6Address;
 import java.net.InetAddress;
 import java.net.InetSocketAddress;
-import java.net.MulticastSocket;
 import java.net.NetworkInterface;
-import java.net.ServerSocket;
-import java.net.Socket;
 import java.net.SocketAddress;
 import java.net.SocketException;
 import java.net.UnknownHostException;
@@ -23,12 +17,7 @@ import java.util.logging.Logger;
 
 import org.briarproject.api.TransportId;
 import org.briarproject.api.TransportProperties;
-import org.briarproject.api.crypto.PseudoRandom;
 import org.briarproject.api.plugins.duplex.DuplexPluginCallback;
-import org.briarproject.api.plugins.duplex.DuplexTransportConnection;
-import org.briarproject.api.system.Clock;
-import org.briarproject.util.ByteUtils;
-import org.briarproject.util.LatchedReference;
 import org.briarproject.util.StringUtils;
 
 /** A socket plugin that supports exchanging invitations over a LAN. */
@@ -38,16 +27,11 @@ class LanTcpPlugin extends TcpPlugin {
 
 	private static final Logger LOG =
 			Logger.getLogger(LanTcpPlugin.class.getName());
-	private static final int MULTICAST_INTERVAL = 1000; // 1 second
 
-	private final Clock clock;
-
-	LanTcpPlugin(Executor pluginExecutor, Clock clock,
-			DuplexPluginCallback callback, int maxFrameLength, long maxLatency,
-			long pollingInterval) {
+	LanTcpPlugin(Executor pluginExecutor, DuplexPluginCallback callback,
+			int maxFrameLength, long maxLatency, long pollingInterval) {
 		super(pluginExecutor, callback, maxFrameLength, maxLatency,
 				pollingInterval);
-		this.clock = clock;
 	}
 
 	public TransportId getId() {
@@ -106,232 +90,4 @@ class LanTcpPlugin extends TcpPlugin {
 		}
 		return addrs;
 	}
-
-	public boolean supportsInvitations() {
-		return true;
-	}
-
-	public DuplexTransportConnection createInvitationConnection(PseudoRandom r,
-			long timeout) {
-		if(!running) return null;
-		// Use the invitation codes to generate the group address and port
-		InetSocketAddress group = chooseMulticastGroup(r);
-		// Bind a multicast socket for sending and receiving packets
-		InetAddress iface = null;
-		MulticastSocket ms = null;
-		try {
-			iface = chooseInvitationInterface();
-			if(iface == null) return null;
-			ms = new MulticastSocket(group.getPort());
-			ms.setInterface(iface);
-			ms.joinGroup(group.getAddress());
-		} catch(IOException e) {
-			if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
-			if(ms != null) tryToClose(ms, group.getAddress());
-			return null;
-		}
-		// Bind a server socket for receiving invitation connections
-		ServerSocket ss = null;
-		try {
-			ss = new ServerSocket();
-			ss.bind(new InetSocketAddress(iface, 0));
-		} catch(IOException e) {
-			if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
-			if(ss != null) tryToClose(ss);
-			return null;
-		}
-		// Start the listener threads
-		LatchedReference<Socket> socketLatch = new LatchedReference<Socket>();
-		new MulticastListenerThread(socketLatch, ms, iface).start();
-		new TcpListenerThread(socketLatch, ss).start();
-		// Send packets until a connection is made or we run out of time
-		byte[] buffer = new byte[2];
-		ByteUtils.writeUint16(ss.getLocalPort(), buffer, 0);
-		DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
-		packet.setAddress(group.getAddress());
-		packet.setPort(group.getPort());
-		long now = clock.currentTimeMillis();
-		long end = now + timeout;
-		try {
-			while(now < end && running) {
-				// Send a packet
-				if(LOG.isLoggable(INFO)) LOG.info("Sending multicast packet");
-				ms.send(packet);
-				// Wait for an incoming or outgoing connection
-				try {
-					Socket s = socketLatch.waitForReference(MULTICAST_INTERVAL);
-					if(s != null) return new TcpTransportConnection(this, s);
-				} catch(InterruptedException e) {
-					if(LOG.isLoggable(INFO))
-						LOG.info("Interrupted while exchanging invitations");
-					Thread.currentThread().interrupt();
-					return null;
-				}
-				now = clock.currentTimeMillis();
-			}
-		} catch(IOException e) {
-			if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
-		} finally {
-			// Closing the sockets will terminate the listener threads
-			tryToClose(ms, group.getAddress());
-			tryToClose(ss);
-		}
-		return null;
-	}
-
-	private InetSocketAddress chooseMulticastGroup(PseudoRandom r) {
-		byte[] b = r.nextBytes(5);
-		// The group address is 239.random.random.random, excluding 0 and 255
-		byte[] group = new byte[4];
-		group[0] = (byte) 239;
-		group[1] = legalAddressByte(b[0]);
-		group[2] = legalAddressByte(b[1]);
-		group[3] = legalAddressByte(b[2]);
-		// The port is random in the range 32768 - 65535, inclusive
-		int port = ByteUtils.readUint16(b, 3);
-		if(port < 32768) port += 32768;
-		InetAddress address;
-		try {
-			address = InetAddress.getByAddress(group);
-		} catch(UnknownHostException badAddressLength) {
-			throw new RuntimeException(badAddressLength);
-		}
-		return new InetSocketAddress(address, port);
-	}
-
-	private byte legalAddressByte(byte b) {
-		if(b == 0) return 1;
-		if(b == (byte) 255) return (byte) 254;
-		return b;
-	}
-
-	private InetAddress chooseInvitationInterface() throws IOException {
-		List<NetworkInterface> ifaces =
-				Collections.list(NetworkInterface.getNetworkInterfaces());
-		// Prefer an interface with a link-local or site-local address
-		for(NetworkInterface iface : ifaces) {
-			for(InetAddress addr : Collections.list(iface.getInetAddresses())) {
-				if(addr.isLoopbackAddress()) continue;
-				boolean link = addr.isLinkLocalAddress();
-				boolean site = addr.isSiteLocalAddress();
-				if(link || site) return addr;
-			}
-		}
-		// Accept an interface without a link-local or site-local address
-		for(NetworkInterface iface : ifaces) {
-			for(InetAddress addr : Collections.list(iface.getInetAddresses())) {
-				if(!addr.isLoopbackAddress()) return addr;
-			}
-		}
-		// No suitable interfaces
-		return null;
-	}
-
-	private void tryToClose(MulticastSocket ms, InetAddress addr) {
-		try {
-			ms.leaveGroup(addr);
-		} catch(IOException e) {
-			if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
-		}
-		ms.close();
-	}
-
-	private class MulticastListenerThread extends Thread {
-
-		private final LatchedReference<Socket> socketLatch;
-		private final MulticastSocket multicastSocket;
-		private final InetAddress localAddress;
-
-		private MulticastListenerThread(LatchedReference<Socket> socketLatch,
-				MulticastSocket multicastSocket, InetAddress localAddress) {
-			this.socketLatch = socketLatch;
-			this.multicastSocket = multicastSocket;
-			this.localAddress = localAddress;
-		}
-
-		@Override
-		public void run() {
-			if(LOG.isLoggable(INFO))
-				LOG.info("Listening for multicast packets");
-			// Listen until a valid packet is received or the socket is closed
-			byte[] buffer = new byte[2];
-			DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
-			try {
-				while(running) {
-					multicastSocket.receive(packet);
-					if(LOG.isLoggable(INFO))
-						LOG.info("Received multicast packet");
-					parseAndConnectBack(packet);
-				}
-			} catch(IOException e) {
-				// This is expected when the socket is closed
-				if(LOG.isLoggable(INFO)) LOG.log(INFO, e.toString(), e);
-			}
-		}
-
-		private void parseAndConnectBack(DatagramPacket packet) {
-			InetAddress addr = packet.getAddress();
-			if(addr.equals(localAddress)) {
-				if(LOG.isLoggable(INFO)) LOG.info("Ignoring own packet");
-				return;
-			}
-			byte[] b = packet.getData();
-			int off = packet.getOffset();
-			int len = packet.getLength();
-			if(len != 2) {
-				if(LOG.isLoggable(INFO)) LOG.info("Invalid length: " + len);
-				return;
-			}
-			int port = ByteUtils.readUint16(b, off);
-			if(port < 32768 || port >= 65536) {
-				if(LOG.isLoggable(INFO)) LOG.info("Invalid port: " + port);
-				return;
-			}
-			if(LOG.isLoggable(INFO))
-				LOG.info("Packet from " + getHostAddress(addr) + ":" + port);
-			try {
-				// Connect back on the advertised TCP port
-				Socket s = new Socket(addr, port);
-				if(LOG.isLoggable(INFO)) LOG.info("Outgoing connection");
-				if(!socketLatch.set(s)) {
-					if(LOG.isLoggable(INFO))
-						LOG.info("Closing redundant connection");
-					s.close();
-				}
-			} catch(IOException e) {
-				if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
-			}
-		}
-	}
-
-	private class TcpListenerThread extends Thread {
-
-		private final LatchedReference<Socket> socketLatch;
-		private final ServerSocket serverSocket;
-
-		private TcpListenerThread(LatchedReference<Socket> socketLatch,
-				ServerSocket serverSocket) {
-			this.socketLatch = socketLatch;
-			this.serverSocket = serverSocket;
-		}
-
-		@Override
-		public void run() {
-			if(LOG.isLoggable(INFO))
-				LOG.info("Listening for invitation connections");
-			// Listen until a connection is received or the socket is closed
-			try {
-				Socket s = serverSocket.accept();
-				if(LOG.isLoggable(INFO)) LOG.info("Incoming connection");
-				if(!socketLatch.set(s)) {
-					if(LOG.isLoggable(INFO))
-						LOG.info("Closing redundant connection");
-					s.close();
-				}
-			} catch(IOException e) {
-				// This is expected when the socket is closed
-				if(LOG.isLoggable(INFO)) LOG.log(INFO, e.toString(), e);
-			}
-		}
-	}
 }
\ No newline at end of file
diff --git a/briar-core/src/org/briarproject/plugins/tcp/LanTcpPluginFactory.java b/briar-core/src/org/briarproject/plugins/tcp/LanTcpPluginFactory.java
index 3ce4e4af56..eb24e4550c 100644
--- a/briar-core/src/org/briarproject/plugins/tcp/LanTcpPluginFactory.java
+++ b/briar-core/src/org/briarproject/plugins/tcp/LanTcpPluginFactory.java
@@ -6,8 +6,6 @@ import org.briarproject.api.TransportId;
 import org.briarproject.api.plugins.duplex.DuplexPlugin;
 import org.briarproject.api.plugins.duplex.DuplexPluginCallback;
 import org.briarproject.api.plugins.duplex.DuplexPluginFactory;
-import org.briarproject.api.system.Clock;
-import org.briarproject.system.SystemClock;
 
 public class LanTcpPluginFactory implements DuplexPluginFactory {
 
@@ -16,11 +14,9 @@ public class LanTcpPluginFactory implements DuplexPluginFactory {
 	private static final long POLLING_INTERVAL = 60 * 1000; // 1 minute
 
 	private final Executor pluginExecutor;
-	private final Clock clock;
 
 	public LanTcpPluginFactory(Executor pluginExecutor) {
 		this.pluginExecutor = pluginExecutor;
-		clock = new SystemClock();
 	}
 
 	public TransportId getId() {
@@ -28,7 +24,7 @@ public class LanTcpPluginFactory implements DuplexPluginFactory {
 	}
 
 	public DuplexPlugin createPlugin(DuplexPluginCallback callback) {
-		return new LanTcpPlugin(pluginExecutor, clock, callback,
-				MAX_FRAME_LENGTH, MAX_LATENCY, POLLING_INTERVAL);
+		return new LanTcpPlugin(pluginExecutor, callback, MAX_FRAME_LENGTH,
+				MAX_LATENCY, POLLING_INTERVAL);
 	}
 }
diff --git a/briar-core/src/org/briarproject/plugins/tcp/TcpPlugin.java b/briar-core/src/org/briarproject/plugins/tcp/TcpPlugin.java
index 42c468bc2c..4f483dc89e 100644
--- a/briar-core/src/org/briarproject/plugins/tcp/TcpPlugin.java
+++ b/briar-core/src/org/briarproject/plugins/tcp/TcpPlugin.java
@@ -18,6 +18,7 @@ import java.util.logging.Logger;
 
 import org.briarproject.api.ContactId;
 import org.briarproject.api.TransportProperties;
+import org.briarproject.api.crypto.PseudoRandom;
 import org.briarproject.api.plugins.duplex.DuplexPlugin;
 import org.briarproject.api.plugins.duplex.DuplexPluginCallback;
 import org.briarproject.api.plugins.duplex.DuplexTransportConnection;
@@ -207,4 +208,13 @@ abstract class TcpPlugin implements DuplexPlugin {
 			return null;
 		}
 	}
+
+	public boolean supportsInvitations() {
+		return false;
+	}
+
+	public DuplexTransportConnection createInvitationConnection(PseudoRandom r,
+			long timeout) {
+		throw new UnsupportedOperationException();
+	}
 }
diff --git a/briar-core/src/org/briarproject/plugins/tcp/WanTcpPlugin.java b/briar-core/src/org/briarproject/plugins/tcp/WanTcpPlugin.java
index 10dd214ef7..9b3e32d749 100644
--- a/briar-core/src/org/briarproject/plugins/tcp/WanTcpPlugin.java
+++ b/briar-core/src/org/briarproject/plugins/tcp/WanTcpPlugin.java
@@ -17,9 +17,7 @@ import java.util.logging.Logger;
 
 import org.briarproject.api.TransportId;
 import org.briarproject.api.TransportProperties;
-import org.briarproject.api.crypto.PseudoRandom;
 import org.briarproject.api.plugins.duplex.DuplexPluginCallback;
-import org.briarproject.api.plugins.duplex.DuplexTransportConnection;
 import org.briarproject.util.StringUtils;
 
 class WanTcpPlugin extends TcpPlugin {
@@ -112,13 +110,4 @@ class WanTcpPlugin extends TcpPlugin {
 		p.put("port", String.valueOf(a.getPort()));
 		callback.mergeLocalProperties(p);
 	}
-
-	public boolean supportsInvitations() {
-		return false;
-	}
-
-	public DuplexTransportConnection createInvitationConnection(PseudoRandom r,
-			long timeout) {
-		throw new UnsupportedOperationException();
-	}
 }
diff --git a/briar-tests/src/org/briarproject/plugins/tcp/LanTcpClientTest.java b/briar-tests/src/org/briarproject/plugins/tcp/LanTcpClientTest.java
index 9e9fb77d59..8991467a91 100644
--- a/briar-tests/src/org/briarproject/plugins/tcp/LanTcpClientTest.java
+++ b/briar-tests/src/org/briarproject/plugins/tcp/LanTcpClientTest.java
@@ -9,9 +9,7 @@ import java.util.concurrent.Executors;
 import org.briarproject.api.ContactId;
 import org.briarproject.api.TransportConfig;
 import org.briarproject.api.TransportProperties;
-import org.briarproject.api.system.Clock;
 import org.briarproject.plugins.DuplexClientTest;
-import org.briarproject.system.SystemClock;
 
 // This is not a JUnit test - it has to be run manually while the server test
 // is running on another machine
@@ -21,15 +19,14 @@ public class LanTcpClientTest extends DuplexClientTest {
 			String serverPort) {
 		// Store the server's internal address and port
 		TransportProperties p = new TransportProperties();
-		p.put("internal", serverAddress);
+		p.put("address", serverAddress);
 		p.put("port", serverPort);
 		Map<ContactId, TransportProperties> remote =
 			Collections.singletonMap(contactId, p);
 		// Create the plugin
 		callback = new ClientCallback(new TransportConfig(),
 				new TransportProperties(), remote);
-		Clock clock = new SystemClock();
-		plugin = new LanTcpPlugin(executor, clock, callback, 0, 0, 0);
+		plugin = new LanTcpPlugin(executor, callback, 0, 0, 0);
 	}
 
 	public static void main(String[] args) throws Exception {
diff --git a/briar-tests/src/org/briarproject/plugins/tcp/LanTcpPluginTest.java b/briar-tests/src/org/briarproject/plugins/tcp/LanTcpPluginTest.java
index 832107d521..9094e7503e 100644
--- a/briar-tests/src/org/briarproject/plugins/tcp/LanTcpPluginTest.java
+++ b/briar-tests/src/org/briarproject/plugins/tcp/LanTcpPluginTest.java
@@ -20,9 +20,6 @@ import org.briarproject.api.TransportProperties;
 import org.briarproject.api.plugins.duplex.DuplexPlugin;
 import org.briarproject.api.plugins.duplex.DuplexPluginCallback;
 import org.briarproject.api.plugins.duplex.DuplexTransportConnection;
-import org.briarproject.api.system.Clock;
-import org.briarproject.system.SystemClock;
-
 import org.junit.Test;
 
 public class LanTcpPluginTest extends BriarTestCase {
@@ -35,9 +32,7 @@ public class LanTcpPluginTest extends BriarTestCase {
 		callback.local.put("address", "127.0.0.1");
 		callback.local.put("port", "0");
 		Executor executor = Executors.newCachedThreadPool();
-		Clock clock = new SystemClock();
-		DuplexPlugin plugin =
-				new LanTcpPlugin(executor, clock, callback, 0, 0, 0);
+		DuplexPlugin plugin = new LanTcpPlugin(executor, callback, 0, 0, 0);
 		plugin.start();
 		// The plugin should have bound a socket and stored the port number
 		assertTrue(callback.propertiesLatch.await(5, SECONDS));
@@ -62,9 +57,7 @@ public class LanTcpPluginTest extends BriarTestCase {
 	public void testOutgoingConnection() throws Exception {
 		Callback callback = new Callback();
 		Executor executor = Executors.newCachedThreadPool();
-		Clock clock = new SystemClock();
-		DuplexPlugin plugin =
-				new LanTcpPlugin(executor, clock, callback, 0, 0, 0);
+		DuplexPlugin plugin = new LanTcpPlugin(executor, callback, 0, 0, 0);
 		plugin.start();
 		// Listen on a local port
 		final ServerSocket ss = new ServerSocket();
diff --git a/briar-tests/src/org/briarproject/plugins/tcp/LanTcpServerTest.java b/briar-tests/src/org/briarproject/plugins/tcp/LanTcpServerTest.java
index 3506de7f71..ff96ae16d2 100644
--- a/briar-tests/src/org/briarproject/plugins/tcp/LanTcpServerTest.java
+++ b/briar-tests/src/org/briarproject/plugins/tcp/LanTcpServerTest.java
@@ -7,9 +7,7 @@ import java.util.concurrent.Executors;
 
 import org.briarproject.api.TransportConfig;
 import org.briarproject.api.TransportProperties;
-import org.briarproject.api.system.Clock;
 import org.briarproject.plugins.DuplexServerTest;
-import org.briarproject.system.SystemClock;
 
 // This is not a JUnit test - it has to be run manually while the client test
 // is running on another machine
@@ -19,8 +17,7 @@ public class LanTcpServerTest extends DuplexServerTest {
 		callback = new ServerCallback(new TransportConfig(),
 				new TransportProperties(),
 				Collections.singletonMap(contactId, new TransportProperties()));
-		Clock clock = new SystemClock();
-		plugin = new LanTcpPlugin(executor, clock, callback, 0, 0, 0);
+		plugin = new LanTcpPlugin(executor, callback, 0, 0, 0);
 	}
 
 	public static void main(String[] args) throws Exception {
-- 
GitLab