diff --git a/briar-android/src/main/AndroidManifest.xml b/briar-android/src/main/AndroidManifest.xml
index 0f483f12b9b60b6fc7ea8cb5b07a72131a477af4..3c3ac063e6085c12a485364264e48268f73f0070 100644
--- a/briar-android/src/main/AndroidManifest.xml
+++ b/briar-android/src/main/AndroidManifest.xml
@@ -300,7 +300,7 @@
 		</activity>
 
 		<activity
-			android:name="org.briarproject.briar.android.keyagreement.KeyAgreementActivity"
+			android:name="org.briarproject.briar.android.keyagreement.ContactExchangeActivity"
 			android:label="@string/add_contact_title"
 			android:theme="@style/BriarTheme.NoActionBar"
 			android:parentActivityName="org.briarproject.briar.android.navdrawer.NavDrawerActivity">
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 68564c4e49f615cf295a705394cf5149ab19096e..50b5416877ea13f06630cd11cb5e8a586be8ab00 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
@@ -26,6 +26,7 @@ import org.briarproject.briar.android.fragment.ScreenFilterDialogFragment;
 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.keyagreement.ContactExchangeActivity;
 import org.briarproject.briar.android.keyagreement.IntroFragment;
 import org.briarproject.briar.android.keyagreement.KeyAgreementActivity;
 import org.briarproject.briar.android.keyagreement.ShowQrCodeFragment;
