diff --git a/bramble-android/src/main/java/org/briarproject/bramble/plugin/droidtooth/DroidtoothPlugin.java b/bramble-android/src/main/java/org/briarproject/bramble/plugin/droidtooth/DroidtoothPlugin.java
index 5bb975e1a6b0390725f71c60b950c00f05fb72e6..a5a28e005452d06230d34a04e27c25588bf7413d 100644
--- a/bramble-android/src/main/java/org/briarproject/bramble/plugin/droidtooth/DroidtoothPlugin.java
+++ b/bramble-android/src/main/java/org/briarproject/bramble/plugin/droidtooth/DroidtoothPlugin.java
@@ -11,7 +11,6 @@ import android.content.IntentFilter;
 
 import org.briarproject.bramble.api.FormatException;
 import org.briarproject.bramble.api.contact.ContactId;
-import org.briarproject.bramble.api.crypto.PseudoRandom;
 import org.briarproject.bramble.api.data.BdfList;
 import org.briarproject.bramble.api.keyagreement.KeyAgreementConnection;
 import org.briarproject.bramble.api.keyagreement.KeyAgreementListener;
@@ -30,23 +29,14 @@ import org.briarproject.bramble.util.StringUtils;
 
 import java.io.Closeable;
 import java.io.IOException;
-import java.io.InputStream;
 import java.security.SecureRandom;
-import java.util.ArrayList;
 import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.UUID;
 import java.util.concurrent.Callable;
-import java.util.concurrent.CompletionService;
-import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Executor;
-import java.util.concurrent.ExecutorCompletionService;
-import java.util.concurrent.Future;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.logging.Logger;
 
@@ -61,8 +51,6 @@ import static android.bluetooth.BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERA
 import static android.bluetooth.BluetoothAdapter.SCAN_MODE_NONE;
 import static android.bluetooth.BluetoothAdapter.STATE_OFF;
 import static android.bluetooth.BluetoothAdapter.STATE_ON;
-import static android.bluetooth.BluetoothDevice.EXTRA_DEVICE;
-import static java.util.concurrent.TimeUnit.MILLISECONDS;
 import static java.util.logging.Level.INFO;
 import static java.util.logging.Level.WARNING;
 import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.TRANSPORT_ID_BLUETOOTH;
@@ -79,10 +67,6 @@ class DroidtoothPlugin implements DuplexPlugin {
 
 	private static final Logger LOG =
 			Logger.getLogger(DroidtoothPlugin.class.getName());
-	private static final String FOUND =
-			"android.bluetooth.device.action.FOUND";
-	private static final String DISCOVERY_FINISHED =
-			"android.bluetooth.adapter.action.DISCOVERY_FINISHED";
 
 	private final Executor ioExecutor;
 	private final AndroidExecutor androidExecutor;
@@ -374,90 +358,6 @@ class DroidtoothPlugin implements DuplexPlugin {
 		return new DroidtoothTransportConnection(this, s);
 	}
 
-	@Override
-	public boolean supportsInvitations() {
-		return true;
-	}
-
-	@Override
-	public DuplexTransportConnection createInvitationConnection(PseudoRandom r,
-			long timeout, boolean alice) {
-		if (!isRunning()) return null;
-		// Use the invitation codes to generate the UUID
-		byte[] b = r.nextBytes(UUID_BYTES);
-		UUID uuid = UUID.nameUUIDFromBytes(b);
-		if (LOG.isLoggable(INFO)) LOG.info("Invitation UUID " + uuid);
-		// Bind a server socket for receiving invitation connections
-		BluetoothServerSocket ss;
-		try {
-			ss = adapter.listenUsingInsecureRfcommWithServiceRecord(
-					"RFCOMM", uuid);
-		} catch (IOException e) {
-			if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
-			return null;
-		}
-		// Create the background tasks
-		CompletionService<BluetoothSocket> complete =
-				new ExecutorCompletionService<>(ioExecutor);
-		List<Future<BluetoothSocket>> futures = new ArrayList<>();
-		if (alice) {
-			// Return the first connected socket
-			futures.add(complete.submit(new ListeningTask(ss)));
-			futures.add(complete.submit(new DiscoveryTask(uuid.toString())));
-		} else {
-			// Return the first socket with readable data
-			futures.add(complete.submit(new ReadableTask(
-					new ListeningTask(ss))));
-			futures.add(complete.submit(new ReadableTask(
-					new DiscoveryTask(uuid.toString()))));
-		}
-		BluetoothSocket chosen = null;
-		try {
-			Future<BluetoothSocket> f = complete.poll(timeout, MILLISECONDS);
-			if (f == null) return null; // No task completed within the timeout
-			chosen = f.get();
-			return new DroidtoothTransportConnection(this, chosen);
-		} catch (InterruptedException e) {
-			LOG.info("Interrupted while exchanging invitations");
-			Thread.currentThread().interrupt();
-			return null;
-		} catch (ExecutionException e) {
-			if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
-			return null;
-		} finally {
-			// Closing the socket will terminate the listener task
-			tryToClose(ss);
-			closeSockets(futures, chosen);
-		}
-	}
-
-	private void closeSockets(final List<Future<BluetoothSocket>> futures,
-			@Nullable final BluetoothSocket chosen) {
-		ioExecutor.execute(new Runnable() {
-			@Override
-			public void run() {
-				for (Future<BluetoothSocket> f : futures) {
-					try {
-						if (f.cancel(true)) {
-							LOG.info("Cancelled task");
-						} else {
-							BluetoothSocket s = f.get();
-							if (s != null && s != chosen) {
-								LOG.info("Closing unwanted socket");
-								s.close();
-							}
-						}
-					} catch (InterruptedException e) {
-						LOG.info("Interrupted while closing sockets");
-						return;
-					} catch (ExecutionException | IOException e) {
-						if (LOG.isLoggable(INFO)) LOG.info(e.toString());
-					}
-				}
-			}
-		});
-	}
-
 	@Override
 	public boolean supportsKeyAgreement() {
 		return true;
@@ -472,7 +372,7 @@ class DroidtoothPlugin implements DuplexPlugin {
 		// No truncation necessary because COMMIT_LENGTH = 16
 		UUID uuid = UUID.nameUUIDFromBytes(commitment);
 		if (LOG.isLoggable(INFO)) LOG.info("Key agreement UUID " + uuid);
-		// Bind a server socket for receiving invitation connections
+		// Bind a server socket for receiving key agreement connections
 		BluetoothServerSocket ss;
 		try {
 			ss = adapter.listenUsingInsecureRfcommWithServiceRecord(
@@ -536,115 +436,6 @@ class DroidtoothPlugin implements DuplexPlugin {
 		}
 	}
 
-	private class DiscoveryTask implements Callable<BluetoothSocket> {
-
-		private final String uuid;
-
-		private DiscoveryTask(String uuid) {
-			this.uuid = uuid;
-		}
-
-		@Override
-		public BluetoothSocket call() throws Exception {
-			// Repeat discovery until we connect or get interrupted
-			while (true) {
-				// Discover nearby devices
-				LOG.info("Discovering nearby devices");
-				List<String> addresses = discoverDevices();
-				if (addresses.isEmpty()) {
-					LOG.info("No devices discovered");
-					continue;
-				}
-				// Connect to any device with the right UUID
-				for (String address : addresses) {
-					BluetoothSocket s = connect(address, uuid);
-					if (s != null) {
-						LOG.info("Outgoing connection");
-						return s;
-					}
-				}
-			}
-		}
-
-		private List<String> discoverDevices() throws InterruptedException {
-			IntentFilter filter = new IntentFilter();
-			filter.addAction(FOUND);
-			filter.addAction(DISCOVERY_FINISHED);
-			DiscoveryReceiver disco = new DiscoveryReceiver();
-			appContext.registerReceiver(disco, filter);
-			LOG.info("Starting discovery");
-			adapter.startDiscovery();
-			return disco.waitForAddresses();
-		}
-	}
-
-	private static class DiscoveryReceiver extends BroadcastReceiver {
-
-		private final CountDownLatch finished = new CountDownLatch(1);
-		private final List<String> addresses = new CopyOnWriteArrayList<>();
-
-		@Override
-		public void onReceive(Context ctx, Intent intent) {
-			String action = intent.getAction();
-			if (action.equals(DISCOVERY_FINISHED)) {
-				LOG.info("Discovery finished");
-				ctx.unregisterReceiver(this);
-				finished.countDown();
-			} else if (action.equals(FOUND)) {
-				BluetoothDevice d = intent.getParcelableExtra(EXTRA_DEVICE);
-				if (LOG.isLoggable(INFO)) {
-					LOG.info("Discovered device: " +
-							scrubMacAddress(d.getAddress()));
-				}
-				addresses.add(d.getAddress());
-			}
-		}
-
-		private List<String> waitForAddresses() throws InterruptedException {
-			finished.await();
-			List<String> shuffled = new ArrayList<>(addresses);
-			Collections.shuffle(shuffled);
-			return shuffled;
-		}
-	}
-
-	private static class ListeningTask implements Callable<BluetoothSocket> {
-
-		private final BluetoothServerSocket serverSocket;
-
-		private ListeningTask(BluetoothServerSocket serverSocket) {
-			this.serverSocket = serverSocket;
-		}
-
-		@Override
-		public BluetoothSocket call() throws IOException {
-			BluetoothSocket s = serverSocket.accept();
-			LOG.info("Incoming connection");
-			return s;
-		}
-	}
-
-	private static class ReadableTask implements Callable<BluetoothSocket> {
-
-		private final Callable<BluetoothSocket> connectionTask;
-
-		private ReadableTask(Callable<BluetoothSocket> connectionTask) {
-			this.connectionTask = connectionTask;
-		}
-
-		@Override
-		public BluetoothSocket call() throws Exception {
-			BluetoothSocket s = connectionTask.call();
-			InputStream in = s.getInputStream();
-			while (in.available() == 0) {
-				LOG.info("Waiting for data");
-				Thread.sleep(1000);
-			}
-			LOG.info("Data available");
-			return s;
-		}
-	}
-
 	private class BluetoothKeyAgreementListener extends KeyAgreementListener {
 
 		private final BluetoothServerSocket ss;
diff --git a/bramble-android/src/main/java/org/briarproject/bramble/plugin/tor/TorPlugin.java b/bramble-android/src/main/java/org/briarproject/bramble/plugin/tor/TorPlugin.java
index 9c3609ed65ceb2a6f8438869ed5ce38f9a9e92dc..69eb13d5a43e36f8444c1c0bf83d9d825500e02e 100644
--- a/bramble-android/src/main/java/org/briarproject/bramble/plugin/tor/TorPlugin.java
+++ b/bramble-android/src/main/java/org/briarproject/bramble/plugin/tor/TorPlugin.java
@@ -17,7 +17,6 @@ import net.freehaven.tor.control.EventHandler;
 import net.freehaven.tor.control.TorControlConnection;
 
 import org.briarproject.bramble.api.contact.ContactId;
-import org.briarproject.bramble.api.crypto.PseudoRandom;
 import org.briarproject.bramble.api.data.BdfList;
 import org.briarproject.bramble.api.event.Event;
 import org.briarproject.bramble.api.event.EventListener;
@@ -589,17 +588,6 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
 		}
 	}
 
-	@Override
-	public boolean supportsInvitations() {
-		return false;
-	}
-
-	@Override
-	public DuplexTransportConnection createInvitationConnection(PseudoRandom r,
-			long timeout, boolean alice) {
-		throw new UnsupportedOperationException();
-	}
-
 	@Override
 	public boolean supportsKeyAgreement() {
 		return false;
diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/crypto/CryptoComponent.java b/bramble-api/src/main/java/org/briarproject/bramble/api/crypto/CryptoComponent.java
index 3d610c8506d2195e68788f97574f061c05ddb6c1..9579e2e9cee8625eaa2df0503e861ca7d9e65199 100644
--- a/bramble-api/src/main/java/org/briarproject/bramble/api/crypto/CryptoComponent.java
+++ b/bramble-api/src/main/java/org/briarproject/bramble/api/crypto/CryptoComponent.java
@@ -10,8 +10,6 @@ public interface CryptoComponent {
 
 	SecretKey generateSecretKey();
 
-	PseudoRandom getPseudoRandom(int seed1, int seed2);
-
 	SecureRandom getSecureRandom();
 
 	KeyPair generateAgreementKeyPair();
@@ -24,15 +22,6 @@ public interface CryptoComponent {
 
 	KeyParser getMessageKeyParser();
 
-	/** Generates a random invitation code. */
-	int generateBTInvitationCode();
-
-	/**
-	 * Derives a confirmation code from the given master secret.
-	 * @param alice whether the code is for use by Alice or Bob.
-	 */
-	int deriveBTConfirmationCode(SecretKey master, boolean alice);
-
 	/**
 	 * Derives a stream header key from the given master secret.
 	 * @param alice whether the key is for use by Alice or Bob.
diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/crypto/PseudoRandom.java b/bramble-api/src/main/java/org/briarproject/bramble/api/crypto/PseudoRandom.java
deleted file mode 100644
index 8e61027d6f15201a1066c3529c6de99c884c3d2e..0000000000000000000000000000000000000000
--- a/bramble-api/src/main/java/org/briarproject/bramble/api/crypto/PseudoRandom.java
+++ /dev/null
@@ -1,12 +0,0 @@
-package org.briarproject.bramble.api.crypto;
-
-import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
-
-/**
- * A deterministic pseudo-random number generator.
- */
-@NotNullByDefault
-public interface PseudoRandom {
-
-	byte[] nextBytes(int bytes);
-}
diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/crypto/StreamDecrypterFactory.java b/bramble-api/src/main/java/org/briarproject/bramble/api/crypto/StreamDecrypterFactory.java
index b07c3239a1cb63232392f5e6d7fc5fa64de3974c..beecd1789f00230fc0a1412f99baff1a0be4ae93 100644
--- a/bramble-api/src/main/java/org/briarproject/bramble/api/crypto/StreamDecrypterFactory.java
+++ b/bramble-api/src/main/java/org/briarproject/bramble/api/crypto/StreamDecrypterFactory.java
@@ -14,8 +14,9 @@ public interface StreamDecrypterFactory {
 	StreamDecrypter createStreamDecrypter(InputStream in, StreamContext ctx);
 
 	/**
-	 * Creates a {@link StreamDecrypter} for decrypting an invitation stream.
+	 * Creates a {@link StreamDecrypter} for decrypting a contact exchange
+	 * stream.
 	 */
-	StreamDecrypter createInvitationStreamDecrypter(InputStream in,
+	StreamDecrypter createContactExchangeStreamDecrypter(InputStream in,
 			SecretKey headerKey);
 }
diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/crypto/StreamEncrypterFactory.java b/bramble-api/src/main/java/org/briarproject/bramble/api/crypto/StreamEncrypterFactory.java
index 74340133740b6c6d1243f3b402c0591da7b5e5d5..03ad5e143987eed6e9f9488258f615a20135061e 100644
--- a/bramble-api/src/main/java/org/briarproject/bramble/api/crypto/StreamEncrypterFactory.java
+++ b/bramble-api/src/main/java/org/briarproject/bramble/api/crypto/StreamEncrypterFactory.java
@@ -14,8 +14,9 @@ public interface StreamEncrypterFactory {
 	StreamEncrypter createStreamEncrypter(OutputStream out, StreamContext ctx);
 
 	/**
-	 * Creates a {@link StreamEncrypter} for encrypting an invitation stream.
+	 * Creates a {@link StreamEncrypter} for encrypting a contact exchange
+	 * stream.
 	 */
-	StreamEncrypter createInvitationStreamEncrypter(OutputStream out,
+	StreamEncrypter createContactExchangeStreamDecrypter(OutputStream out,
 			SecretKey headerKey);
 }
diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/invitation/InvitationConstants.java b/bramble-api/src/main/java/org/briarproject/bramble/api/invitation/InvitationConstants.java
deleted file mode 100644
index f7c6ed3319bb00ce0c59ea1d82541d6209ba6752..0000000000000000000000000000000000000000
--- a/bramble-api/src/main/java/org/briarproject/bramble/api/invitation/InvitationConstants.java
+++ /dev/null
@@ -1,20 +0,0 @@
-package org.briarproject.bramble.api.invitation;
-
-public interface InvitationConstants {
-
-	/**
-	 * The connection timeout in milliseconds.
-	 */
-	long CONNECTION_TIMEOUT = 60 * 1000;
-
-	/**
-	 * The confirmation timeout in milliseconds.
-	 */
-	long CONFIRMATION_TIMEOUT = 60 * 1000;
-
-	/**
-	 * The number of bits in an invitation or confirmation code. Codes must fit
-	 * into six decimal digits.
-	 */
-	int CODE_BITS = 19;
-}
diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/invitation/InvitationListener.java b/bramble-api/src/main/java/org/briarproject/bramble/api/invitation/InvitationListener.java
deleted file mode 100644
index acf545f1a77368cf72ddc30292c06a92f97eb965..0000000000000000000000000000000000000000
--- a/bramble-api/src/main/java/org/briarproject/bramble/api/invitation/InvitationListener.java
+++ /dev/null
@@ -1,47 +0,0 @@
-package org.briarproject.bramble.api.invitation;
-
-/**
- * An interface for receiving updates about the state of an
- * {@link InvitationTask}.
- */
-public interface InvitationListener {
-
-	/** Called if a connection to the remote peer is established. */
-	void connectionSucceeded();
-
-	/**
-	 * Called if a connection to the remote peer cannot be established. This
-	 * indicates that the protocol has ended unsuccessfully.
-	 */
-	void connectionFailed();
-
-	/** Called if key agreement with the remote peer succeeds. */
-	void keyAgreementSucceeded(int localCode, int remoteCode);
-
-	/**
-	 * Called if key agreement with the remote peer fails or the connection is
-	 * lost. This indicates that the protocol has ended unsuccessfully.
-	 */
-	void keyAgreementFailed();
-
-	/** Called if the remote peer's confirmation check succeeds. */
-	void remoteConfirmationSucceeded();
-
-	/**
-	 * Called if remote peer's confirmation check fails or the connection is
-	 * lost. This indicates that the protocol has ended unsuccessfully.
-	 */
-	void remoteConfirmationFailed();
-
-	/**
-	 * Called if the exchange of pseudonyms succeeds. This indicates that the
-	 * protocol has ended successfully.
-	 */
-	void pseudonymExchangeSucceeded(String remoteName);
-
-	/**
-	 * Called if the exchange of pseudonyms fails or the connection is lost.
-	 * This indicates that the protocol has ended unsuccessfully.
-	 */
-	void pseudonymExchangeFailed();
-}
diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/invitation/InvitationState.java b/bramble-api/src/main/java/org/briarproject/bramble/api/invitation/InvitationState.java
deleted file mode 100644
index 456230837ca89048fdee981c85bb34b92808b86d..0000000000000000000000000000000000000000
--- a/bramble-api/src/main/java/org/briarproject/bramble/api/invitation/InvitationState.java
+++ /dev/null
@@ -1,85 +0,0 @@
-package org.briarproject.bramble.api.invitation;
-
-import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
-
-import javax.annotation.Nullable;
-import javax.annotation.concurrent.Immutable;
-
-/**
- * A snapshot of the state of an {@link InvitationTask}.
- */
-@Immutable
-@NotNullByDefault
-public class InvitationState {
-
-	private final int localInvitationCode, remoteInvitationCode;
-	private final int localConfirmationCode, remoteConfirmationCode;
-	private final boolean connected, connectionFailed;
-	private final boolean localCompared, remoteCompared;
-	private final boolean localMatched, remoteMatched;
-	@Nullable
-	private final String contactName;
-
-	public InvitationState(int localInvitationCode, int remoteInvitationCode,
-			int localConfirmationCode, int remoteConfirmationCode,
-			boolean connected, boolean connectionFailed, boolean localCompared,
-			boolean remoteCompared, boolean localMatched,
-			boolean remoteMatched, @Nullable String contactName) {
-		this.localInvitationCode = localInvitationCode;
-		this.remoteInvitationCode = remoteInvitationCode;
-		this.localConfirmationCode = localConfirmationCode;
-		this.remoteConfirmationCode = remoteConfirmationCode;
-		this.connected = connected;
-		this.connectionFailed = connectionFailed;
-		this.localCompared = localCompared;
-		this.remoteCompared = remoteCompared;
-		this.localMatched = localMatched;
-		this.remoteMatched = remoteMatched;
-		this.contactName = contactName;
-	}
-
-	public int getLocalInvitationCode() {
-		return localInvitationCode;
-	}
-
-	public int getRemoteInvitationCode() {
-		return remoteInvitationCode;
-	}
-
-	public int getLocalConfirmationCode() {
-		return localConfirmationCode;
-	}
-
-	public int getRemoteConfirmationCode() {
-		return remoteConfirmationCode;
-	}
-
-	public boolean getConnected() {
-		return connected;
-	}
-
-	public boolean getConnectionFailed() {
-		return connectionFailed;
-	}
-
-	public boolean getLocalCompared() {
-		return localCompared;
-	}
-
-	public boolean getRemoteCompared() {
-		return remoteCompared;
-	}
-
-	public boolean getLocalMatched() {
-		return localMatched;
-	}
-
-	public boolean getRemoteMatched() {
-		return remoteMatched;
-	}
-
-	@Nullable
-	public String getContactName() {
-		return contactName;
-	}
-}
diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/invitation/InvitationTask.java b/bramble-api/src/main/java/org/briarproject/bramble/api/invitation/InvitationTask.java
deleted file mode 100644
index b09d940f308f678b783bf752b61ee9f0eca4823f..0000000000000000000000000000000000000000
--- a/bramble-api/src/main/java/org/briarproject/bramble/api/invitation/InvitationTask.java
+++ /dev/null
@@ -1,38 +0,0 @@
-package org.briarproject.bramble.api.invitation;
-
-import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
-
-/**
- * A task for exchanging invitations with a remote peer.
- */
-@NotNullByDefault
-public interface InvitationTask {
-
-	/**
-	 * Adds a listener to be informed of state changes and returns the
-	 * task's current state.
-	 */
-	InvitationState addListener(InvitationListener l);
-
-	/**
-	 * Removes the given listener.
-	 */
-	void removeListener(InvitationListener l);
-
-	/**
-	 * Asynchronously starts the connection process.
-	 */
-	void connect();
-
-	/**
-	 * Asynchronously informs the remote peer that the local peer's
-	 * confirmation codes matched.
-	 */
-	void localConfirmationSucceeded();
-
-	/**
-	 * Asynchronously informs the remote peer that the local peer's
-	 * confirmation codes did not match.
-	 */
-	void localConfirmationFailed();
-}
diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/invitation/InvitationTaskFactory.java b/bramble-api/src/main/java/org/briarproject/bramble/api/invitation/InvitationTaskFactory.java
deleted file mode 100644
index 76f94908a3af51af770304922f5d554ec90f5cc5..0000000000000000000000000000000000000000
--- a/bramble-api/src/main/java/org/briarproject/bramble/api/invitation/InvitationTaskFactory.java
+++ /dev/null
@@ -1,15 +0,0 @@
-package org.briarproject.bramble.api.invitation;
-
-import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
-
-/**
- * Creates tasks for exchanging invitations with remote peers.
- */
-@NotNullByDefault
-public interface InvitationTaskFactory {
-
-	/**
-	 * Creates a task using the given local and remote invitation codes.
-	 */
-	InvitationTask createTask(int localCode, int remoteCode);
-}
diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/PluginManager.java b/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/PluginManager.java
index 1056a6438c967a6470a3887a1637ad4b7ec03c47..32c3a8012573916e922f630bee005eff1ff1ed06 100644
--- a/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/PluginManager.java
+++ b/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/PluginManager.java
@@ -32,11 +32,6 @@ public interface PluginManager {
 	 */
 	Collection<DuplexPlugin> getDuplexPlugins();
 
-	/**
-	 * Returns any duplex plugins that support invitations.
-	 */
-	Collection<DuplexPlugin> getInvitationPlugins();
-
 	/**
 	 * Returns any duplex plugins that support key agreement.
 	 */
diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/duplex/DuplexPlugin.java b/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/duplex/DuplexPlugin.java
index f2c2027f902b0b7ba3a39d3d019dc056c3f5cb35..83fc642487260d3401fbbd26fb14a5cccd3ac1ad 100644
--- a/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/duplex/DuplexPlugin.java
+++ b/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/duplex/DuplexPlugin.java
@@ -1,7 +1,6 @@
 package org.briarproject.bramble.api.plugin.duplex;
 
 import org.briarproject.bramble.api.contact.ContactId;
-import org.briarproject.bramble.api.crypto.PseudoRandom;
 import org.briarproject.bramble.api.data.BdfList;
 import org.briarproject.bramble.api.keyagreement.KeyAgreementListener;
 import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@@ -23,20 +22,6 @@ public interface DuplexPlugin extends Plugin {
 	@Nullable
 	DuplexTransportConnection createConnection(ContactId c);
 
-	/**
-	 * Returns true if the plugin supports exchanging invitations.
-	 */
-	boolean supportsInvitations();
-
-	/**
-	 * Attempts to create and return an invitation connection to the remote
-	 * peer. Returns null if no connection can be established within the given
-	 * time.
-	 */
-	@Nullable
-	DuplexTransportConnection createInvitationConnection(PseudoRandom r,
-			long timeout, boolean alice);
-
 	/**
 	 * Returns true if the plugin supports short-range key agreement.
 	 */
diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/transport/StreamReaderFactory.java b/bramble-api/src/main/java/org/briarproject/bramble/api/transport/StreamReaderFactory.java
index 44e31a671b6019567450eded400300010c8e3f37..87823679b0030c43a1084319efeff20d975d9623 100644
--- a/bramble-api/src/main/java/org/briarproject/bramble/api/transport/StreamReaderFactory.java
+++ b/bramble-api/src/main/java/org/briarproject/bramble/api/transport/StreamReaderFactory.java
@@ -15,9 +15,9 @@ public interface StreamReaderFactory {
 	InputStream createStreamReader(InputStream in, StreamContext ctx);
 
 	/**
-	 * Creates an {@link InputStream InputStream} for reading from an
-	 * invitation stream.
+	 * Creates an {@link InputStream InputStream} for reading from a contact
+	 * exchangestream.
 	 */
-	InputStream createInvitationStreamReader(InputStream in,
+	InputStream createContactExchangeStreamReader(InputStream in,
 			SecretKey headerKey);
 }
diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/transport/StreamWriterFactory.java b/bramble-api/src/main/java/org/briarproject/bramble/api/transport/StreamWriterFactory.java
index 134bde50e7924332543c0d138f4ddd2193caf516..3277acee08fcad4b7f34bdd216e3ead3cb6e1f8c 100644
--- a/bramble-api/src/main/java/org/briarproject/bramble/api/transport/StreamWriterFactory.java
+++ b/bramble-api/src/main/java/org/briarproject/bramble/api/transport/StreamWriterFactory.java
@@ -15,9 +15,9 @@ public interface StreamWriterFactory {
 	OutputStream createStreamWriter(OutputStream out, StreamContext ctx);
 
 	/**
-	 * Creates an {@link OutputStream OutputStream} for writing to an
-	 * invitation stream.
+	 * Creates an {@link OutputStream OutputStream} for writing to a contact
+	 * exchange stream.
 	 */
-	OutputStream createInvitationStreamWriter(OutputStream out,
+	OutputStream createContactExchangeStreamWriter(OutputStream out,
 			SecretKey headerKey);
 }
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/BrambleCoreModule.java b/bramble-core/src/main/java/org/briarproject/bramble/BrambleCoreModule.java
index eb9c2eb5d9f363da76e10abf6d72ea4b900c8164..365b50de915b71198f674cdaf961ad99246c5500 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/BrambleCoreModule.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/BrambleCoreModule.java
@@ -8,7 +8,6 @@ import org.briarproject.bramble.db.DatabaseExecutorModule;
 import org.briarproject.bramble.db.DatabaseModule;
 import org.briarproject.bramble.event.EventModule;
 import org.briarproject.bramble.identity.IdentityModule;
-import org.briarproject.bramble.invitation.InvitationModule;
 import org.briarproject.bramble.keyagreement.KeyAgreementModule;
 import org.briarproject.bramble.lifecycle.LifecycleModule;
 import org.briarproject.bramble.plugin.PluginModule;
@@ -32,7 +31,6 @@ import dagger.Module;
 		DatabaseExecutorModule.class,
 		EventModule.class,
 		IdentityModule.class,
-		InvitationModule.class,
 		KeyAgreementModule.class,
 		LifecycleModule.class,
 		PluginModule.class,
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/contact/ContactExchangeTaskImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/contact/ContactExchangeTaskImpl.java
index e33b1deb0c2ad90d8b12dd6eca31f98d056b53ef..d97c8c9eef8443c203547e1db1f7b0925b40bc73 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/contact/ContactExchangeTaskImpl.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/contact/ContactExchangeTaskImpl.java
@@ -80,7 +80,7 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
 	private volatile boolean alice;
 
 	@Inject
-	public ContactExchangeTaskImpl(DatabaseComponent db,
+	ContactExchangeTaskImpl(DatabaseComponent db,
 			AuthorFactory authorFactory, BdfReaderFactory bdfReaderFactory,
 			BdfWriterFactory bdfWriterFactory, Clock clock,
 			ConnectionManager connectionManager, ContactManager contactManager,
@@ -146,12 +146,12 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
 
 		// Create the readers
 		InputStream streamReader =
-				streamReaderFactory.createInvitationStreamReader(in,
+				streamReaderFactory.createContactExchangeStreamReader(in,
 						alice ? bobHeaderKey : aliceHeaderKey);
 		BdfReader r = bdfReaderFactory.createReader(streamReader);
 		// Create the writers
 		OutputStream streamWriter =
-				streamWriterFactory.createInvitationStreamWriter(out,
+				streamWriterFactory.createContactExchangeStreamWriter(out,
 						alice ? aliceHeaderKey : bobHeaderKey);
 		BdfWriter w = bdfWriterFactory.createWriter(streamWriter);
 
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/crypto/CryptoComponentImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/crypto/CryptoComponentImpl.java
index f0c26edd9ef9e59915e68cb71d5524dad7c5a1c0..6c97f1c2cd8bfb704ee942a8e66358c23ddd63e1 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/crypto/CryptoComponentImpl.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/crypto/CryptoComponentImpl.java
@@ -4,7 +4,6 @@ import org.briarproject.bramble.api.crypto.CryptoComponent;
 import org.briarproject.bramble.api.crypto.KeyPair;
 import org.briarproject.bramble.api.crypto.KeyParser;
 import org.briarproject.bramble.api.crypto.PrivateKey;
-import org.briarproject.bramble.api.crypto.PseudoRandom;
 import org.briarproject.bramble.api.crypto.PublicKey;
 import org.briarproject.bramble.api.crypto.SecretKey;
 import org.briarproject.bramble.api.plugin.TransportId;
@@ -41,7 +40,6 @@ import java.util.logging.Logger;
 import javax.inject.Inject;
 
 import static java.util.logging.Level.INFO;
-import static org.briarproject.bramble.api.invitation.InvitationConstants.CODE_BITS;
 import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.COMMIT_LENGTH;
 import static org.briarproject.bramble.api.transport.TransportConstants.TAG_LENGTH;
 import static org.briarproject.bramble.crypto.EllipticCurveConstants.PARAMETERS;
@@ -68,9 +66,6 @@ class CryptoComponentImpl implements CryptoComponent {
 		return s.getBytes(Charset.forName("US-ASCII"));
 	}
 
-	// KDF labels for bluetooth confirmation code derivation
-	private static final byte[] BT_A_CONFIRM = ascii("ALICE_CONFIRMATION_CODE");
-	private static final byte[] BT_B_CONFIRM = ascii("BOB_CONFIRMATION_CODE");
 	// KDF labels for contact exchange stream header key derivation
 	private static final byte[] A_INVITE = ascii("ALICE_INVITATION_KEY");
 	private static final byte[] B_INVITE = ascii("BOB_INVITATION_KEY");
@@ -171,14 +166,6 @@ class CryptoComponentImpl implements CryptoComponent {
 		return new SecretKey(b);
 	}
 
-	@Override
-	public PseudoRandom getPseudoRandom(int seed1, int seed2) {
-		byte[] seed = new byte[INT_32_BYTES * 2];
-		ByteUtils.writeUint32(seed1, seed, 0);
-		ByteUtils.writeUint32(seed2, seed, INT_32_BYTES);
-		return new PseudoRandomImpl(seed);
-	}
-
 	@Override
 	public SecureRandom getSecureRandom() {
 		return secureRandom;
@@ -250,20 +237,6 @@ class CryptoComponentImpl implements CryptoComponent {
 		return messageEncrypter.getKeyParser();
 	}
 
-	@Override
-	public int generateBTInvitationCode() {
-		int codeBytes = (CODE_BITS + 7) / 8;
-		byte[] random = new byte[codeBytes];
-		secureRandom.nextBytes(random);
-		return ByteUtils.readUint(random, CODE_BITS);
-	}
-
-	@Override
-	public int deriveBTConfirmationCode(SecretKey master, boolean alice) {
-		byte[] b = macKdf(master, alice ? BT_A_CONFIRM : BT_B_CONFIRM);
-		return ByteUtils.readUint(b, CODE_BITS);
-	}
-
 	@Override
 	public SecretKey deriveHeaderKey(SecretKey master,
 			boolean alice) {
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/crypto/StreamDecrypterFactoryImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/crypto/StreamDecrypterFactoryImpl.java
index 49fd6ba2eca274a786a6a6d6f816d236d5ac44e5..aac3e504ecededad21eebf61d5b23e781cf0b0c0 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/crypto/StreamDecrypterFactoryImpl.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/crypto/StreamDecrypterFactoryImpl.java
@@ -32,7 +32,7 @@ class StreamDecrypterFactoryImpl implements StreamDecrypterFactory {
 	}
 
 	@Override
-	public StreamDecrypter createInvitationStreamDecrypter(InputStream in,
+	public StreamDecrypter createContactExchangeStreamDecrypter(InputStream in,
 			SecretKey headerKey) {
 		return new StreamDecrypterImpl(in, cipherProvider.get(), 0, headerKey);
 	}
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/crypto/StreamEncrypterFactoryImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/crypto/StreamEncrypterFactoryImpl.java
index be7f553de2d4fc2284496069dada1d0dea657267..0d9b4a0fc2bf65d2732d649575ed63bdbcd4d764 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/crypto/StreamEncrypterFactoryImpl.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/crypto/StreamEncrypterFactoryImpl.java
@@ -46,8 +46,8 @@ class StreamEncrypterFactoryImpl implements StreamEncrypterFactory {
 	}
 
 	@Override
-	public StreamEncrypter createInvitationStreamEncrypter(OutputStream out,
-			SecretKey headerKey) {
+	public StreamEncrypter createContactExchangeStreamDecrypter(
+			OutputStream out, SecretKey headerKey) {
 		AuthenticatedCipher cipher = cipherProvider.get();
 		byte[] streamHeaderNonce = new byte[STREAM_HEADER_NONCE_LENGTH];
 		crypto.getSecureRandom().nextBytes(streamHeaderNonce);
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/invitation/AliceConnector.java b/bramble-core/src/main/java/org/briarproject/bramble/invitation/AliceConnector.java
deleted file mode 100644
index 871db9b4ce9748fb8e6a1705c5cf2a34193d6327..0000000000000000000000000000000000000000
--- a/bramble-core/src/main/java/org/briarproject/bramble/invitation/AliceConnector.java
+++ /dev/null
@@ -1,119 +0,0 @@
-package org.briarproject.bramble.invitation;
-
-import org.briarproject.bramble.api.contact.ContactExchangeTask;
-import org.briarproject.bramble.api.crypto.CryptoComponent;
-import org.briarproject.bramble.api.crypto.PseudoRandom;
-import org.briarproject.bramble.api.crypto.SecretKey;
-import org.briarproject.bramble.api.data.BdfReader;
-import org.briarproject.bramble.api.data.BdfReaderFactory;
-import org.briarproject.bramble.api.data.BdfWriter;
-import org.briarproject.bramble.api.data.BdfWriterFactory;
-import org.briarproject.bramble.api.identity.LocalAuthor;
-import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
-import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
-import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.security.GeneralSecurityException;
-import java.util.logging.Logger;
-
-import static java.util.logging.Level.INFO;
-import static java.util.logging.Level.WARNING;
-
-/**
- * A connection thread for the peer being Alice in the invitation protocol.
- */
-@NotNullByDefault
-class AliceConnector extends Connector {
-
-	private static final Logger LOG =
-			Logger.getLogger(AliceConnector.class.getName());
-
-	AliceConnector(CryptoComponent crypto, BdfReaderFactory bdfReaderFactory,
-			BdfWriterFactory bdfWriterFactory,
-			ContactExchangeTask contactExchangeTask, ConnectorGroup group,
-			DuplexPlugin plugin, LocalAuthor localAuthor, PseudoRandom random) {
-		super(crypto, bdfReaderFactory, bdfWriterFactory, contactExchangeTask,
-				group, plugin, localAuthor, random);
-	}
-
-	@Override
-	public void run() {
-		// Create an incoming or outgoing connection
-		DuplexTransportConnection conn = createInvitationConnection(true);
-		if (conn == null) return;
-		if (LOG.isLoggable(INFO)) LOG.info(pluginName + " connected");
-		// Don't proceed with more than one connection
-		if (group.getAndSetConnected()) {
-			if (LOG.isLoggable(INFO)) LOG.info(pluginName + " redundant");
-			tryToClose(conn, false);
-			return;
-		}
-		// Carry out the key agreement protocol
-		InputStream in;
-		OutputStream out;
-		BdfReader r;
-		BdfWriter w;
-		SecretKey master;
-		try {
-			in = conn.getReader().getInputStream();
-			out = conn.getWriter().getOutputStream();
-			r = bdfReaderFactory.createReader(in);
-			w = bdfWriterFactory.createWriter(out);
-			// Alice goes first
-			sendPublicKeyHash(w);
-			byte[] hash = receivePublicKeyHash(r);
-			sendPublicKey(w);
-			byte[] key = receivePublicKey(r);
-			master = deriveMasterSecret(hash, key, true);
-		} catch (IOException e) {
-			if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
-			group.keyAgreementFailed();
-			tryToClose(conn, true);
-			return;
-		} catch (GeneralSecurityException e) {
-			if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
-			group.keyAgreementFailed();
-			tryToClose(conn, true);
-			return;
-		}
-		// The key agreement succeeded - derive the confirmation codes
-		if (LOG.isLoggable(INFO)) LOG.info(pluginName + " agreement succeeded");
-		int aliceCode = crypto.deriveBTConfirmationCode(master, true);
-		int bobCode = crypto.deriveBTConfirmationCode(master, false);
-		group.keyAgreementSucceeded(aliceCode, bobCode);
-		// Exchange confirmation results
-		boolean localMatched, remoteMatched;
-		try {
-			localMatched = group.waitForLocalConfirmationResult();
-			sendConfirmation(w, localMatched);
-			remoteMatched = receiveConfirmation(r);
-		} catch (IOException e) {
-			if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
-			group.remoteConfirmationFailed();
-			tryToClose(conn, true);
-			return;
-		} catch (InterruptedException e) {
-			LOG.warning("Interrupted while waiting for confirmation");
-			group.remoteConfirmationFailed();
-			tryToClose(conn, true);
-			Thread.currentThread().interrupt();
-			return;
-		}
-		if (remoteMatched) group.remoteConfirmationSucceeded();
-		else group.remoteConfirmationFailed();
-		if (!(localMatched && remoteMatched)) {
-			if (LOG.isLoggable(INFO))
-				LOG.info(pluginName + " confirmation failed");
-			tryToClose(conn, false);
-			return;
-		}
-		// Confirmation succeeded - upgrade to a secure connection
-		if (LOG.isLoggable(INFO))
-			LOG.info(pluginName + " confirmation succeeded");
-		contactExchangeTask.startExchange(group, localAuthor, master, conn,
-				plugin.getId(), true);
-	}
-}
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/invitation/BobConnector.java b/bramble-core/src/main/java/org/briarproject/bramble/invitation/BobConnector.java
deleted file mode 100644
index d87a9334a01483bdd2916c81ff42e52c790aa6f0..0000000000000000000000000000000000000000
--- a/bramble-core/src/main/java/org/briarproject/bramble/invitation/BobConnector.java
+++ /dev/null
@@ -1,119 +0,0 @@
-package org.briarproject.bramble.invitation;
-
-import org.briarproject.bramble.api.contact.ContactExchangeTask;
-import org.briarproject.bramble.api.crypto.CryptoComponent;
-import org.briarproject.bramble.api.crypto.PseudoRandom;
-import org.briarproject.bramble.api.crypto.SecretKey;
-import org.briarproject.bramble.api.data.BdfReader;
-import org.briarproject.bramble.api.data.BdfReaderFactory;
-import org.briarproject.bramble.api.data.BdfWriter;
-import org.briarproject.bramble.api.data.BdfWriterFactory;
-import org.briarproject.bramble.api.identity.LocalAuthor;
-import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
-import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
-import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.security.GeneralSecurityException;
-import java.util.logging.Logger;
-
-import static java.util.logging.Level.INFO;
-import static java.util.logging.Level.WARNING;
-
-/**
- * A connection thread for the peer being Bob in the invitation protocol.
- */
-@NotNullByDefault
-class BobConnector extends Connector {
-
-	private static final Logger LOG =
-			Logger.getLogger(BobConnector.class.getName());
-
-	BobConnector(CryptoComponent crypto, BdfReaderFactory bdfReaderFactory,
-			BdfWriterFactory bdfWriterFactory,
-			ContactExchangeTask contactExchangeTask, ConnectorGroup group,
-			DuplexPlugin plugin, LocalAuthor localAuthor, PseudoRandom random) {
-		super(crypto, bdfReaderFactory, bdfWriterFactory, contactExchangeTask,
-				group, plugin, localAuthor, random);
-	}
-
-	@Override
-	public void run() {
-		// Create an incoming or outgoing connection
-		DuplexTransportConnection conn = createInvitationConnection(false);
-		if (conn == null) return;
-		if (LOG.isLoggable(INFO)) LOG.info(pluginName + " connected");
-		// Carry out the key agreement protocol
-		InputStream in;
-		OutputStream out;
-		BdfReader r;
-		BdfWriter w;
-		SecretKey master;
-		try {
-			in = conn.getReader().getInputStream();
-			out = conn.getWriter().getOutputStream();
-			r = bdfReaderFactory.createReader(in);
-			w = bdfWriterFactory.createWriter(out);
-			// Alice goes first
-			byte[] hash = receivePublicKeyHash(r);
-			// Don't proceed with more than one connection
-			if (group.getAndSetConnected()) {
-				if (LOG.isLoggable(INFO)) LOG.info(pluginName + " redundant");
-				tryToClose(conn, false);
-				return;
-			}
-			sendPublicKeyHash(w);
-			byte[] key = receivePublicKey(r);
-			sendPublicKey(w);
-			master = deriveMasterSecret(hash, key, false);
-		} catch (IOException e) {
-			if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
-			group.keyAgreementFailed();
-			tryToClose(conn, true);
-			return;
-		} catch (GeneralSecurityException e) {
-			if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
-			group.keyAgreementFailed();
-			tryToClose(conn, true);
-			return;
-		}
-		// The key agreement succeeded - derive the confirmation codes
-		if (LOG.isLoggable(INFO)) LOG.info(pluginName + " agreement succeeded");
-		int aliceCode = crypto.deriveBTConfirmationCode(master, true);
-		int bobCode = crypto.deriveBTConfirmationCode(master, false);
-		group.keyAgreementSucceeded(bobCode, aliceCode);
-		// Exchange confirmation results
-		boolean localMatched, remoteMatched;
-		try {
-			remoteMatched = receiveConfirmation(r);
-			localMatched = group.waitForLocalConfirmationResult();
-			sendConfirmation(w, localMatched);
-		} catch (IOException e) {
-			if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
-			group.remoteConfirmationFailed();
-			tryToClose(conn, true);
-			return;
-		} catch (InterruptedException e) {
-			LOG.warning("Interrupted while waiting for confirmation");
-			group.remoteConfirmationFailed();
-			tryToClose(conn, true);
-			Thread.currentThread().interrupt();
-			return;
-		}
-		if (remoteMatched) group.remoteConfirmationSucceeded();
-		else group.remoteConfirmationFailed();
-		if (!(localMatched && remoteMatched)) {
-			if (LOG.isLoggable(INFO))
-				LOG.info(pluginName + " confirmation failed");
-			tryToClose(conn, false);
-			return;
-		}
-		// Confirmation succeeded - upgrade to a secure connection
-		if (LOG.isLoggable(INFO))
-			LOG.info(pluginName + " confirmation succeeded");
-		contactExchangeTask.startExchange(group, localAuthor, master, conn,
-				plugin.getId(), false);
-	}
-}
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/invitation/Connector.java b/bramble-core/src/main/java/org/briarproject/bramble/invitation/Connector.java
deleted file mode 100644
index 4c1a63c6732e7996ab2e19466d6d7a9a4b023741..0000000000000000000000000000000000000000
--- a/bramble-core/src/main/java/org/briarproject/bramble/invitation/Connector.java
+++ /dev/null
@@ -1,150 +0,0 @@
-package org.briarproject.bramble.invitation;
-
-import org.briarproject.bramble.api.FormatException;
-import org.briarproject.bramble.api.contact.ContactExchangeTask;
-import org.briarproject.bramble.api.crypto.CryptoComponent;
-import org.briarproject.bramble.api.crypto.KeyPair;
-import org.briarproject.bramble.api.crypto.KeyParser;
-import org.briarproject.bramble.api.crypto.PseudoRandom;
-import org.briarproject.bramble.api.crypto.SecretKey;
-import org.briarproject.bramble.api.data.BdfReader;
-import org.briarproject.bramble.api.data.BdfReaderFactory;
-import org.briarproject.bramble.api.data.BdfWriter;
-import org.briarproject.bramble.api.data.BdfWriterFactory;
-import org.briarproject.bramble.api.identity.LocalAuthor;
-import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
-import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
-import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
-
-import java.io.IOException;
-import java.security.GeneralSecurityException;
-import java.util.Arrays;
-import java.util.logging.Logger;
-
-import javax.annotation.Nullable;
-
-import static java.util.logging.Level.INFO;
-import static java.util.logging.Level.WARNING;
-import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
-import static org.briarproject.bramble.api.invitation.InvitationConstants.CONNECTION_TIMEOUT;
-
-// FIXME: This class has way too many dependencies
-@NotNullByDefault
-abstract class Connector extends Thread {
-
-	private static final Logger LOG =
-			Logger.getLogger(Connector.class.getName());
-	private static final String LABEL_PUBLIC_KEY =
-			"org.briarproject.bramble.invitation.PUBLIC_KEY";
-
-	protected final CryptoComponent crypto;
-	protected final BdfReaderFactory bdfReaderFactory;
-	protected final BdfWriterFactory bdfWriterFactory;
-	protected final ContactExchangeTask contactExchangeTask;
-	protected final ConnectorGroup group;
-	protected final DuplexPlugin plugin;
-	protected final LocalAuthor localAuthor;
-	protected final PseudoRandom random;
-	protected final String pluginName;
-
-	private final KeyPair keyPair;
-	private final KeyParser keyParser;
-
-	Connector(CryptoComponent crypto, BdfReaderFactory bdfReaderFactory,
-			BdfWriterFactory bdfWriterFactory,
-			ContactExchangeTask contactExchangeTask, ConnectorGroup group,
-			DuplexPlugin plugin, LocalAuthor localAuthor, PseudoRandom random) {
-		super("Connector");
-		this.crypto = crypto;
-		this.bdfReaderFactory = bdfReaderFactory;
-		this.bdfWriterFactory = bdfWriterFactory;
-		this.contactExchangeTask = contactExchangeTask;
-		this.group = group;
-		this.plugin = plugin;
-		this.localAuthor = localAuthor;
-		this.random = random;
-		pluginName = plugin.getClass().getName();
-		keyPair = crypto.generateAgreementKeyPair();
-		keyParser = crypto.getAgreementKeyParser();
-	}
-
-	@Nullable
-	DuplexTransportConnection createInvitationConnection(boolean alice) {
-		if (LOG.isLoggable(INFO))
-			LOG.info(pluginName + " creating invitation connection");
-		return plugin.createInvitationConnection(random, CONNECTION_TIMEOUT,
-				alice);
-	}
-
-	void sendPublicKeyHash(BdfWriter w) throws IOException {
-		byte[] hash =
-				crypto.hash(LABEL_PUBLIC_KEY, keyPair.getPublic().getEncoded());
-		w.writeRaw(hash);
-		w.flush();
-		if (LOG.isLoggable(INFO)) LOG.info(pluginName + " sent hash");
-	}
-
-	byte[] receivePublicKeyHash(BdfReader r) throws IOException {
-		int hashLength = crypto.getHashLength();
-		byte[] b = r.readRaw(hashLength);
-		if (b.length < hashLength) throw new FormatException();
-		if (LOG.isLoggable(INFO)) LOG.info(pluginName + " received hash");
-		return b;
-	}
-
-	void sendPublicKey(BdfWriter w) throws IOException {
-		byte[] key = keyPair.getPublic().getEncoded();
-		w.writeRaw(key);
-		w.flush();
-		if (LOG.isLoggable(INFO)) LOG.info(pluginName + " sent key");
-	}
-
-	byte[] receivePublicKey(BdfReader r)
-			throws GeneralSecurityException, IOException {
-		byte[] b = r.readRaw(MAX_PUBLIC_KEY_LENGTH);
-		keyParser.parsePublicKey(b);
-		if (LOG.isLoggable(INFO)) LOG.info(pluginName + " received key");
-		return b;
-	}
-
-	SecretKey deriveMasterSecret(byte[] hash, byte[] key, boolean alice)
-			throws GeneralSecurityException {
-		// Check that the hash matches the key
-		byte[] keyHash =
-				crypto.hash(LABEL_PUBLIC_KEY, keyPair.getPublic().getEncoded());
-		if (!Arrays.equals(hash, keyHash)) {
-			if (LOG.isLoggable(INFO))
-				LOG.info(pluginName + " hash does not match key");
-			throw new GeneralSecurityException();
-		}
-		//  Derive the master secret
-		if (LOG.isLoggable(INFO))
-			LOG.info(pluginName + " deriving master secret");
-		return crypto.deriveMasterSecret(key, keyPair, alice);
-	}
-
-	void sendConfirmation(BdfWriter w, boolean confirmed) throws IOException {
-		w.writeBoolean(confirmed);
-		w.flush();
-		if (LOG.isLoggable(INFO))
-			LOG.info(pluginName + " sent confirmation: " + confirmed);
-	}
-
-	boolean receiveConfirmation(BdfReader r) throws IOException {
-		boolean confirmed = r.readBoolean();
-		if (LOG.isLoggable(INFO))
-			LOG.info(pluginName + " received confirmation: " + confirmed);
-		return confirmed;
-	}
-
-	protected void tryToClose(DuplexTransportConnection conn,
-			boolean exception) {
-		try {
-			LOG.info("Closing connection");
-			conn.getReader().dispose(exception, true);
-			conn.getWriter().dispose(exception);
-		} catch (IOException e) {
-			if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
-		}
-	}
-}
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/invitation/ConnectorGroup.java b/bramble-core/src/main/java/org/briarproject/bramble/invitation/ConnectorGroup.java
deleted file mode 100644
index 103dd03c63e79f5cf57e01ba62130817e6b5c591..0000000000000000000000000000000000000000
--- a/bramble-core/src/main/java/org/briarproject/bramble/invitation/ConnectorGroup.java
+++ /dev/null
@@ -1,278 +0,0 @@
-package org.briarproject.bramble.invitation;
-
-import org.briarproject.bramble.api.contact.ContactExchangeListener;
-import org.briarproject.bramble.api.contact.ContactExchangeTask;
-import org.briarproject.bramble.api.crypto.CryptoComponent;
-import org.briarproject.bramble.api.crypto.PseudoRandom;
-import org.briarproject.bramble.api.data.BdfReaderFactory;
-import org.briarproject.bramble.api.data.BdfWriterFactory;
-import org.briarproject.bramble.api.db.DbException;
-import org.briarproject.bramble.api.identity.Author;
-import org.briarproject.bramble.api.identity.IdentityManager;
-import org.briarproject.bramble.api.identity.LocalAuthor;
-import org.briarproject.bramble.api.invitation.InvitationListener;
-import org.briarproject.bramble.api.invitation.InvitationState;
-import org.briarproject.bramble.api.invitation.InvitationTask;
-import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
-import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
-import org.briarproject.bramble.api.plugin.PluginManager;
-import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantLock;
-import java.util.logging.Logger;
-
-import static java.util.concurrent.TimeUnit.MILLISECONDS;
-import static java.util.logging.Level.WARNING;
-import static org.briarproject.bramble.api.invitation.InvitationConstants.CONFIRMATION_TIMEOUT;
-
-/**
- * A task consisting of one or more parallel connection attempts.
- */
-@MethodsNotNullByDefault
-@ParametersNotNullByDefault
-class ConnectorGroup extends Thread implements InvitationTask,
-		ContactExchangeListener {
-
-	private static final Logger LOG =
-			Logger.getLogger(ConnectorGroup.class.getName());
-
-	private final CryptoComponent crypto;
-	private final BdfReaderFactory bdfReaderFactory;
-	private final BdfWriterFactory bdfWriterFactory;
-	private final ContactExchangeTask contactExchangeTask;
-	private final IdentityManager identityManager;
-	private final PluginManager pluginManager;
-	private final int localInvitationCode, remoteInvitationCode;
-	private final Collection<InvitationListener> listeners;
-	private final AtomicBoolean connected;
-	private final CountDownLatch localConfirmationLatch;
-	private final Lock lock = new ReentrantLock();
-
-	// The following are locking: lock
-	private int localConfirmationCode = -1, remoteConfirmationCode = -1;
-	private boolean connectionFailed = false;
-	private boolean localCompared = false, remoteCompared = false;
-	private boolean localMatched = false, remoteMatched = false;
-	private String remoteName = null;
-
-	ConnectorGroup(CryptoComponent crypto, BdfReaderFactory bdfReaderFactory,
-			BdfWriterFactory bdfWriterFactory,
-			ContactExchangeTask contactExchangeTask,
-			IdentityManager identityManager, PluginManager pluginManager,
-			int localInvitationCode, int remoteInvitationCode) {
-		super("ConnectorGroup");
-		this.crypto = crypto;
-		this.bdfReaderFactory = bdfReaderFactory;
-		this.bdfWriterFactory = bdfWriterFactory;
-		this.contactExchangeTask = contactExchangeTask;
-		this.identityManager = identityManager;
-		this.pluginManager = pluginManager;
-		this.localInvitationCode = localInvitationCode;
-		this.remoteInvitationCode = remoteInvitationCode;
-		listeners = new CopyOnWriteArrayList<InvitationListener>();
-		connected = new AtomicBoolean(false);
-		localConfirmationLatch = new CountDownLatch(1);
-	}
-
-	@Override
-	public InvitationState addListener(InvitationListener l) {
-		lock.lock();
-		try {
-			listeners.add(l);
-			return new InvitationState(localInvitationCode,
-					remoteInvitationCode, localConfirmationCode,
-					remoteConfirmationCode, connected.get(), connectionFailed,
-					localCompared, remoteCompared, localMatched, remoteMatched,
-					remoteName);
-		} finally {
-			lock.unlock();
-		}
-	}
-
-	@Override
-	public void removeListener(InvitationListener l) {
-		listeners.remove(l);
-	}
-
-	@Override
-	public void connect() {
-		start();
-	}
-
-	@Override
-	public void run() {
-		LocalAuthor localAuthor;
-		// Load the local pseudonym
-		try {
-			localAuthor = identityManager.getLocalAuthor();
-		} catch (DbException e) {
-			if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
-			lock.lock();
-			try {
-				connectionFailed = true;
-			} finally {
-				lock.unlock();
-			}
-			for (InvitationListener l : listeners) l.connectionFailed();
-			return;
-		}
-		// Start the connection threads
-		Collection<Connector> connectors = new ArrayList<Connector>();
-		// Alice is the party with the smaller invitation code
-		if (localInvitationCode < remoteInvitationCode) {
-			for (DuplexPlugin plugin : pluginManager.getInvitationPlugins()) {
-				Connector c = createAliceConnector(plugin, localAuthor);
-				connectors.add(c);
-				c.start();
-			}
-		} else {
-			for (DuplexPlugin plugin : pluginManager.getInvitationPlugins()) {
-				Connector c = createBobConnector(plugin, localAuthor);
-				connectors.add(c);
-				c.start();
-			}
-		}
-		// Wait for the connection threads to finish
-		try {
-			for (Connector c : connectors) c.join();
-		} catch (InterruptedException e) {
-			LOG.warning("Interrupted while waiting for connectors");
-			Thread.currentThread().interrupt();
-		}
-		// If none of the threads connected, inform the listeners
-		if (!connected.get()) {
-			lock.lock();
-			try {
-				connectionFailed = true;
-			} finally {
-				lock.unlock();
-			}
-			for (InvitationListener l : listeners) l.connectionFailed();
-		}
-	}
-
-	private Connector createAliceConnector(DuplexPlugin plugin,
-			LocalAuthor localAuthor) {
-		PseudoRandom random = crypto.getPseudoRandom(localInvitationCode,
-				remoteInvitationCode);
-		return new AliceConnector(crypto, bdfReaderFactory, bdfWriterFactory,
-				contactExchangeTask, this, plugin, localAuthor, random);
-	}
-
-	private Connector createBobConnector(DuplexPlugin plugin,
-			LocalAuthor localAuthor) {
-		PseudoRandom random = crypto.getPseudoRandom(remoteInvitationCode,
-				localInvitationCode);
-		return new BobConnector(crypto, bdfReaderFactory, bdfWriterFactory,
-				contactExchangeTask, this, plugin, localAuthor, random);
-	}
-
-	@Override
-	public void localConfirmationSucceeded() {
-		lock.lock();
-		try {
-			localCompared = true;
-			localMatched = true;
-		} finally {
-			lock.unlock();
-		}
-		localConfirmationLatch.countDown();
-	}
-
-	@Override
-	public void localConfirmationFailed() {
-		lock.lock();
-		try {
-			localCompared = true;
-			localMatched = false;
-		} finally {
-			lock.unlock();
-		}
-		localConfirmationLatch.countDown();
-	}
-
-	boolean getAndSetConnected() {
-		boolean redundant = connected.getAndSet(true);
-		if (!redundant)
-			for (InvitationListener l : listeners) l.connectionSucceeded();
-		return redundant;
-	}
-
-	void keyAgreementSucceeded(int localCode, int remoteCode) {
-		lock.lock();
-		try {
-			localConfirmationCode = localCode;
-			remoteConfirmationCode = remoteCode;
-		} finally {
-			lock.unlock();
-		}
-		for (InvitationListener l : listeners)
-			l.keyAgreementSucceeded(localCode, remoteCode);
-	}
-
-	void keyAgreementFailed() {
-		for (InvitationListener l : listeners) l.keyAgreementFailed();
-	}
-
-	boolean waitForLocalConfirmationResult() throws InterruptedException {
-		localConfirmationLatch.await(CONFIRMATION_TIMEOUT, MILLISECONDS);
-		lock.lock();
-		try {
-			return localMatched;
-		} finally {
-			lock.unlock();
-		}
-	}
-
-	void remoteConfirmationSucceeded() {
-		lock.lock();
-		try {
-			remoteCompared = true;
-			remoteMatched = true;
-		} finally {
-			lock.unlock();
-		}
-		for (InvitationListener l : listeners) l.remoteConfirmationSucceeded();
-	}
-
-	void remoteConfirmationFailed() {
-		lock.lock();
-		try {
-			remoteCompared = true;
-			remoteMatched = false;
-		} finally {
-			lock.unlock();
-		}
-		for (InvitationListener l : listeners) l.remoteConfirmationFailed();
-	}
-
-	@Override
-	public void contactExchangeSucceeded(Author remoteAuthor) {
-		String name = remoteAuthor.getName();
-		lock.lock();
-		try {
-			remoteName = name;
-		} finally {
-			lock.unlock();
-		}
-		for (InvitationListener l : listeners)
-			l.pseudonymExchangeSucceeded(name);
-	}
-
-	@Override
-	public void duplicateContact(Author remoteAuthor) {
-		// TODO differentiate
-		for (InvitationListener l : listeners) l.pseudonymExchangeFailed();
-	}
-
-	@Override
-	public void contactExchangeFailed() {
-		for (InvitationListener l : listeners) l.pseudonymExchangeFailed();
-	}
-}
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/invitation/InvitationModule.java b/bramble-core/src/main/java/org/briarproject/bramble/invitation/InvitationModule.java
deleted file mode 100644
index 0c6dbcfac53a6e864bf8ea40d8b6338e8f437597..0000000000000000000000000000000000000000
--- a/bramble-core/src/main/java/org/briarproject/bramble/invitation/InvitationModule.java
+++ /dev/null
@@ -1,16 +0,0 @@
-package org.briarproject.bramble.invitation;
-
-import org.briarproject.bramble.api.invitation.InvitationTaskFactory;
-
-import dagger.Module;
-import dagger.Provides;
-
-@Module
-public class InvitationModule {
-
-	@Provides
-	InvitationTaskFactory provideInvitationTaskFactory(
-			InvitationTaskFactoryImpl invitationTaskFactory) {
-		return invitationTaskFactory;
-	}
-}
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/invitation/InvitationTaskFactoryImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/invitation/InvitationTaskFactoryImpl.java
deleted file mode 100644
index 19bd8c6b9da1ad22dc18c81b5ee0d0e462b90b24..0000000000000000000000000000000000000000
--- a/bramble-core/src/main/java/org/briarproject/bramble/invitation/InvitationTaskFactoryImpl.java
+++ /dev/null
@@ -1,47 +0,0 @@
-package org.briarproject.bramble.invitation;
-
-import org.briarproject.bramble.api.contact.ContactExchangeTask;
-import org.briarproject.bramble.api.crypto.CryptoComponent;
-import org.briarproject.bramble.api.data.BdfReaderFactory;
-import org.briarproject.bramble.api.data.BdfWriterFactory;
-import org.briarproject.bramble.api.identity.IdentityManager;
-import org.briarproject.bramble.api.invitation.InvitationTask;
-import org.briarproject.bramble.api.invitation.InvitationTaskFactory;
-import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
-import org.briarproject.bramble.api.plugin.PluginManager;
-
-import javax.annotation.concurrent.Immutable;
-import javax.inject.Inject;
-
-@Immutable
-@NotNullByDefault
-class InvitationTaskFactoryImpl implements InvitationTaskFactory {
-
-	private final CryptoComponent crypto;
-	private final BdfReaderFactory bdfReaderFactory;
-	private final BdfWriterFactory bdfWriterFactory;
-	private final ContactExchangeTask contactExchangeTask;
-	private final IdentityManager identityManager;
-	private final PluginManager pluginManager;
-
-	@Inject
-	InvitationTaskFactoryImpl(CryptoComponent crypto,
-			BdfReaderFactory bdfReaderFactory,
-			BdfWriterFactory bdfWriterFactory,
-			ContactExchangeTask contactExchangeTask,
-			IdentityManager identityManager, PluginManager pluginManager) {
-		this.crypto = crypto;
-		this.bdfReaderFactory = bdfReaderFactory;
-		this.bdfWriterFactory = bdfWriterFactory;
-		this.contactExchangeTask = contactExchangeTask;
-		this.identityManager = identityManager;
-		this.pluginManager = pluginManager;
-	}
-
-	@Override
-	public InvitationTask createTask(int localCode, int remoteCode) {
-		return new ConnectorGroup(crypto, bdfReaderFactory, bdfWriterFactory,
-				contactExchangeTask, identityManager, pluginManager,
-				localCode, remoteCode);
-	}
-}
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/PluginManagerImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/PluginManagerImpl.java
index 442f3ed89245a3c868d858d489e66118293af154..087d943da1f966b6d60bc863901ef929cc9c26d7 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/PluginManagerImpl.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/PluginManagerImpl.java
@@ -164,14 +164,6 @@ class PluginManagerImpl implements PluginManager, Service {
 		return new ArrayList<DuplexPlugin>(duplexPlugins);
 	}
 
-	@Override
-	public Collection<DuplexPlugin> getInvitationPlugins() {
-		List<DuplexPlugin> supported = new ArrayList<DuplexPlugin>();
-		for (DuplexPlugin d : duplexPlugins)
-			if (d.supportsInvitations()) supported.add(d);
-		return supported;
-	}
-
 	@Override
 	public Collection<DuplexPlugin> getKeyAgreementPlugins() {
 		List<DuplexPlugin> supported = new ArrayList<DuplexPlugin>();
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/tcp/TcpPlugin.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/tcp/TcpPlugin.java
index a1bed00752770e10c74ad08c73ddd723c20ae1dd..247950ff578ed72b8105a39a5940b03d1d9d4465 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/tcp/TcpPlugin.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/tcp/TcpPlugin.java
@@ -1,7 +1,6 @@
 package org.briarproject.bramble.plugin.tcp;
 
 import org.briarproject.bramble.api.contact.ContactId;
-import org.briarproject.bramble.api.crypto.PseudoRandom;
 import org.briarproject.bramble.api.data.BdfList;
 import org.briarproject.bramble.api.keyagreement.KeyAgreementListener;
 import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
@@ -281,17 +280,6 @@ abstract class TcpPlugin implements DuplexPlugin {
 		}
 	}
 
-	@Override
-	public boolean supportsInvitations() {
-		return false;
-	}
-
-	@Override
-	public DuplexTransportConnection createInvitationConnection(PseudoRandom r,
-			long timeout, boolean alice) {
-		throw new UnsupportedOperationException();
-	}
-
 	@Override
 	public boolean supportsKeyAgreement() {
 		return false;
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/transport/StreamReaderFactoryImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/transport/StreamReaderFactoryImpl.java
index 0d2e9cfb73011e73a9263d3e0026d5b4dc90c8fa..4d6475b23c616ac5e65936e5758dba7656fda688 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/transport/StreamReaderFactoryImpl.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/transport/StreamReaderFactoryImpl.java
@@ -29,10 +29,10 @@ class StreamReaderFactoryImpl implements StreamReaderFactory {
 	}
 
 	@Override
-	public InputStream createInvitationStreamReader(InputStream in,
+	public InputStream createContactExchangeStreamReader(InputStream in,
 			SecretKey headerKey) {
 		return new StreamReaderImpl(
-				streamDecrypterFactory.createInvitationStreamDecrypter(in,
+				streamDecrypterFactory.createContactExchangeStreamDecrypter(in,
 						headerKey));
 	}
 }
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/transport/StreamWriterFactoryImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/transport/StreamWriterFactoryImpl.java
index 77dc7657d77153f912ce6b6860b9a81da399b93f..b443a20e3d0ce31efc93b85465b3e31c339465a7 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/transport/StreamWriterFactoryImpl.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/transport/StreamWriterFactoryImpl.java
@@ -30,10 +30,10 @@ class StreamWriterFactoryImpl implements StreamWriterFactory {
 	}
 
 	@Override
-	public OutputStream createInvitationStreamWriter(OutputStream out,
+	public OutputStream createContactExchangeStreamWriter(OutputStream out,
 			SecretKey headerKey) {
 		return new StreamWriterImpl(
-				streamEncrypterFactory.createInvitationStreamEncrypter(out,
+				streamEncrypterFactory.createContactExchangeStreamDecrypter(out,
 						headerKey));
 	}
 }
\ No newline at end of file
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/crypto/PseudoRandomImpl.java b/bramble-core/src/test/java/org/briarproject/bramble/crypto/PseudoRandom.java
similarity index 82%
rename from bramble-core/src/main/java/org/briarproject/bramble/crypto/PseudoRandomImpl.java
rename to bramble-core/src/test/java/org/briarproject/bramble/crypto/PseudoRandom.java
index 1eabb29df3ccc355a6929903342a5f734c673fb4..fdc715c469a318e82d67b316124584c2cf7fb9f3 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/crypto/PseudoRandomImpl.java
+++ b/bramble-core/src/test/java/org/briarproject/bramble/crypto/PseudoRandom.java
@@ -1,6 +1,5 @@
 package org.briarproject.bramble.crypto;
 
-import org.briarproject.bramble.api.crypto.PseudoRandom;
 import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
 import org.spongycastle.crypto.Digest;
 import org.spongycastle.crypto.engines.Salsa20Engine;
@@ -11,11 +10,11 @@ import javax.annotation.concurrent.NotThreadSafe;
 
 @NotThreadSafe
 @NotNullByDefault
-class PseudoRandomImpl implements PseudoRandom {
+class PseudoRandom {
 
 	private final Salsa20Engine cipher = new Salsa20Engine();
 
-	PseudoRandomImpl(byte[] seed) {
+	PseudoRandom(byte[] seed) {
 		// Hash the seed to produce a 32-byte key
 		byte[] key = new byte[32];
 		Digest digest = new Blake2sDigest();
@@ -26,8 +25,7 @@ class PseudoRandomImpl implements PseudoRandom {
 		cipher.init(true, new ParametersWithIV(new KeyParameter(key), nonce));
 	}
 
-	@Override
-	public byte[] nextBytes(int length) {
+	byte[] nextBytes(int length) {
 		byte[] in = new byte[length], out = new byte[length];
 		cipher.processBytes(in, 0, length, out, 0);
 		return out;
diff --git a/bramble-core/src/test/java/org/briarproject/bramble/crypto/PseudoSecureRandom.java b/bramble-core/src/test/java/org/briarproject/bramble/crypto/PseudoSecureRandom.java
index 92702ca90a006570043ce5f154634cfd27adc421..5e75bf20e74ef8ad50f725973a9966c732494807 100644
--- a/bramble-core/src/test/java/org/briarproject/bramble/crypto/PseudoSecureRandom.java
+++ b/bramble-core/src/test/java/org/briarproject/bramble/crypto/PseudoSecureRandom.java
@@ -1,7 +1,5 @@
 package org.briarproject.bramble.crypto;
 
-import org.briarproject.bramble.api.crypto.PseudoRandom;
-
 import java.security.Provider;
 import java.security.SecureRandom;
 import java.security.SecureRandomSpi;
@@ -19,7 +17,7 @@ class PseudoSecureRandom extends SecureRandom {
 		private final PseudoRandom pseudoRandom;
 
 		private PseudoSecureRandomSpi(byte[] seed) {
-			pseudoRandom = new PseudoRandomImpl(seed);
+			pseudoRandom = new PseudoRandom(seed);
 		}
 
 		@Override
diff --git a/bramble-j2se/src/main/java/org/briarproject/bramble/plugin/bluetooth/BluetoothPlugin.java b/bramble-j2se/src/main/java/org/briarproject/bramble/plugin/bluetooth/BluetoothPlugin.java
index 92407910399e9a73d5f2e16a6e31a1e94d11d253..426f387b33c3febae0215e82cb418130ce539d05 100644
--- a/bramble-j2se/src/main/java/org/briarproject/bramble/plugin/bluetooth/BluetoothPlugin.java
+++ b/bramble-j2se/src/main/java/org/briarproject/bramble/plugin/bluetooth/BluetoothPlugin.java
@@ -2,7 +2,6 @@ package org.briarproject.bramble.plugin.bluetooth;
 
 import org.briarproject.bramble.api.FormatException;
 import org.briarproject.bramble.api.contact.ContactId;
-import org.briarproject.bramble.api.crypto.PseudoRandom;
 import org.briarproject.bramble.api.data.BdfList;
 import org.briarproject.bramble.api.keyagreement.KeyAgreementConnection;
 import org.briarproject.bramble.api.keyagreement.KeyAgreementListener;
@@ -19,33 +18,23 @@ import org.briarproject.bramble.util.OsUtils;
 import org.briarproject.bramble.util.StringUtils;
 
 import java.io.IOException;
-import java.io.InputStream;
 import java.security.SecureRandom;
-import java.util.ArrayList;
 import java.util.Collection;
-import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.UUID;
 import java.util.concurrent.Callable;
-import java.util.concurrent.CompletionService;
-import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Executor;
-import java.util.concurrent.ExecutorCompletionService;
-import java.util.concurrent.Future;
-import java.util.concurrent.Semaphore;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.logging.Logger;
 
 import javax.annotation.Nullable;
 import javax.bluetooth.BluetoothStateException;
-import javax.bluetooth.DiscoveryAgent;
 import javax.bluetooth.LocalDevice;
 import javax.microedition.io.Connector;
 import javax.microedition.io.StreamConnection;
 import javax.microedition.io.StreamConnectionNotifier;
 
-import static java.util.concurrent.TimeUnit.MILLISECONDS;
 import static java.util.logging.Level.INFO;
 import static java.util.logging.Level.WARNING;
 import static javax.bluetooth.DiscoveryAgent.GIAC;
@@ -67,7 +56,6 @@ class BluetoothPlugin implements DuplexPlugin {
 	private final Backoff backoff;
 	private final DuplexPluginCallback callback;
 	private final int maxLatency;
-	private final Semaphore discoverySemaphore = new Semaphore(1);
 	private final AtomicBoolean used = new AtomicBoolean(false);
 
 	private volatile boolean running = false;
@@ -273,95 +261,6 @@ class BluetoothPlugin implements DuplexPlugin {
 		return new BluetoothTransportConnection(this, s);
 	}
 
-	@Override
-	public boolean supportsInvitations() {
-		return true;
-	}
-
-	@Override
-	public DuplexTransportConnection createInvitationConnection(PseudoRandom r,
-			long timeout, boolean alice) {
-		if (!running) return null;
-		// Use the invitation codes to generate the UUID
-		byte[] b = r.nextBytes(UUID_BYTES);
-		String uuid = UUID.nameUUIDFromBytes(b).toString();
-		String url = makeUrl("localhost", uuid);
-		// Make the device discoverable if possible
-		makeDeviceDiscoverable();
-		// Bind a server socket for receiving invitation connections
-		final StreamConnectionNotifier ss;
-		try {
-			ss = (StreamConnectionNotifier) Connector.open(url);
-		} catch (IOException e) {
-			if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
-			return null;
-		}
-		if (!running) {
-			tryToClose(ss);
-			return null;
-		}
-		// Create the background tasks
-		CompletionService<StreamConnection> complete =
-				new ExecutorCompletionService<>(ioExecutor);
-		List<Future<StreamConnection>> futures = new ArrayList<>();
-		if (alice) {
-			// Return the first connected socket
-			futures.add(complete.submit(new ListeningTask(ss)));
-			futures.add(complete.submit(new DiscoveryTask(uuid)));
-		} else {
-			// Return the first socket with readable data
-			futures.add(complete.submit(new ReadableTask(
-					new ListeningTask(ss))));
-			futures.add(complete.submit(new ReadableTask(
-					new DiscoveryTask(uuid))));
-		}
-		StreamConnection chosen = null;
-		try {
-			Future<StreamConnection> f = complete.poll(timeout, MILLISECONDS);
-			if (f == null) return null; // No task completed within the timeout
-			chosen = f.get();
-			return new BluetoothTransportConnection(this, chosen);
-		} catch (InterruptedException e) {
-			LOG.info("Interrupted while exchanging invitations");
-			Thread.currentThread().interrupt();
-			return null;
-		} catch (ExecutionException e) {
-			if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
-			return null;
-		} finally {
-			// Closing the socket will terminate the listener task
-			tryToClose(ss);
-			closeSockets(futures, chosen);
-		}
-	}
-
-	private void closeSockets(final List<Future<StreamConnection>> futures,
-			@Nullable final StreamConnection chosen) {
-		ioExecutor.execute(new Runnable() {
-			@Override
-			public void run() {
-				for (Future<StreamConnection> f : futures) {
-					try {
-						if (f.cancel(true)) {
-							LOG.info("Cancelled task");
-						} else {
-							StreamConnection s = f.get();
-							if (s != null && s != chosen) {
-								LOG.info("Closing unwanted socket");
-								s.close();
-							}
-						}
-					} catch (InterruptedException e) {
-						LOG.info("Interrupted while closing sockets");
-						return;
-					} catch (ExecutionException | IOException e) {
-						if (LOG.isLoggable(INFO)) LOG.info(e.toString());
-					}
-				}
-			}
-		});
-	}
-
 	@Override
 	public boolean supportsKeyAgreement() {
 		return true;
@@ -376,7 +275,7 @@ class BluetoothPlugin implements DuplexPlugin {
 		String url = makeUrl("localhost", uuid);
 		// Make the device discoverable if possible
 		makeDeviceDiscoverable();
-		// Bind a server socket for receiving invitation connections
+		// Bind a server socket for receiving key agreementconnections
 		final StreamConnectionNotifier ss;
 		try {
 			ss = (StreamConnectionNotifier) Connector.open(url);
@@ -431,77 +330,6 @@ class BluetoothPlugin implements DuplexPlugin {
 		}
 	}
 
-	private class DiscoveryTask implements Callable<StreamConnection> {
-
-		private final String uuid;
-
-		private DiscoveryTask(String uuid) {
-			this.uuid = uuid;
-		}
-
-		@Override
-		public StreamConnection call() throws Exception {
-			// Repeat discovery until we connect or get interrupted
-			DiscoveryAgent discoveryAgent = localDevice.getDiscoveryAgent();
-			while (true) {
-				if (!discoverySemaphore.tryAcquire())
-					throw new Exception("Discovery is already in progress");
-				try {
-					InvitationListener listener =
-							new InvitationListener(discoveryAgent, uuid);
-					discoveryAgent.startInquiry(GIAC, listener);
-					String url = listener.waitForUrl();
-					if (url != null) {
-						StreamConnection s = connect(url);
-						if (s != null) {
-							LOG.info("Outgoing connection");
-							return s;
-						}
-					}
-				} finally {
-					discoverySemaphore.release();
-				}
-			}
-		}
-	}
-
-	private static class ListeningTask implements Callable<StreamConnection> {
-
-		private final StreamConnectionNotifier serverSocket;
-
-		private ListeningTask(StreamConnectionNotifier serverSocket) {
-			this.serverSocket = serverSocket;
-		}
-
-		@Override
-		public StreamConnection call() throws Exception {
-			StreamConnection s = serverSocket.acceptAndOpen();
-			LOG.info("Incoming connection");
-			return s;
-		}
-	}
-
-	private static class ReadableTask implements Callable<StreamConnection> {
-
-		private final Callable<StreamConnection> connectionTask;
-
-		private ReadableTask(Callable<StreamConnection> connectionTask) {
-			this.connectionTask = connectionTask;
-		}
-
-		@Override
-		public StreamConnection call() throws Exception {
-			StreamConnection s = connectionTask.call();
-			InputStream in = s.openInputStream();
-			while (in.available() == 0) {
-				LOG.info("Waiting for data");
-				Thread.sleep(1000);
-			}
-			LOG.info("Data available");
-			return s;
-		}
-	}
-
 	private class BluetoothKeyAgreementListener extends KeyAgreementListener {
 
 		private final StreamConnectionNotifier ss;
diff --git a/bramble-j2se/src/main/java/org/briarproject/bramble/plugin/bluetooth/InvitationListener.java b/bramble-j2se/src/main/java/org/briarproject/bramble/plugin/bluetooth/InvitationListener.java
deleted file mode 100644
index 0a389134b3452582a422ec37f53d62eda3c04b8e..0000000000000000000000000000000000000000
--- a/bramble-j2se/src/main/java/org/briarproject/bramble/plugin/bluetooth/InvitationListener.java
+++ /dev/null
@@ -1,109 +0,0 @@
-package org.briarproject.bramble.plugin.bluetooth;
-
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Enumeration;
-import java.util.TreeSet;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.logging.Logger;
-
-import javax.bluetooth.BluetoothStateException;
-import javax.bluetooth.DataElement;
-import javax.bluetooth.DeviceClass;
-import javax.bluetooth.DiscoveryAgent;
-import javax.bluetooth.DiscoveryListener;
-import javax.bluetooth.RemoteDevice;
-import javax.bluetooth.ServiceRecord;
-import javax.bluetooth.UUID;
-
-import static java.util.logging.Level.WARNING;
-
-class InvitationListener implements DiscoveryListener {
-
-	private static final Logger LOG =
-			Logger.getLogger(InvitationListener.class.getName());
-
-	private final AtomicInteger searches = new AtomicInteger(1);
-	private final CountDownLatch finished = new CountDownLatch(1);
-	private final DiscoveryAgent discoveryAgent;
-	private final String uuid;
-
-	private volatile String url = null;
-
-	InvitationListener(DiscoveryAgent discoveryAgent, String uuid) {
-		this.discoveryAgent = discoveryAgent;
-		this.uuid = uuid;
-	}
-
-	String waitForUrl() throws InterruptedException {
-		finished.await();
-		return url;
-	}
-
-	@Override
-	public void deviceDiscovered(RemoteDevice device, DeviceClass deviceClass) {
-		UUID[] uuids = new UUID[] {new UUID(uuid, false)};
-		// Try to discover the services associated with the UUID
-		try {
-			discoveryAgent.searchServices(null, uuids, device, this);
-			searches.incrementAndGet();
-		} catch (BluetoothStateException e) {
-			if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
-		}
-	}
-
-	@Override
-	public void servicesDiscovered(int transaction, ServiceRecord[] services) {
-		for (ServiceRecord record : services) {
-			// Does this service have a URL?
-			String serviceUrl = record.getConnectionURL(
-					ServiceRecord.NOAUTHENTICATE_NOENCRYPT, false);
-			if (serviceUrl == null) continue;
-			// Does this service have the UUID we're looking for?
-			Collection<String> uuids = new TreeSet<>();
-			findNestedClassIds(record.getAttributeValue(0x1), uuids);
-			for (String u : uuids) {
-				if (uuid.equalsIgnoreCase(u)) {
-					// The UUID matches - store the URL
-					url = serviceUrl;
-					finished.countDown();
-					return;
-				}
-			}
-		}
-	}
-
-	@Override
-	public void inquiryCompleted(int discoveryType) {
-		if (searches.decrementAndGet() == 0) finished.countDown();
-	}
-
-	@Override
-	public void serviceSearchCompleted(int transaction, int response) {
-		if (searches.decrementAndGet() == 0) finished.countDown();
-	}
-
-	// UUIDs are sometimes buried in nested data elements
-	private void findNestedClassIds(Object o, Collection<String> ids) {
-		o = getDataElementValue(o);
-		if (o instanceof Enumeration<?>) {
-			for (Object o1 : Collections.list((Enumeration<?>) o))
-				findNestedClassIds(o1, ids);
-		} else if (o instanceof UUID) {
-			ids.add(o.toString());
-		}
-	}
-
-	private Object getDataElementValue(Object o) {
-		if (o instanceof DataElement) {
-			// Bluecove throws an exception if the type is unknown
-			try {
-				return ((DataElement) o).getValue();
-			} catch (ClassCastException e) {
-				return null;
-			}
-		}
-		return null;
-	}
-}
diff --git a/bramble-j2se/src/main/java/org/briarproject/bramble/plugin/modem/ModemPlugin.java b/bramble-j2se/src/main/java/org/briarproject/bramble/plugin/modem/ModemPlugin.java
index 98094f36d0811e1b63930b7fcf1a8a1acfd29a85..2e9f3819c8204211895727d85544046a0266ac35 100644
--- a/bramble-j2se/src/main/java/org/briarproject/bramble/plugin/modem/ModemPlugin.java
+++ b/bramble-j2se/src/main/java/org/briarproject/bramble/plugin/modem/ModemPlugin.java
@@ -1,7 +1,6 @@
 package org.briarproject.bramble.plugin.modem;
 
 import org.briarproject.bramble.api.contact.ContactId;
-import org.briarproject.bramble.api.crypto.PseudoRandom;
 import org.briarproject.bramble.api.data.BdfList;
 import org.briarproject.bramble.api.keyagreement.KeyAgreementListener;
 import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
@@ -167,17 +166,6 @@ class ModemPlugin implements DuplexPlugin, Modem.Callback {
 		return new ModemTransportConnection();
 	}
 
-	@Override
-	public boolean supportsInvitations() {
-		return false;
-	}
-
-	@Override
-	public DuplexTransportConnection createInvitationConnection(PseudoRandom r,
-			long timeout, boolean alice) {
-		throw new UnsupportedOperationException();
-	}
-
 	@Override
 	public boolean supportsKeyAgreement() {
 		return false;
diff --git a/briar-android/src/main/AndroidManifest.xml b/briar-android/src/main/AndroidManifest.xml
index 22e0c0dd8824f04a120b13f862076046bb36d861..cadb8490399fd28f2f684339c455649753cbd899 100644
--- a/briar-android/src/main/AndroidManifest.xml
+++ b/briar-android/src/main/AndroidManifest.xml
@@ -15,8 +15,6 @@
 	<uses-permission android:name="android.permission.READ_LOGS"/>
 	<uses-permission android:name="android.permission.VIBRATE" />
 	<uses-permission android:name="android.permission.WAKE_LOCK" />
-	<!-- Since API 23, this is needed to add contacts via Bluetooth -->
-	<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
 
 	<application
 		android:name="org.briarproject.briar.android.BriarApplicationImpl"
@@ -292,16 +290,6 @@
 				/>
 		</activity>
 
-		<activity
-			android:name="org.briarproject.briar.android.invitation.AddContactActivity"
-			android:label="@string/add_contact_title"
-			android:parentActivityName="org.briarproject.briar.android.navdrawer.NavDrawerActivity">
-			<meta-data
-				android:name="android.support.PARENT_ACTIVITY"
-				android:value="org.briarproject.briar.android.navdrawer.NavDrawerActivity"
-				/>
-		</activity>
-
 		<activity
 			android:name="org.briarproject.briar.android.keyagreement.KeyAgreementActivity"
 			android:label="@string/add_contact_title"
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/AndroidComponent.java b/briar-android/src/main/java/org/briarproject/briar/android/AndroidComponent.java
index 9394f9d02ee249fc0bf9495e4f00df3fc6cb343b..4ad3f75b2fb78074c6c14b959af87d5aa8ac3a0e 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/AndroidComponent.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/AndroidComponent.java
@@ -12,7 +12,6 @@ import org.briarproject.bramble.api.db.DatabaseConfig;
 import org.briarproject.bramble.api.db.DatabaseExecutor;
 import org.briarproject.bramble.api.event.EventBus;
 import org.briarproject.bramble.api.identity.IdentityManager;
-import org.briarproject.bramble.api.invitation.InvitationTaskFactory;
 import org.briarproject.bramble.api.keyagreement.KeyAgreementTaskFactory;
 import org.briarproject.bramble.api.keyagreement.PayloadEncoder;
 import org.briarproject.bramble.api.keyagreement.PayloadParser;
@@ -89,8 +88,6 @@ public interface AndroidComponent
 
 	EventBus eventBus();
 
-	InvitationTaskFactory invitationTaskFactory();
-
 	AndroidNotificationManager androidNotificationManager();
 
 	ScreenFilterMonitor screenFilterMonitor();
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/activity/ActivityComponent.java b/briar-android/src/main/java/org/briarproject/briar/android/activity/ActivityComponent.java
index 7b9bb80ce2dae32d1bc490f64e3191147f66e869..61eea88b24ced60cb3c9e59ed3ff821cc22326c2 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/activity/ActivityComponent.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/activity/ActivityComponent.java
@@ -24,7 +24,6 @@ import org.briarproject.briar.android.forum.ForumModule;
 import org.briarproject.briar.android.introduction.ContactChooserFragment;
 import org.briarproject.briar.android.introduction.IntroductionActivity;
 import org.briarproject.briar.android.introduction.IntroductionMessageFragment;
-import org.briarproject.briar.android.invitation.AddContactActivity;
 import org.briarproject.briar.android.keyagreement.IntroFragment;
 import org.briarproject.briar.android.keyagreement.KeyAgreementActivity;
 import org.briarproject.briar.android.keyagreement.ShowQrCodeFragment;
@@ -91,8 +90,6 @@ public interface ActivityComponent {
 
 	void inject(PanicPreferencesActivity activity);
 
-	void inject(AddContactActivity activity);
-
 	void inject(KeyAgreementActivity activity);
 
 	void inject(ConversationActivity activity);
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/invitation/AddContactActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/invitation/AddContactActivity.java
deleted file mode 100644
index 1036382a31820d432e42385ffba3f4844fe97ca7..0000000000000000000000000000000000000000
--- a/briar-android/src/main/java/org/briarproject/briar/android/invitation/AddContactActivity.java
+++ /dev/null
@@ -1,449 +0,0 @@
-package org.briarproject.briar.android.invitation;
-
-import android.content.Intent;
-import android.os.Bundle;
-import android.widget.Toast;
-
-import org.briarproject.bramble.api.crypto.CryptoComponent;
-import org.briarproject.bramble.api.db.DbException;
-import org.briarproject.bramble.api.identity.AuthorId;
-import org.briarproject.bramble.api.identity.IdentityManager;
-import org.briarproject.bramble.api.identity.LocalAuthor;
-import org.briarproject.bramble.api.invitation.InvitationListener;
-import org.briarproject.bramble.api.invitation.InvitationState;
-import org.briarproject.bramble.api.invitation.InvitationTask;
-import org.briarproject.bramble.api.invitation.InvitationTaskFactory;
-import org.briarproject.briar.R;
-import org.briarproject.briar.android.activity.ActivityComponent;
-import org.briarproject.briar.android.activity.BriarActivity;
-import org.briarproject.briar.api.android.ReferenceManager;
-
-import java.util.logging.Logger;
-
-import javax.inject.Inject;
-
-import static android.widget.Toast.LENGTH_LONG;
-import static java.util.logging.Level.INFO;
-import static java.util.logging.Level.WARNING;
-import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_BLUETOOTH;
-import static org.briarproject.briar.android.invitation.ConfirmationCodeView.ConfirmationState.CONNECTED;
-import static org.briarproject.briar.android.invitation.ConfirmationCodeView.ConfirmationState.DETAILS;
-import static org.briarproject.briar.android.invitation.ConfirmationCodeView.ConfirmationState.WAIT_FOR_CONTACT;
-
-public class AddContactActivity extends BriarActivity
-		implements InvitationListener {
-
-	private static final Logger LOG =
-			Logger.getLogger(AddContactActivity.class.getName());
-
-	@Inject
-	CryptoComponent crypto;
-	@Inject
-	InvitationTaskFactory invitationTaskFactory;
-	@Inject
-	ReferenceManager referenceManager;
-
-	private AddContactView view = null;
-	private InvitationTask task = null;
-	private long taskHandle = -1;
-	private AuthorId localAuthorId = null;
-	private int localInvitationCode = -1, remoteInvitationCode = -1;
-	private int localConfirmationCode = -1, remoteConfirmationCode = -1;
-	private boolean connected = false, connectionFailed = false;
-	private boolean localCompared = false, remoteCompared = false;
-	private boolean localMatched = false, remoteMatched = false;
-	private String contactName = null;
-
-	// Fields that are accessed from background threads must be volatile
-	@Inject
-	volatile IdentityManager identityManager;
-
-	@Override
-	public void onCreate(Bundle state) {
-		super.onCreate(state);
-		if (state == null) {
-			// This is a new activity
-			setView(new ChooseIdentityView(this));
-		} else {
-			// Restore the activity's state
-			byte[] b = state.getByteArray("briar.LOCAL_AUTHOR_ID");
-			if (b != null) localAuthorId = new AuthorId(b);
-			taskHandle = state.getLong("briar.TASK_HANDLE", -1);
-			task = referenceManager.getReference(taskHandle,
-					InvitationTask.class);
-
-			if (task == null) {
-				// No background task - we must be in an initial or final state
-				localInvitationCode = state.getInt("briar.LOCAL_CODE");
-				remoteInvitationCode = state.getInt("briar.REMOTE_CODE");
-				connectionFailed = state.getBoolean("briar.FAILED");
-				contactName = state.getString("briar.CONTACT_NAME");
-				if (contactName != null) {
-					localCompared = remoteCompared = true;
-					localMatched = remoteMatched = true;
-				}
-				// Set the appropriate view for the state
-				if (localInvitationCode == -1) {
-					setView(new ChooseIdentityView(this));
-				} else if (remoteInvitationCode == -1) {
-					setView(new InvitationCodeView(this));
-				} else if (connectionFailed) {
-					setView(new ErrorView(this, R.string.connection_failed,
-							R.string.could_not_find_contact));
-				} else if (contactName == null) {
-					setView(new ErrorView(this, R.string.codes_do_not_match,
-							R.string.interfering));
-				} else {
-					showToastAndFinish();
-				}
-			} else {
-				// A background task exists - listen to it and get its state
-				InvitationState s = task.addListener(this);
-				localInvitationCode = s.getLocalInvitationCode();
-				remoteInvitationCode = s.getRemoteInvitationCode();
-				localConfirmationCode = s.getLocalConfirmationCode();
-				remoteConfirmationCode = s.getRemoteConfirmationCode();
-				connected = s.getConnected();
-				connectionFailed = s.getConnectionFailed();
-				localCompared = s.getLocalCompared();
-				remoteCompared = s.getRemoteCompared();
-				localMatched = s.getLocalMatched();
-				remoteMatched = s.getRemoteMatched();
-				contactName = s.getContactName();
-				// Set the appropriate view for the state
-				if (localInvitationCode == -1) {
-					setView(new ChooseIdentityView(this));
-				} else if (remoteInvitationCode == -1) {
-					setView(new InvitationCodeView(this));
-				} else if (connectionFailed) {
-					setView(new ErrorView(AddContactActivity.this,
-							R.string.connection_failed,
-							R.string.could_not_find_contact));
-				} else if (connected && localConfirmationCode == -1) {
-					setView(new ConfirmationCodeView(this, CONNECTED));
-				} else if (localConfirmationCode == -1) {
-					setView(new InvitationCodeView(this, true));
-				} else if (!localCompared) {
-					setView(new ConfirmationCodeView(this));
-				} else if (!remoteCompared) {
-					setView(new ConfirmationCodeView(this, WAIT_FOR_CONTACT));
-				} else if (localMatched && remoteMatched) {
-					if (contactName == null) {
-						setView(new ConfirmationCodeView(this, DETAILS));
-					} else {
-						showToastAndFinish();
-					}
-				} else {
-					setView(new ErrorView(this, R.string.codes_do_not_match,
-							R.string.interfering));
-				}
-			}
-		}
-	}
-
-	@Override
-	public void injectActivity(ActivityComponent component) {
-		component.inject(this);
-	}
-
-	private void showToastAndFinish() {
-		String format = getString(R.string.contact_added_toast);
-		String text = String.format(format, contactName);
-		Toast.makeText(this, text, LENGTH_LONG).show();
-		supportFinishAfterTransition();
-	}
-
-	@Override
-	public void onStart() {
-		super.onStart();
-		view.populate();
-	}
-
-	@Override
-	public void onSaveInstanceState(Bundle state) {
-		super.onSaveInstanceState(state);
-		if (localAuthorId != null) {
-			byte[] b = localAuthorId.getBytes();
-			state.putByteArray("briar.LOCAL_AUTHOR_ID", b);
-		}
-		state.putInt("briar.LOCAL_CODE", localInvitationCode);
-		state.putInt("briar.REMOTE_CODE", remoteInvitationCode);
-		state.putBoolean("briar.FAILED", connectionFailed);
-		state.putString("briar.CONTACT_NAME", contactName);
-		if (task != null) state.putLong("briar.TASK_HANDLE", taskHandle);
-	}
-
-	@Override
-	public void onDestroy() {
-		super.onDestroy();
-		if (task != null) task.removeListener(this);
-	}
-
-	@Override
-	public void onActivityResult(int request, int result, Intent data) {
-		if (request == REQUEST_BLUETOOTH) {
-			if (result != RESULT_CANCELED) reset(new InvitationCodeView(this));
-		}
-	}
-
-	@SuppressWarnings("ConstantConditions")
-	void setView(AddContactView view) {
-		this.view = view;
-		view.init(this);
-		setContentView(view);
-		getSupportActionBar().setTitle(R.string.add_contact_title);
-	}
-
-	void reset(AddContactView view) {
-		// Don't reset localAuthorId
-		task = null;
-		taskHandle = -1;
-		localInvitationCode = -1;
-		localConfirmationCode = remoteConfirmationCode = -1;
-		connected = connectionFailed = false;
-		localCompared = remoteCompared = false;
-		localMatched = remoteMatched = false;
-		contactName = null;
-		setView(view);
-	}
-
-	void loadLocalAuthor() {
-		runOnDbThread(new Runnable() {
-			@Override
-			public void run() {
-				try {
-					long now = System.currentTimeMillis();
-					LocalAuthor author = identityManager.getLocalAuthor();
-					long duration = System.currentTimeMillis() - now;
-					if (LOG.isLoggable(INFO))
-						LOG.info("Loading author took " + duration + " ms");
-					setLocalAuthorId(author.getId());
-				} catch (DbException e) {
-					if (LOG.isLoggable(WARNING))
-						LOG.log(WARNING, e.toString(), e);
-				}
-			}
-		});
-	}
-
-	void setLocalAuthorId(final AuthorId localAuthorId) {
-		runOnUiThreadUnlessDestroyed(new Runnable() {
-			@Override
-			public void run() {
-				AddContactActivity.this.localAuthorId = localAuthorId;
-			}
-		});
-	}
-
-	int getLocalInvitationCode() {
-		if (localInvitationCode == -1)
-			localInvitationCode = crypto.generateBTInvitationCode();
-		return localInvitationCode;
-	}
-
-	int getRemoteInvitationCode() {
-		return remoteInvitationCode;
-	}
-
-	void remoteInvitationCodeEntered(int code) {
-		if (localAuthorId == null) throw new IllegalStateException();
-		if (localInvitationCode == -1) throw new IllegalStateException();
-		remoteInvitationCode = code;
-
-		// change UI to show a progress indicator
-		setView(new InvitationCodeView(this, true));
-
-		task = invitationTaskFactory.createTask(localInvitationCode, code);
-		taskHandle = referenceManager.putReference(task, InvitationTask.class);
-		task.addListener(AddContactActivity.this);
-		// Add a second listener so we can remove the first in onDestroy(),
-		// allowing the activity to be garbage collected if it's destroyed
-		task.addListener(new ReferenceCleaner(referenceManager, taskHandle));
-		task.connect();
-	}
-
-	int getLocalConfirmationCode() {
-		return localConfirmationCode;
-	}
-
-	void remoteConfirmationCodeEntered(int code) {
-		localCompared = true;
-		if (code == remoteConfirmationCode) {
-			localMatched = true;
-			if (remoteMatched) {
-				setView(new ConfirmationCodeView(this, DETAILS));
-			} else if (remoteCompared) {
-				setView(new ErrorView(this, R.string.codes_do_not_match,
-						R.string.interfering));
-			} else {
-				setView(new ConfirmationCodeView(this, WAIT_FOR_CONTACT));
-			}
-			task.localConfirmationSucceeded();
-		} else {
-			localMatched = false;
-			setView(new ErrorView(this, R.string.codes_do_not_match,
-					R.string.interfering));
-			task.localConfirmationFailed();
-		}
-	}
-
-	@Override
-	public void connectionSucceeded() {
-		runOnUiThreadUnlessDestroyed(new Runnable() {
-			@Override
-			public void run() {
-				connected = true;
-				setView(new ConfirmationCodeView(AddContactActivity.this,
-						CONNECTED));
-			}
-		});
-	}
-
-	@Override
-	public void connectionFailed() {
-		runOnUiThreadUnlessDestroyed(new Runnable() {
-			@Override
-			public void run() {
-				connectionFailed = true;
-				setView(new ErrorView(AddContactActivity.this,
-						R.string.connection_failed,
-						R.string.could_not_find_contact));
-			}
-		});
-	}
-
-	@Override
-	public void keyAgreementSucceeded(final int localCode,
-			final int remoteCode) {
-		runOnUiThreadUnlessDestroyed(new Runnable() {
-			@Override
-			public void run() {
-				localConfirmationCode = localCode;
-				remoteConfirmationCode = remoteCode;
-				setView(new ConfirmationCodeView(AddContactActivity.this));
-			}
-		});
-	}
-
-	@Override
-	public void keyAgreementFailed() {
-		runOnUiThreadUnlessDestroyed(new Runnable() {
-			@Override
-			public void run() {
-				connectionFailed = true;
-				setView(new ErrorView(AddContactActivity.this,
-						R.string.connection_failed,
-						R.string.could_not_find_contact));
-			}
-		});
-	}
-
-	@Override
-	public void remoteConfirmationSucceeded() {
-		runOnUiThreadUnlessDestroyed(new Runnable() {
-			@Override
-			public void run() {
-				remoteCompared = true;
-				remoteMatched = true;
-				if (localMatched) {
-					setView(new ConfirmationCodeView(AddContactActivity.this,
-							DETAILS));
-				}
-			}
-		});
-	}
-
-	@Override
-	public void remoteConfirmationFailed() {
-		runOnUiThreadUnlessDestroyed(new Runnable() {
-			@Override
-			public void run() {
-				remoteCompared = true;
-				remoteMatched = false;
-				if (localMatched) {
-					setView(new ErrorView(AddContactActivity.this,
-							R.string.codes_do_not_match, R.string.interfering));
-				}
-			}
-		});
-	}
-
-	@Override
-	public void pseudonymExchangeSucceeded(final String remoteName) {
-		runOnUiThreadUnlessDestroyed(new Runnable() {
-			@Override
-			public void run() {
-				contactName = remoteName;
-				showToastAndFinish();
-			}
-		});
-	}
-
-	@Override
-	public void pseudonymExchangeFailed() {
-		runOnUiThreadUnlessDestroyed(new Runnable() {
-			@Override
-			public void run() {
-				setView(new ErrorView(AddContactActivity.this,
-						R.string.connection_failed,
-						R.string.could_not_find_contact));
-			}
-		});
-	}
-
-	/**
-	 * Cleans up the reference to the invitation task when the task completes.
-	 * This class is static to prevent memory leaks.
-	 */
-	private static class ReferenceCleaner implements InvitationListener {
-
-		private final ReferenceManager referenceManager;
-		private final long handle;
-
-		private ReferenceCleaner(ReferenceManager referenceManager,
-				long handle) {
-			this.referenceManager = referenceManager;
-			this.handle = handle;
-		}
-
-		@Override
-		public void connectionSucceeded() {
-			// Wait for key agreement to succeed or fail
-		}
-
-		@Override
-		public void connectionFailed() {
-			referenceManager.removeReference(handle, InvitationTask.class);
-		}
-
-		@Override
-		public void keyAgreementSucceeded(int localCode, int remoteCode) {
-			// Wait for remote confirmation to succeed or fail
-		}
-
-		@Override
-		public void keyAgreementFailed() {
-			referenceManager.removeReference(handle, InvitationTask.class);
-		}
-
-		@Override
-		public void remoteConfirmationSucceeded() {
-			// Wait for the pseudonym exchange to succeed or fail
-		}
-
-		@Override
-		public void remoteConfirmationFailed() {
-			referenceManager.removeReference(handle, InvitationTask.class);
-		}
-
-		@Override
-		public void pseudonymExchangeSucceeded(String remoteName) {
-			referenceManager.removeReference(handle, InvitationTask.class);
-		}
-
-		@Override
-		public void pseudonymExchangeFailed() {
-			referenceManager.removeReference(handle, InvitationTask.class);
-		}
-	}
-}
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/invitation/AddContactView.java b/briar-android/src/main/java/org/briarproject/briar/android/invitation/AddContactView.java
deleted file mode 100644
index 3ca73869a2dc568865a9ed761e2c38c20d056956..0000000000000000000000000000000000000000
--- a/briar-android/src/main/java/org/briarproject/briar/android/invitation/AddContactView.java
+++ /dev/null
@@ -1,22 +0,0 @@
-package org.briarproject.briar.android.invitation;
-
-import android.content.Context;
-import android.widget.LinearLayout;
-
-abstract class AddContactView extends LinearLayout {
-
-	static final public int CODE_LEN = 6;
-	protected AddContactActivity container = null;
-
-	AddContactView(Context ctx) {
-		super(ctx);
-	}
-
-	void init(AddContactActivity container) {
-		this.container = container;
-		populate();
-	}
-
-	abstract void populate();
-
-}
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/invitation/ChooseIdentityView.java b/briar-android/src/main/java/org/briarproject/briar/android/invitation/ChooseIdentityView.java
deleted file mode 100644
index d392a6ef2cca8ee7da7473c4e96cd4e1ec58c848..0000000000000000000000000000000000000000
--- a/briar-android/src/main/java/org/briarproject/briar/android/invitation/ChooseIdentityView.java
+++ /dev/null
@@ -1,43 +0,0 @@
-package org.briarproject.briar.android.invitation;
-
-import android.content.Context;
-import android.content.Intent;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.widget.Button;
-
-import org.briarproject.briar.R;
-
-import static android.bluetooth.BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE;
-import static android.bluetooth.BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION;
-import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_BLUETOOTH;
-
-class ChooseIdentityView extends AddContactView implements OnClickListener {
-
-	ChooseIdentityView(Context ctx) {
-		super(ctx);
-	}
-
-	@Override
-	void populate() {
-		removeAllViews();
-		Context ctx = getContext();
-
-		LayoutInflater inflater = (LayoutInflater) ctx.getSystemService
-				(Context.LAYOUT_INFLATER_SERVICE);
-		View view = inflater.inflate(R.layout.invitation_bluetooth_start, this);
-
-		Button continueButton = (Button) view.findViewById(R.id.continueButton);
-		continueButton.setOnClickListener(this);
-
-		container.loadLocalAuthor();
-	}
-
-	@Override
-	public void onClick(View view) {
-		Intent i = new Intent(ACTION_REQUEST_DISCOVERABLE);
-		i.putExtra(EXTRA_DISCOVERABLE_DURATION, 120);
-		container.startActivityForResult(i, REQUEST_BLUETOOTH);
-	}
-}
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/invitation/ConfirmationCodeView.java b/briar-android/src/main/java/org/briarproject/briar/android/invitation/ConfirmationCodeView.java
deleted file mode 100644
index 2bd953a9b6c39b54159c632fa7ee1792bcdfe8e9..0000000000000000000000000000000000000000
--- a/briar-android/src/main/java/org/briarproject/briar/android/invitation/ConfirmationCodeView.java
+++ /dev/null
@@ -1,121 +0,0 @@
-package org.briarproject.briar.android.invitation;
-
-import android.content.Context;
-import android.text.Editable;
-import android.text.TextWatcher;
-import android.view.KeyEvent;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.inputmethod.EditorInfo;
-import android.view.inputmethod.InputMethodManager;
-import android.widget.Button;
-import android.widget.EditText;
-import android.widget.TextView;
-
-import org.briarproject.briar.R;
-
-import static android.content.Context.INPUT_METHOD_SERVICE;
-
-class ConfirmationCodeView extends AddContactView {
-
-	public enum ConfirmationState { CONNECTED, ENTER_CODE, WAIT_FOR_CONTACT, DETAILS }
-	private ConfirmationState state;
-
-	ConfirmationCodeView(Context ctx) {
-		super(ctx);
-		this.state = ConfirmationState.ENTER_CODE;
-	}
-
-	ConfirmationCodeView(Context ctx, ConfirmationState state) {
-		super(ctx);
-		this.state = state;
-	}
-
-	@Override
-	void populate() {
-		removeAllViews();
-		Context ctx = getContext();
-
-		LayoutInflater inflater = (LayoutInflater) ctx.getSystemService
-				(Context.LAYOUT_INFLATER_SERVICE);
-		View view = inflater.inflate(R.layout.invitation_bluetooth_confirmation_code, this);
-
-		// local confirmation code
-		TextView code = (TextView) view.findViewById(R.id.codeView);
-		int localCode = container.getLocalConfirmationCode();
-		code.setText(String.format("%06d", localCode));
-
-		if (state != ConfirmationState.ENTER_CODE) {
-			// hide views we no longer need
-			view.findViewById(R.id.enterCodeTextView).setVisibility(View.GONE);
-			view.findViewById(R.id.codeEntryView).setVisibility(View.GONE);
-			view.findViewById(R.id.continueButton).setVisibility(View.GONE);
-
-			// show progress indicator
-			view.findViewById(R.id.progressBar).setVisibility(View.VISIBLE);
-
-			// show what we are waiting for
-			TextView connecting = (TextView) view.findViewById(R.id.waitingView);
-			int textId;
-			if (state == ConfirmationState.CONNECTED) {
-				textId = R.string.calculating_confirmation_code;
-				view.findViewById(R.id.yourConfirmationCodeView).setVisibility(View.GONE);
-				view.findViewById(R.id.codeView).setVisibility(View.GONE);
-			} else if (state == ConfirmationState.WAIT_FOR_CONTACT) {
-				textId = R.string.waiting_for_contact;
-			} else {
-				textId = R.string.exchanging_contact_details;
-			}
-			connecting.setText(ctx.getString(textId));
-			connecting.setVisibility(View.VISIBLE);
-		}
-		else {
-			// handle click on continue button
-			final EditText codeEntry = (EditText) view.findViewById(R.id.codeEntryView);
-			final Button continueButton = (Button) view.findViewById(R.id.continueButton);
-			continueButton.setOnClickListener(new OnClickListener() {
-				@Override
-				public void onClick(View v) {
-					send(codeEntry);
-				}
-			});
-
-			// activate continue button only when we have a 6 digit (CODE_LEN) code
-			codeEntry.addTextChangedListener(new TextWatcher() {
-				@Override
-				public void beforeTextChanged(CharSequence s, int start, int count, int after) {
-				}
-
-				@Override
-				public void onTextChanged(CharSequence s, int start, int before, int count) {
-					continueButton.setEnabled(codeEntry.getText().length() == CODE_LEN);
-				}
-
-				@Override
-				public void afterTextChanged(Editable s) {
-				}
-			});
-
-			codeEntry.setOnEditorActionListener(new TextView.OnEditorActionListener() {
-				@Override
-				public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
-					if (actionId == EditorInfo.IME_ACTION_GO && v.getText().length() == CODE_LEN) {
-						send(v);
-						return true;
-					}
-					return false;
-				}
-			});
-		}
-	}
-
-	private void send(TextView codeEntry) {
-		int code = Integer.parseInt(codeEntry.getText().toString());
-		container.remoteConfirmationCodeEntered(code);
-
-		// Hide the soft keyboard
-		Object o = getContext().getSystemService(INPUT_METHOD_SERVICE);
-		((InputMethodManager) o).hideSoftInputFromWindow(codeEntry.getWindowToken(), 0);
-	}
-
-}
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/invitation/ErrorView.java b/briar-android/src/main/java/org/briarproject/briar/android/invitation/ErrorView.java
deleted file mode 100644
index 0535a76020391401231a39873b99410abdcc508d..0000000000000000000000000000000000000000
--- a/briar-android/src/main/java/org/briarproject/briar/android/invitation/ErrorView.java
+++ /dev/null
@@ -1,59 +0,0 @@
-package org.briarproject.briar.android.invitation;
-
-import android.content.Context;
-import android.content.Intent;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.widget.Button;
-import android.widget.TextView;
-
-import org.briarproject.briar.R;
-
-import static android.bluetooth.BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE;
-import static android.bluetooth.BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION;
-import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_BLUETOOTH;
-
-class ErrorView extends AddContactView implements OnClickListener {
-
-	private final int error;
-	private final int explanation;
-
-	ErrorView(Context ctx) {
-		super(ctx);
-		this.error = R.string.connection_failed;
-		this.explanation = R.string.could_not_find_contact;
-	}
-
-	ErrorView(Context ctx, int error, int explanation) {
-		super(ctx);
-		this.error = error;
-		this.explanation = explanation;
-	}
-
-	@Override
-	void populate() {
-		removeAllViews();
-		Context ctx = getContext();
-
-		LayoutInflater inflater = (LayoutInflater) ctx.getSystemService
-				(Context.LAYOUT_INFLATER_SERVICE);
-		View view = inflater.inflate(R.layout.invitation_error, this);
-
-		TextView errorView = (TextView) view.findViewById(R.id.errorTextView);
-		errorView.setText(ctx.getString(error));
-
-		TextView explanationView = (TextView) view.findViewById(R.id.explanationTextView);
-		explanationView.setText(ctx.getString(explanation));
-
-		Button tryAgainButton = (Button) view.findViewById(R.id.tryAgainButton);
-		tryAgainButton.setOnClickListener(this);
-	}
-
-	@Override
-	public void onClick(View view) {
-		Intent i = new Intent(ACTION_REQUEST_DISCOVERABLE);
-		i.putExtra(EXTRA_DISCOVERABLE_DURATION, 120);
-		container.startActivityForResult(i, REQUEST_BLUETOOTH);
-	}
-}
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/invitation/InvitationCodeView.java b/briar-android/src/main/java/org/briarproject/briar/android/invitation/InvitationCodeView.java
deleted file mode 100644
index bfc7272a6685e0fd056a52866a7577f4e2e099cd..0000000000000000000000000000000000000000
--- a/briar-android/src/main/java/org/briarproject/briar/android/invitation/InvitationCodeView.java
+++ /dev/null
@@ -1,111 +0,0 @@
-package org.briarproject.briar.android.invitation;
-
-import android.content.Context;
-import android.text.Editable;
-import android.text.TextWatcher;
-import android.view.KeyEvent;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.inputmethod.EditorInfo;
-import android.view.inputmethod.InputMethodManager;
-import android.widget.Button;
-import android.widget.EditText;
-import android.widget.TextView;
-
-import org.briarproject.briar.R;
-
-import static android.content.Context.INPUT_METHOD_SERVICE;
-
-class InvitationCodeView extends AddContactView {
-
-	private boolean waiting;
-
-	InvitationCodeView(Context ctx, boolean waiting) {
-		super(ctx);
-		this.waiting = waiting;
-	}
-
-	InvitationCodeView(Context ctx) {
-		this(ctx, false);
-	}
-
-	@Override
-	void populate() {
-		removeAllViews();
-		Context ctx = getContext();
-
-		LayoutInflater inflater = (LayoutInflater) ctx.getSystemService
-				(Context.LAYOUT_INFLATER_SERVICE);
-		View view = inflater.inflate(R.layout.invitation_bluetooth_invitation_code, this);
-
-		// local invitation code
-		TextView code = (TextView) view.findViewById(R.id.codeView);
-		int localCode = container.getLocalInvitationCode();
-		code.setText(String.format("%06d", localCode));
-
-		if (waiting) {
-			// hide views we no longer need
-			view.findViewById(R.id.enterCodeTextView).setVisibility(View.GONE);
-			view.findViewById(R.id.codeEntryView).setVisibility(View.GONE);
-			view.findViewById(R.id.continueButton).setVisibility(View.GONE);
-
-			// show progress indicator
-			view.findViewById(R.id.progressBar).setVisibility(View.VISIBLE);
-
-			// show which code we are waiting for
-			TextView connecting = (TextView) view.findViewById(R.id.waitingView);
-			int remoteCode = container.getRemoteInvitationCode();
-			String format = container.getString(R.string.searching_format);
-			connecting.setText(String.format(format, remoteCode));
-			connecting.setVisibility(View.VISIBLE);
-		}
-		else {
-			// handle click on continue button
-			final EditText codeEntry = (EditText) view.findViewById(R.id.codeEntryView);
-			final Button continueButton = (Button) view.findViewById(R.id.continueButton);
-			continueButton.setOnClickListener(new OnClickListener() {
-				@Override
-				public void onClick(View v) {
-					send(codeEntry);
-				}
-			});
-
-			// activate continue button only when we have a 6 digit (CODE_LEN) code
-			codeEntry.addTextChangedListener(new TextWatcher() {
-				@Override
-				public void beforeTextChanged(CharSequence s, int start, int count, int after) {
-				}
-
-				@Override
-				public void onTextChanged(CharSequence s, int start, int before, int count) {
-					continueButton.setEnabled(codeEntry.getText().length() == CODE_LEN);
-				}
-
-				@Override
-				public void afterTextChanged(Editable s) {
-				}
-			});
-
-			codeEntry.setOnEditorActionListener(new TextView.OnEditorActionListener() {
-				@Override
-				public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
-					if (actionId == EditorInfo.IME_ACTION_GO && v.getText().length() == CODE_LEN) {
-						send(v);
-						return true;
-					}
-					return false;
-				}
-			});
-		}
-	}
-
-	private void send(TextView codeEntry) {
-		int code = Integer.parseInt(codeEntry.getText().toString());
-		container.remoteInvitationCodeEntered(code);
-
-		// Hide the soft keyboard
-		Object o = getContext().getSystemService(INPUT_METHOD_SERVICE);
-		((InputMethodManager) o).hideSoftInputFromWindow(codeEntry.getWindowToken(), 0);
-	}
-
-}
diff --git a/briar-android/src/main/res/layout/invitation_bluetooth_confirmation_code.xml b/briar-android/src/main/res/layout/invitation_bluetooth_confirmation_code.xml
deleted file mode 100644
index a5919e47f5a5c36f9280fccd72a093484634df23..0000000000000000000000000000000000000000
--- a/briar-android/src/main/res/layout/invitation_bluetooth_confirmation_code.xml
+++ /dev/null
@@ -1,109 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<ScrollView
-	xmlns:android="http://schemas.android.com/apk/res/android"
-	xmlns:tools="http://schemas.android.com/tools"
-	android:layout_width="match_parent"
-	android:layout_height="match_parent">
-
-	<RelativeLayout
-		android:orientation="vertical"
-		android:layout_width="match_parent"
-		android:layout_height="wrap_content"
-		android:paddingBottom="@dimen/margin_activity_vertical"
-		android:paddingEnd="@dimen/margin_activity_horizontal"
-		android:paddingRight="@dimen/margin_activity_horizontal"
-		android:paddingStart="@dimen/margin_activity_horizontal"
-		android:paddingLeft="@dimen/margin_activity_horizontal"
-		android:paddingTop="@dimen/margin_activity_vertical">
-
-		<TextView
-			android:id="@+id/connectedView"
-			style="@style/BriarTextTitle"
-			android:textSize="@dimen/text_size_large"
-			android:layout_width="wrap_content"
-			android:layout_height="wrap_content"
-			android:text="@string/connected_to_contact"
-			android:padding="@dimen/margin_medium"
-			android:layout_centerHorizontal="true"
-			android:drawableLeft="@drawable/navigation_accept"
-			android:drawableStart="@drawable/navigation_accept"
-			android:gravity="center_vertical"/>
-
-		<TextView
-			android:id="@+id/yourConfirmationCodeView"
-			style="@style/BriarTextBody"
-			android:layout_width="wrap_content"
-			android:layout_height="wrap_content"
-			android:text="@string/your_confirmation_code"
-			android:padding="@dimen/margin_medium"
-			android:layout_below="@+id/connectedView"
-			android:layout_centerHorizontal="true"/>
-
-		<TextView
-			android:id="@+id/codeView"
-			android:layout_width="wrap_content"
-			android:layout_height="wrap_content"
-			android:padding="@dimen/margin_medium"
-			android:textSize="50sp"
-			android:textColor="@color/briar_text_secondary"
-			android:layout_below="@+id/yourConfirmationCodeView"
-			android:layout_centerHorizontal="true"
-			tools:text="1337"/>
-
-		<TextView
-			android:id="@+id/waitingView"
-			style="@style/BriarTextBody"
-			android:layout_width="wrap_content"
-			android:layout_height="wrap_content"
-			android:text="@string/searching_format"
-			android:layout_gravity="center_horizontal"
-			android:padding="@dimen/margin_medium"
-			android:layout_below="@+id/codeView"
-			android:layout_centerHorizontal="true"
-			android:visibility="gone"
-			android:gravity="center_horizontal"/>
-
-		<ProgressBar
-			android:id="@+id/progressBar"
-			style="?android:attr/progressBarStyleLarge"
-			android:layout_width="wrap_content"
-			android:layout_height="wrap_content"
-			android:indeterminate="true"
-			android:layout_below="@+id/waitingView"
-			android:layout_centerHorizontal="true"
-			android:visibility="gone"/>
-
-		<TextView
-			android:id="@+id/enterCodeTextView"
-			style="@style/BriarTextBody"
-			android:layout_width="wrap_content"
-			android:layout_height="wrap_content"
-			android:text="@string/enter_confirmation_code"
-			android:layout_gravity="center_horizontal"
-			android:padding="@dimen/margin_medium"
-			android:layout_below="@+id/codeView"
-			android:layout_centerHorizontal="true"/>
-
-		<include
-			android:id="@+id/codeEntryView"
-			layout="@layout/view_code_entry"
-			android:layout_below="@+id/enterCodeTextView"
-			android:layout_centerHorizontal="true"
-			android:layout_width="wrap_content"
-			android:layout_height="wrap_content"
-			android:layout_margin="@dimen/margin_medium"/>
-
-		<Button
-			android:id="@+id/continueButton"
-			style="@style/BriarButton.Default"
-			android:layout_width="wrap_content"
-			android:layout_height="wrap_content"
-			android:text="@string/continue_button"
-			android:layout_gravity="center_horizontal"
-			android:enabled="false"
-			android:layout_below="@+id/codeEntryView"
-			android:layout_centerHorizontal="true"
-			android:layout_margin="@dimen/margin_medium"/>
-
-	</RelativeLayout>
-</ScrollView>
\ No newline at end of file
diff --git a/briar-android/src/main/res/layout/invitation_bluetooth_invitation_code.xml b/briar-android/src/main/res/layout/invitation_bluetooth_invitation_code.xml
deleted file mode 100644
index 9f8aaa9e4a21bf4633d17f8ca8dce5ee834788be..0000000000000000000000000000000000000000
--- a/briar-android/src/main/res/layout/invitation_bluetooth_invitation_code.xml
+++ /dev/null
@@ -1,96 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<ScrollView
-	xmlns:android="http://schemas.android.com/apk/res/android"
-	xmlns:tools="http://schemas.android.com/tools"
-	android:layout_width="match_parent"
-	android:layout_height="match_parent">
-
-	<RelativeLayout
-		android:orientation="vertical"
-		android:layout_width="match_parent"
-		android:layout_height="wrap_content"
-		android:paddingBottom="@dimen/margin_activity_vertical"
-		android:paddingEnd="@dimen/margin_activity_horizontal"
-		android:paddingRight="@dimen/margin_activity_horizontal"
-		android:paddingStart="@dimen/margin_activity_horizontal"
-		android:paddingLeft="@dimen/margin_activity_horizontal"
-		android:paddingTop="@dimen/margin_activity_vertical">
-
-		<TextView
-			android:id="@+id/yourCodeView"
-			style="@style/BriarTextBody"
-			android:layout_width="wrap_content"
-			android:layout_height="wrap_content"
-			android:text="@string/your_invitation_code"
-			android:layout_marginTop="@dimen/margin_medium"
-			android:layout_centerHorizontal="true"/>
-
-		<TextView
-			android:id="@+id/codeView"
-			android:layout_width="wrap_content"
-			android:layout_height="wrap_content"
-			android:layout_marginTop="@dimen/margin_medium"
-			android:textSize="50sp"
-			android:textColor="@color/briar_text_secondary"
-			android:layout_below="@+id/yourCodeView"
-			android:layout_centerHorizontal="true"
-			tools:text="1337"/>
-
-		<TextView
-			android:id="@+id/waitingView"
-			style="@style/BriarTextBody"
-			android:layout_width="wrap_content"
-			android:layout_height="wrap_content"
-			android:text="@string/searching_format"
-			android:layout_gravity="center_horizontal"
-			android:layout_marginTop="@dimen/margin_medium"
-			android:layout_below="@+id/codeView"
-			android:layout_centerHorizontal="true"
-			android:visibility="gone"
-			android:gravity="center_horizontal"/>
-
-		<ProgressBar
-			android:id="@+id/progressBar"
-			style="?android:attr/progressBarStyleLarge"
-			android:layout_width="wrap_content"
-			android:layout_height="wrap_content"
-			android:layout_marginTop="@dimen/margin_medium"
-			android:indeterminate="true"
-			android:layout_below="@+id/waitingView"
-			android:layout_centerHorizontal="true"
-			android:visibility="gone"/>
-
-		<TextView
-			android:id="@+id/enterCodeTextView"
-			style="@style/BriarTextBody"
-			android:layout_width="wrap_content"
-			android:layout_height="wrap_content"
-			android:text="@string/enter_invitation_code"
-			android:layout_gravity="center_horizontal"
-			android:padding="@dimen/margin_medium"
-			android:layout_below="@+id/codeView"
-			android:layout_centerHorizontal="true"/>
-
-		<include
-			android:id="@+id/codeEntryView"
-			layout="@layout/view_code_entry"
-			android:layout_below="@+id/enterCodeTextView"
-			android:layout_centerHorizontal="true"
-			android:layout_width="wrap_content"
-			android:layout_height="wrap_content"
-			android:layout_margin="@dimen/margin_medium"/>
-
-		<Button
-			android:id="@+id/continueButton"
-			style="@style/BriarButton.Default"
-			android:layout_width="wrap_content"
-			android:layout_height="wrap_content"
-			android:text="@string/continue_button"
-			android:layout_gravity="center_horizontal"
-			android:enabled="false"
-			android:layout_below="@+id/codeEntryView"
-			android:layout_centerHorizontal="true"
-			android:layout_margin="@dimen/margin_medium"/>
-
-	</RelativeLayout>
-</ScrollView>
\ No newline at end of file
diff --git a/briar-android/src/main/res/layout/invitation_bluetooth_start.xml b/briar-android/src/main/res/layout/invitation_bluetooth_start.xml
deleted file mode 100644
index af184c641155f5b1f97a02d6bf2259da9c4183a2..0000000000000000000000000000000000000000
--- a/briar-android/src/main/res/layout/invitation_bluetooth_start.xml
+++ /dev/null
@@ -1,60 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<ScrollView
-	xmlns:android="http://schemas.android.com/apk/res/android"
-	android:layout_width="match_parent"
-	android:layout_height="match_parent">
-
-	<LinearLayout
-		android:layout_width="match_parent"
-		android:layout_height="wrap_content"
-		android:orientation="vertical"
-		android:paddingBottom="@dimen/margin_activity_vertical"
-		android:paddingEnd="@dimen/margin_activity_horizontal"
-		android:paddingLeft="@dimen/margin_activity_horizontal"
-		android:paddingRight="@dimen/margin_activity_horizontal"
-		android:paddingStart="@dimen/margin_activity_horizontal"
-		android:paddingTop="@dimen/margin_activity_vertical">
-
-		<TextView
-			style="@style/BriarTextBody"
-			android:layout_width="match_parent"
-			android:layout_height="wrap_content"
-			android:text="@string/your_nickname"
-			android:visibility="gone"/>
-
-		<Spinner
-			android:id="@+id/spinner"
-			android:layout_width="match_parent"
-			android:layout_height="wrap_content"
-			android:background="@drawable/border_spinner"
-			android:layout_marginTop="@dimen/margin_medium"
-			android:spinnerMode="dropdown"
-			android:visibility="gone"/>
-
-		<ImageView
-			android:id="@+id/imageView"
-			android:layout_width="match_parent"
-			android:layout_height="wrap_content"
-			android:layout_marginTop="@dimen/margin_xlarge"
-			android:adjustViewBounds="true"
-			android:scaleType="fitCenter"
-			android:src="@drawable/bluetooth"/>
-
-		<TextView
-			style="@style/BriarTextBody"
-			android:layout_width="match_parent"
-			android:layout_height="wrap_content"
-			android:layout_marginTop="@dimen/margin_xlarge"
-			android:text="@string/face_to_face"/>
-
-		<Button
-			android:id="@+id/continueButton"
-			style="@style/BriarButton.Default"
-			android:layout_width="match_parent"
-			android:layout_height="wrap_content"
-			android:layout_gravity="center_horizontal"
-			android:layout_marginTop="@dimen/margin_medium"
-			android:text="@string/continue_button"/>
-	</LinearLayout>
-
-</ScrollView>
\ No newline at end of file
diff --git a/briar-android/src/main/res/layout/invitation_error.xml b/briar-android/src/main/res/layout/invitation_error.xml
deleted file mode 100644
index 01fc51da703ecfc11846e334a9683f4b82aa2ca0..0000000000000000000000000000000000000000
--- a/briar-android/src/main/res/layout/invitation_error.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<LinearLayout
-	xmlns:android="http://schemas.android.com/apk/res/android"
-	android:orientation="vertical"
-	android:layout_width="match_parent"
-	android:layout_height="match_parent"
-	android:paddingBottom="@dimen/margin_activity_vertical"
-	android:paddingEnd="@dimen/margin_activity_horizontal"
-	android:paddingStart="@dimen/margin_activity_horizontal"
-	android:paddingTop="@dimen/margin_activity_vertical">
-
-	<TextView
-		android:id="@+id/errorTextView"
-		android:layout_width="wrap_content"
-		android:layout_height="wrap_content"
-		android:text="@string/connection_failed"
-		android:layout_gravity="center_horizontal"
-		android:textSize="@dimen/text_size_large"
-		android:textColor="@color/briar_text_primary"
-		android:drawableStart="@drawable/alerts_and_states_error"
-		android:drawableLeft="@drawable/alerts_and_states_error"
-		android:gravity="center_vertical"
-		android:padding="@dimen/margin_medium"/>
-
-	<TextView
-		android:id="@+id/explanationTextView"
-		android:layout_width="wrap_content"
-		android:layout_height="wrap_content"
-		android:text="@string/interfering"
-		android:textColor="@color/briar_text_primary"
-		android:layout_gravity="center_horizontal"
-		android:padding="@dimen/margin_medium"/>
-
-	<Button
-		android:id="@+id/tryAgainButton"
-		style="@style/BriarButton.Default"
-		android:layout_width="wrap_content"
-		android:layout_height="wrap_content"
-		android:text="@string/try_again_button"
-		android:layout_gravity="center_horizontal"
-		android:layout_margin="@dimen/margin_medium"/>
-
-</LinearLayout>
\ No newline at end of file
diff --git a/briar-android/src/main/res/layout/view_code_entry.xml b/briar-android/src/main/res/layout/view_code_entry.xml
deleted file mode 100644
index 2c735f04618f2d2fd6b9d6b3f2eb3d1d750b77a5..0000000000000000000000000000000000000000
--- a/briar-android/src/main/res/layout/view_code_entry.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<EditText
-	xmlns:android="http://schemas.android.com/apk/res/android"
-	xmlns:tools="http://schemas.android.com/tools"
-	android:id="@+id/codeEntryView"
-	android:layout_width="wrap_content"
-	android:layout_height="wrap_content"
-	android:inputType="number"
-	android:layout_gravity="center_horizontal"
-	android:textSize="@dimen/text_size_xlarge"
-	android:ems="4"
-	android:maxLines="1"
-	android:maxLength="6"
-	android:layout_margin="@dimen/margin_medium"
-	android:imeOptions="actionGo"
-	tools:text="123456"/>
\ No newline at end of file
diff --git a/briar-android/src/main/res/values/strings.xml b/briar-android/src/main/res/values/strings.xml
index 3a20ab2e72b811f5045e517277fefbe22dffa96a..821642c2f87feb85c910886f50ec55aae65f2492 100644
--- a/briar-android/src/main/res/values/strings.xml
+++ b/briar-android/src/main/res/values/strings.xml
@@ -98,24 +98,12 @@
 
 	<!-- Adding Contacts -->
 	<string name="add_contact_title">Add a Contact</string>
-	<string name="your_nickname">Choose the identity you want to use:</string>
 	<string name="face_to_face">You must meet up 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="searching_format">Searching for contact with invitation code %06d\u2026</string>
 	<string name="connection_failed">Connection failed</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>
-	<string name="your_confirmation_code">Your confirmation code is</string>
-	<string name="enter_confirmation_code">Please enter your contact\'s confirmation code:</string>
-	<string name="waiting_for_contact">Waiting for contact\u2026</string>
 	<string name="waiting_for_contact_to_scan">Waiting for contact to scan and connect\u2026</string>
 	<string name="exchanging_contact_details">Exchanging contact details\u2026</string>
-	<string name="codes_do_not_match">Codes do not match</string>
-	<string name="interfering">This could mean that someone is trying to interfere with your connection</string>
 	<string name="contact_added_toast">Contact added: %s</string>
 	<string name="contact_already_exists">Contact %s already exists</string>
 	<string name="contact_exchange_failed">Contact exchange failed</string>