@@ -100,6 +101,8 @@ public interface ActivityComponent {
 
 	void inject(PanicPreferencesActivity activity);
 
+	void inject(ContactExchangeActivity activity);
+
 	void inject(KeyAgreementActivity activity);
 
 	void inject(ConversationActivity activity);
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/contact/ContactListFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/contact/ContactListFragment.java
index 3b32650715dd7bcdea74db8ba3bfd0358059d0e0..2dc00d67f63d6cd1e96507df45627f3de7a63d00 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/contact/ContactListFragment.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/contact/ContactListFragment.java
@@ -33,7 +33,7 @@ import org.briarproject.briar.R;
 import org.briarproject.briar.android.activity.ActivityComponent;
 import org.briarproject.briar.android.contact.BaseContactListAdapter.OnContactClickListener;
 import org.briarproject.briar.android.fragment.BaseFragment;
-import org.briarproject.briar.android.keyagreement.KeyAgreementActivity;
+import org.briarproject.briar.android.keyagreement.ContactExchangeActivity;
 import org.briarproject.briar.android.view.BriarRecyclerView;
 import org.briarproject.briar.api.android.AndroidNotificationManager;
 import org.briarproject.briar.api.client.BaseMessageHeader;
@@ -165,7 +165,7 @@ public class ContactListFragment extends BaseFragment implements EventListener {
 		switch (item.getItemId()) {
 			case R.id.action_add_contact:
 				Intent intent =
-						new Intent(getContext(), KeyAgreementActivity.class);
+						new Intent(getContext(), ContactExchangeActivity.class);
 				startActivity(intent);
 				return true;
 			default:
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/ContactExchangeActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/ContactExchangeActivity.java
new file mode 100644
index 0000000000000000000000000000000000000000..5a1d1868acaa85cf6c01492185d46ef76db4a6a7
--- /dev/null
+++ b/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/ContactExchangeActivity.java
@@ -0,0 +1,108 @@
+package org.briarproject.briar.android.keyagreement;
+
+import android.os.Bundle;
+import android.widget.Toast;
+
+import org.briarproject.bramble.api.contact.ContactExchangeListener;
+import org.briarproject.bramble.api.contact.ContactExchangeTask;
+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.keyagreement.KeyAgreementResult;
+import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
+import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
+import org.briarproject.briar.R.string;
+import org.briarproject.briar.android.activity.ActivityComponent;
+
+import java.util.logging.Logger;
+
+import javax.annotation.Nullable;
+import javax.inject.Inject;
+
+import static android.widget.Toast.LENGTH_LONG;
+import static java.util.logging.Level.WARNING;
+
+@MethodsNotNullByDefault
+@ParametersNotNullByDefault
+public class ContactExchangeActivity extends KeyAgreementActivity implements
+		ContactExchangeListener {
+
+	private static final Logger LOG =
+			Logger.getLogger(ContactExchangeActivity.class.getName());
+
+	// Fields that are accessed from background threads must be volatile
+	@Inject
+	volatile ContactExchangeTask contactExchangeTask;
+	@Inject
+	volatile IdentityManager identityManager;
+
+	@Override
+	public void injectActivity(ActivityComponent component) {
+		component.inject(this);
+	}
+
+	@Override
+	public void onCreate(@Nullable Bundle state) {
+		super.onCreate(state);
+		getSupportActionBar().setTitle(string.add_contact_title);
+	}
+
+	@Override
+	void keyAgreementFinished(KeyAgreementResult result) {
+		runOnUiThreadUnlessDestroyed(() -> startContactExchange(result));
+	}
+
+	private void startContactExchange(KeyAgreementResult result) {
+		runOnDbThread(() -> {
+			LocalAuthor localAuthor;
+			// Load the local pseudonym
+			try {
+				localAuthor = identityManager.getLocalAuthor();
+			} catch (DbException e) {
+				if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
+				contactExchangeFailed();
+				return;
+			}
+
+			// Exchange contact details
+			contactExchangeTask.startExchange(ContactExchangeActivity.this,
+					localAuthor, result.getMasterKey(),
+					result.getConnection(), result.getTransportId(),
+					result.wasAlice());
+		});
+	}
+
+	@Override
+	public void contactExchangeSucceeded(Author remoteAuthor) {
+		runOnUiThreadUnlessDestroyed(() -> {
+			String contactName = remoteAuthor.getName();
+			String format = getString(string.contact_added_toast);
+			String text = String.format(format, contactName);
+			Toast.makeText(ContactExchangeActivity.this, text, LENGTH_LONG)
+					.show();
+			supportFinishAfterTransition();
+		});
+	}
+
+	@Override
+	public void duplicateContact(Author remoteAuthor) {
+		runOnUiThreadUnlessDestroyed(() -> {
+			String contactName = remoteAuthor.getName();
+			String format = getString(string.contact_already_exists);
+			String text = String.format(format, contactName);
+			Toast.makeText(ContactExchangeActivity.this, text, LENGTH_LONG)
+					.show();
+			finish();
+		});
+	}
+
+	@Override
+	public void contactExchangeFailed() {
+		runOnUiThreadUnlessDestroyed(() -> {
+			Toast.makeText(ContactExchangeActivity.this,
+					string.contact_exchange_failed, LENGTH_LONG).show();
+			finish();
+		});
+	}
+}
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/KeyAgreementActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/KeyAgreementActivity.java
index c64841dd30cd6b52b310ad9975dda3d6a9cfe595..9ec105ac2b1aaafcf03b0175a169154affd7497b 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/KeyAgreementActivity.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/KeyAgreementActivity.java
@@ -16,15 +16,9 @@ import android.support.v7.widget.Toolbar;
 import android.view.MenuItem;
 import android.widget.Toast;
 
-import org.briarproject.bramble.api.contact.ContactExchangeListener;
-import org.briarproject.bramble.api.contact.ContactExchangeTask;
-import org.briarproject.bramble.api.db.DbException;
 import org.briarproject.bramble.api.event.Event;
 import org.briarproject.bramble.api.event.EventBus;
 import org.briarproject.bramble.api.event.EventListener;
-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.keyagreement.KeyAgreementResult;
 import org.briarproject.bramble.api.keyagreement.event.KeyAgreementFinishedEvent;
 import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
@@ -60,9 +54,8 @@ import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_PERMI
 
 @MethodsNotNullByDefault
 @ParametersNotNullByDefault
-public class KeyAgreementActivity extends BriarActivity implements
-		BaseFragmentListener, IntroScreenSeenListener, EventListener,
-		ContactExchangeListener {
+public abstract class KeyAgreementActivity extends BriarActivity implements
+		BaseFragmentListener, IntroScreenSeenListener, EventListener {
 
 	private enum BluetoothState {
 		UNKNOWN, NO_ADAPTER, WAITING, REFUSED, ENABLED
@@ -74,12 +67,6 @@ public class KeyAgreementActivity extends BriarActivity implements
 	@Inject
 	EventBus eventBus;
 
-	// Fields that are accessed from background threads must be volatile
-	@Inject
-	volatile ContactExchangeTask contactExchangeTask;
-	@Inject
-	volatile IdentityManager identityManager;
-
 	private boolean isResumed = false, enableWasRequested = false;
 	private boolean continueClicked, gotCameraPermission;
 	private BluetoothState bluetoothState = BluetoothState.UNKNOWN;
@@ -95,13 +82,9 @@ public class KeyAgreementActivity extends BriarActivity implements
 	public void onCreate(@Nullable Bundle state) {
 		super.onCreate(state);
 		setContentView(R.layout.activity_fragment_container_toolbar);
-
 		Toolbar toolbar = findViewById(R.id.toolbar);
-
 		setSupportActionBar(toolbar);
 		getSupportActionBar().setDisplayHomeAsUpEnabled(true);
-
-		getSupportActionBar().setTitle(R.string.add_contact_title);
 		if (state == null) {
 			showInitialFragment(IntroFragment.newInstance());
 		}
@@ -148,7 +131,7 @@ public class KeyAgreementActivity extends BriarActivity implements
 		if (canShowQrCodeFragment()) showQrCodeFragment();
 	}
 
-	boolean canShowQrCodeFragment() {
+	private boolean canShowQrCodeFragment() {
 		return isResumed && continueClicked
 				&& (SDK_INT < 23 || gotCameraPermission)
 				&& bluetoothState != BluetoothState.UNKNOWN
@@ -207,6 +190,7 @@ public class KeyAgreementActivity extends BriarActivity implements
 	}
 
 	private void showQrCodeFragment() {
+		continueClicked = false;
 		// FIXME #824
 		FragmentManager fm = getSupportFragmentManager();
 		if (fm.findFragmentByTag(ShowQrCodeFragment.TAG) == null) {
@@ -288,6 +272,8 @@ public class KeyAgreementActivity extends BriarActivity implements
 		}
 	}
 
+<<<<<<<HEAD
+
 	private void keyAgreementFinished(KeyAgreementResult result) {
 		runOnUiThreadUnlessDestroyed(() -> startContactExchange(result));
 	}
@@ -342,6 +328,14 @@ public class KeyAgreementActivity extends BriarActivity implements
 			finish();
 		});
 	}
+=======
+
+	abstract void keyAgreementFinished(KeyAgreementResult result);
+>>>>>>>927ab9571...
+	Add some
+	abstraction to
+	the keyagreement
+	ui
 
 	private class BluetoothStateReceiver extends BroadcastReceiver {