diff --git a/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/CameraView.java b/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/CameraView.java
index 3b0003fa82b55eb1c3ac252d442b349c8b8c167d..814bfe8b4a5594132cea1007db3bff04383dc4db 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/CameraView.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/CameraView.java
@@ -6,7 +6,6 @@ import android.hardware.Camera.AutoFocusCallback;
 import android.hardware.Camera.CameraInfo;
 import android.hardware.Camera.Parameters;
 import android.hardware.Camera.Size;
-import android.os.Build;
 import android.support.annotation.Nullable;
 import android.support.annotation.UiThread;
 import android.util.AttributeSet;
@@ -22,6 +21,7 @@ import java.io.IOException;
 import java.util.List;
 import java.util.logging.Logger;
 
+import static android.hardware.Camera.CameraInfo.CAMERA_FACING_BACK;
 import static android.hardware.Camera.CameraInfo.CAMERA_FACING_FRONT;
 import static android.hardware.Camera.Parameters.FLASH_MODE_OFF;
 import static android.hardware.Camera.Parameters.FOCUS_MODE_AUTO;
@@ -32,6 +32,7 @@ import static android.hardware.Camera.Parameters.FOCUS_MODE_FIXED;
 import static android.hardware.Camera.Parameters.FOCUS_MODE_MACRO;
 import static android.hardware.Camera.Parameters.SCENE_MODE_AUTO;
 import static android.hardware.Camera.Parameters.SCENE_MODE_BARCODE;
+import static android.os.Build.VERSION.SDK_INT;
 import static java.util.logging.Level.INFO;
 import static java.util.logging.Level.WARNING;
 
@@ -41,7 +42,12 @@ import static java.util.logging.Level.WARNING;
 public class CameraView extends SurfaceView implements SurfaceHolder.Callback,
 		AutoFocusCallback, View.OnClickListener {
 
+	// Heuristic for the ideal preview size - small previews don't have enough
+	// detail, large previews are slow to decode
+	private static final int IDEAL_PIXELS = 500 * 1000;
+
 	private static final int AUTO_FOCUS_RETRY_DELAY = 5000; // Milliseconds
+
 	private static final Logger LOG =
 			Logger.getLogger(CameraView.class.getName());
 
@@ -49,6 +55,7 @@ public class CameraView extends SurfaceView implements SurfaceHolder.Callback,
 
 	@Nullable
 	private Camera camera = null;
+	private int cameraIndex = 0;
 	private PreviewConsumer previewConsumer = null;
 	private Surface surface = null;
 	private int displayOrientation = 0, surfaceWidth = 0, surfaceHeight = 0;
@@ -89,15 +96,32 @@ public class CameraView extends SurfaceView implements SurfaceHolder.Callback,
 	}
 
 	@UiThread
-	public void start() throws CameraException {
+	public void start(int rotationDegrees) throws CameraException {
 		LOG.info("Opening camera");
 		try {
-			camera = Camera.open();
+			int cameras = Camera.getNumberOfCameras();
+			if (cameras == 0) throw new CameraException("No camera");
+			// Try to find a back-facing camera
+			for (int i = 0; i < cameras; i++) {
+				CameraInfo info = new CameraInfo();
+				Camera.getCameraInfo(i, info);
+				if (info.facing == CAMERA_FACING_BACK) {
+					LOG.info("Using back-facing camera");
+					camera = Camera.open(i);
+					cameraIndex = i;
+					break;
+				}
+			}
+			// If we can't find a back-facing camera, use a front-facing one
+			if (camera == null) {
+				LOG.info("Using front-facing camera");
+				camera = Camera.open(0);
+				cameraIndex = 0;
+			}
 		} catch (RuntimeException e) {
 			throw new CameraException(e);
 		}
-		if (camera == null) throw new CameraException("No back-facing camera");
-		setDisplayOrientation(0);
+		setDisplayOrientation(rotationDegrees);
 		// Use barcode scene mode if it's available
 		Parameters params = camera.getParameters();
 		params = setSceneMode(camera, params);
@@ -163,7 +187,7 @@ public class CameraView extends SurfaceView implements SurfaceHolder.Callback,
 	private void startConsumer() throws CameraException {
 		if (camera == null) throw new CameraException("Camera is null");
 		startAutoFocus();
-		previewConsumer.start(camera);
+		previewConsumer.start(camera, cameraIndex);
 	}
 
 	@UiThread
@@ -199,13 +223,17 @@ public class CameraView extends SurfaceView implements SurfaceHolder.Callback,
 		}
 	}
 
+	/**
+	 * See {@link Camera#setDisplayOrientation(int)}.
+	 */
 	@UiThread
 	private void setDisplayOrientation(int rotationDegrees)
 			throws CameraException {
+		if (camera == null) throw new CameraException("Camera is null");
 		int orientation;
 		CameraInfo info = new CameraInfo();
 		try {
-			Camera.getCameraInfo(0, info);
+			Camera.getCameraInfo(cameraIndex, info);
 		} catch (RuntimeException e) {
 			throw new CameraException(e);
 		}
@@ -215,9 +243,11 @@ public class CameraView extends SurfaceView implements SurfaceHolder.Callback,
 		} else {
 			orientation = (info.orientation - rotationDegrees + 360) % 360;
 		}
-		if (LOG.isLoggable(INFO))
-			LOG.info("Display orientation " + orientation + " degrees");
-		if (camera == null) throw new CameraException("Camera is null");
+		if (LOG.isLoggable(INFO)) {
+			LOG.info("Screen rotation " + rotationDegrees
+					+ " degrees, camera orientation " + orientation
+					+ " degrees");
+		}
 		try {
 			camera.setDisplayOrientation(orientation);
 		} catch (RuntimeException e) {
@@ -285,8 +315,7 @@ public class CameraView extends SurfaceView implements SurfaceHolder.Callback,
 
 	@UiThread
 	private void setVideoStabilisation(Parameters params) {
-		if (Build.VERSION.SDK_INT >= 15 &&
-				params.isVideoStabilizationSupported()) {
+		if (SDK_INT >= 15 && params.isVideoStabilizationSupported()) {
 			params.setVideoStabilization(true);
 		}
 	}
@@ -313,6 +342,8 @@ public class CameraView extends SurfaceView implements SurfaceHolder.Callback,
 	@UiThread
 	private void setPreviewSize(Parameters params) {
 		if (surfaceWidth == 0 || surfaceHeight == 0) return;
+		// Choose a preview size that's close to the aspect ratio of the
+		// surface and close to the ideal size for decoding
 		float idealRatio = (float) surfaceWidth / surfaceHeight;
 		boolean rotatePreview = displayOrientation % 180 == 90;
 		List<Size> sizes = params.getSupportedPreviewSizes();
@@ -323,11 +354,12 @@ public class CameraView extends SurfaceView implements SurfaceHolder.Callback,
 			int height = rotatePreview ? size.width : size.height;
 			float ratio = (float) width / height;
 			float stretch = Math.max(ratio / idealRatio, idealRatio / ratio);
-			int pixels = width * height;
-			float score = pixels / stretch;
+			float pixels = width * height;
+			float zoom = Math.max(pixels / IDEAL_PIXELS, IDEAL_PIXELS / pixels);
+			float score = 1 / (stretch * zoom);
 			if (LOG.isLoggable(INFO)) {
 				LOG.info("Size " + size.width + "x" + size.height
-						+ ", stretch " + stretch + ", pixels " + pixels
+						+ ", stretch " + stretch + ", zoom " + zoom
 						+ ", score " + score);
 			}
 			if (bestSize == null || score > bestScore) {
@@ -358,7 +390,7 @@ public class CameraView extends SurfaceView implements SurfaceHolder.Callback,
 			} catch (RuntimeException e) {
 				throw new CameraException(e);
 			}
-			if (Build.VERSION.SDK_INT >= 15) {
+			if (SDK_INT >= 15) {
 				LOG.info("Video stabilisation enabled: "
 						+ params.getVideoStabilization());
 			}
@@ -389,8 +421,7 @@ public class CameraView extends SurfaceView implements SurfaceHolder.Callback,
 			surface.release();
 		}
 		surface = holder.getSurface();
-		// Start the preview when the camera and the surface are both ready
-		if (camera != null && !previewStarted) startPreview(holder);
+		// We'll start the preview when surfaceChanged() is called
 	}
 
 	@Override
@@ -416,7 +447,7 @@ public class CameraView extends SurfaceView implements SurfaceHolder.Callback,
 		surfaceWidth = w;
 		surfaceHeight = h;
 		if (camera == null) return; // We are stopped
-		stopPreview();
+		if (previewStarted) stopPreview();
 		try {
 			Parameters params = camera.getParameters();
 			setPreviewSize(params);
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/PreviewConsumer.java b/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/PreviewConsumer.java
index 9e5d7a5e9239d1c402e93210960f8f59de09ebec..11735caebbc78716f206df22750644943a490484 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/PreviewConsumer.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/PreviewConsumer.java
@@ -10,7 +10,7 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
 interface PreviewConsumer {
 
 	@UiThread
-	void start(Camera camera);
+	void start(Camera camera, int cameraIndex);
 
 	@UiThread
 	void stop();
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/QrCodeDecoder.java b/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/QrCodeDecoder.java
index 17a14f34782b336229ce093d3489aca0179706ba..e191c640500a6af4afec18aed69a04a1eaaeb85f 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/QrCodeDecoder.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/QrCodeDecoder.java
@@ -1,6 +1,7 @@
 package org.briarproject.briar.android.keyagreement;
 
 import android.hardware.Camera;
+import android.hardware.Camera.CameraInfo;
 import android.hardware.Camera.PreviewCallback;
 import android.hardware.Camera.Size;
 import android.os.AsyncTask;
@@ -38,20 +39,23 @@ class QrCodeDecoder implements PreviewConsumer, PreviewCallback {
 	private final ResultCallback callback;
 
 	private Camera camera = null;
+	private int cameraIndex = 0;
 
 	QrCodeDecoder(ResultCallback callback) {
 		this.callback = callback;
 	}
 
 	@Override
-	public void start(Camera camera) {
+	public void start(Camera camera, int cameraIndex) {
 		this.camera = camera;
+		this.cameraIndex = cameraIndex;
 		askForPreviewFrame();
 	}
 
 	@Override
 	public void stop() {
 		camera = null;
+		cameraIndex = 0;
 	}
 
 	@UiThread
@@ -63,46 +67,65 @@ class QrCodeDecoder implements PreviewConsumer, PreviewCallback {
 	@Override
 	public void onPreviewFrame(byte[] data, Camera camera) {
 		if (camera == this.camera) {
+			LOG.info("Got preview frame");
 			try {
 				Size size = camera.getParameters().getPreviewSize();
-				new DecoderTask(data, size.width, size.height).execute();
+				// The preview should be in NV21 format: width * height bytes of
+				// Y followed by width * height / 2 bytes of interleaved U and V
+				if (data.length == size.width * size.height * 3 / 2) {
+					CameraInfo info = new CameraInfo();
+					Camera.getCameraInfo(cameraIndex, info);
+					new DecoderTask(data, size.width, size.height,
+							info.orientation).execute();
+				} else {
+					// Camera parameters have changed - ask for a new preview
+					LOG.info("Preview size does not match camera parameters");
+					askForPreviewFrame();
+				}
 			} catch (RuntimeException e) {
 				LOG.log(WARNING, "Error getting camera parameters.", e);
 			}
+		} else {
+			LOG.info("Camera has changed, ignoring preview frame");
 		}
 	}
 
 	private class DecoderTask extends AsyncTask<Void, Void, Void> {
 
 		private final byte[] data;
-		private final int width, height;
+		private final int width, height, orientation;
 
-		private DecoderTask(byte[] data, int width, int height) {
+		private DecoderTask(byte[] data, int width, int height,
+				int orientation) {
 			this.data = data;
 			this.width = width;
 			this.height = height;
+			this.orientation = orientation;
 		}
 
 		@Override
 		protected Void doInBackground(Void... params) {
 			long now = System.currentTimeMillis();
-			LuminanceSource src = new PlanarYUVLuminanceSource(data, width,
-					height, 0, 0, width, height, false);
-			BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(src));
+			BinaryBitmap bitmap = binarize(data, width, height, orientation);
 			Result result;
 			try {
 				result = reader.decode(bitmap,
 						singletonMap(CHARACTER_SET, "ISO8859_1"));
+				long duration = System.currentTimeMillis() - now;
+				if (LOG.isLoggable(INFO))
+					LOG.info("Decoding barcode took " + duration + " ms");
 			} catch (ReaderException e) {
-				return null; // No barcode found
+				// No barcode found
+				long duration = System.currentTimeMillis() - now;
+				if (LOG.isLoggable(INFO))
+					LOG.info("No barcode found after " + duration + " ms");
+				return null;
 			} catch (RuntimeException e) {
-				return null; // Preview data did not match width and height
+				LOG.warning("Invalid preview frame");
+				return null;
 			} finally {
 				reader.reset();
 			}
-			long duration = System.currentTimeMillis() - now;
-			if (LOG.isLoggable(INFO))
-				LOG.info("Decoding barcode took " + duration + " ms");
 			callback.handleResult(result);
 			return null;
 		}
@@ -113,6 +136,19 @@ class QrCodeDecoder implements PreviewConsumer, PreviewCallback {
 		}
 	}
 
+	private static BinaryBitmap binarize(byte[] data, int width, int height,
+			int orientation) {
+		// Crop to a square at the top (portrait) or left (landscape) of the
+		// screen - this will be faster to decode and should include
+		// everything visible in the viewfinder
+		int crop = Math.min(width, height);
+		int left = orientation >= 180 ? width - crop : 0;
+		int top = orientation >= 180 ? height - crop : 0;
+		LuminanceSource src = new PlanarYUVLuminanceSource(data, width,
+				height, left, top, crop, crop, false);
+		return new BinaryBitmap(new HybridBinarizer(src));
+	}
+
 	@NotNullByDefault
 	interface ResultCallback {
 
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/QrCodeUtils.java b/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/QrCodeUtils.java
index a4099c298dd843a920c872559b462a63b58a8541..00f1a51fa0875336605d09420bd4ec07ccf4e1e9 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/QrCodeUtils.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/QrCodeUtils.java
@@ -32,21 +32,24 @@ class QrCodeUtils {
 			// Generate QR code
 			BitMatrix encoded = new QRCodeWriter().encode(input, QR_CODE,
 					smallestDimen, smallestDimen);
-			// Convert QR code to Bitmap
-			int width = encoded.getWidth();
-			int height = encoded.getHeight();
-			int[] pixels = new int[width * height];
-			for (int x = 0; x < width; x++) {
-				for (int y = 0; y < height; y++) {
-					pixels[y * width + x] = encoded.get(x, y) ? BLACK : WHITE;
-				}
-			}
-			Bitmap qr = Bitmap.createBitmap(width, height, ARGB_8888);
-			qr.setPixels(pixels, 0, width, 0, 0, width, height);
-			return qr;
+			return renderQrCode(encoded);
 		} catch (WriterException e) {
 			if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
 			return null;
 		}
 	}
+
+	private static Bitmap renderQrCode(BitMatrix matrix) {
+		int width = matrix.getWidth();
+		int height = matrix.getHeight();
+		int[] pixels = new int[width * height];
+		for (int x = 0; x < width; x++) {
+			for (int y = 0; y < height; y++) {
+				pixels[y * width + x] = matrix.get(x, y) ? BLACK : WHITE;
+			}
+		}
+		Bitmap qr = Bitmap.createBitmap(width, height, ARGB_8888);
+		qr.setPixels(pixels, 0, width, 0, 0, width, height);
+		return qr;
+	}
 }
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/ShowQrCodeFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/ShowQrCodeFragment.java
index 24f0fb1be24859668161b8cb23ea22f98113399b..b89f630d105a40418fad4ee9f6e43a3ddcb047e5 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/ShowQrCodeFragment.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/ShowQrCodeFragment.java
@@ -2,11 +2,14 @@ package org.briarproject.briar.android.keyagreement;
 
 import android.content.Context;
 import android.graphics.Bitmap;
+import android.hardware.Camera;
 import android.os.AsyncTask;
 import android.os.Bundle;
 import android.support.annotation.UiThread;
 import android.util.DisplayMetrics;
+import android.view.Display;
 import android.view.LayoutInflater;
+import android.view.Surface;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.animation.AlphaAnimation;
@@ -138,13 +141,32 @@ public class ShowQrCodeFragment extends BaseEventFragment
 	public void onStart() {
 		super.onStart();
 		try {
-			cameraView.start();
+			cameraView.start(getScreenRotationDegrees());
 		} catch (CameraException e) {
 			logCameraExceptionAndFinish(e);
 		}
 		startListening();
 	}
 
+	/**
+	 * See {@link Camera#setDisplayOrientation(int)}.
+	 */
+	private int getScreenRotationDegrees() {
+		Display d = getActivity().getWindowManager().getDefaultDisplay();
+		switch (d.getRotation()) {
+			case Surface.ROTATION_0:
+				return 0;
+			case Surface.ROTATION_90:
+				return 90;
+			case Surface.ROTATION_180:
+				return 180;
+			case Surface.ROTATION_270:
+				return 270;
+			default:
+				throw new AssertionError();
+		}
+	}
+
 	@Override
 	public void onStop() {
 		super.onStop();
@@ -200,6 +222,7 @@ public class ShowQrCodeFragment extends BaseEventFragment
 				LOG.info("Remote payload is " + payloadBytes.length + " bytes");
 			Payload remotePayload = payloadParser.parse(payloadBytes);
 			gotRemotePayload = true;
+			cameraView.stop();
 			cameraView.setVisibility(INVISIBLE);
 			statusView.setVisibility(VISIBLE);
 			status.setText(R.string.connecting_to_device);
@@ -209,6 +232,8 @@ public class ShowQrCodeFragment extends BaseEventFragment
 			String msg = getString(R.string.qr_code_unsupported,
 					getString(R.string.app_name));
 			showNextFragment(ErrorFragment.newInstance(msg));
+		} catch (CameraException e) {
+			logCameraExceptionAndFinish(e);
 		} catch (IOException | IllegalArgumentException e) {
 			if (LOG.isLoggable(WARNING)) LOG.log(WARNING, "QR Code Invalid", e);
 			reset();
diff --git a/briar-android/src/main/res/layout-land/fragment_keyagreement_id.xml b/briar-android/src/main/res/layout-land/fragment_keyagreement_id.xml
index 155721bb4aa1f0fc0e73f47c13f3fb948c176abd..2c98d297489b9d371b64a9276657a3b419604916 100644
--- a/briar-android/src/main/res/layout-land/fragment_keyagreement_id.xml
+++ b/briar-android/src/main/res/layout-land/fragment_keyagreement_id.xml
@@ -1,79 +1,89 @@
 <?xml version="1.0" encoding="utf-8"?>
-<LinearLayout
+<ScrollView
+	android:id="@+id/scrollView"
 	xmlns:android="http://schemas.android.com/apk/res/android"
+	xmlns:app="http://schemas.android.com/apk/res-auto"
 	xmlns:tools="http://schemas.android.com/tools"
 	android:layout_width="match_parent"
-	android:layout_height="match_parent"
-	android:orientation="horizontal">
+	android:layout_height="wrap_content"
+	android:orientation="vertical">
 
-	<ImageView
-		android:id="@+id/imageView"
-		android:layout_width="0dp"
+	<android.support.constraint.ConstraintLayout
+		android:layout_width="match_parent"
 		android:layout_height="wrap_content"
-		android:layout_gravity="center_vertical"
-		android:layout_weight="1"
-		android:adjustViewBounds="true"
-		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"
-		android:scaleType="fitCenter"
-		android:src="@drawable/qr_code_intro"/>
+		android:padding="@dimen/margin_large">
 
-	<ScrollView
-		android:id="@+id/scrollView"
-		android:layout_width="0dp"
-		android:layout_height="match_parent"
-		android:layout_weight="1">
-
-		<LinearLayout
-			android:layout_width="match_parent"
+		<ImageView
+			android:id="@+id/diagram"
+			android:layout_width="0dp"
 			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">
+			android:layout_weight="1"
+			android:adjustViewBounds="true"
+			android:padding="@dimen/margin_medium"
+			android:scaleType="fitCenter"
+			android:src="@drawable/qr_code_intro"
+			app:layout_constraintTop_toTopOf="parent"
+			app:layout_constraintStart_toStartOf="parent"
+			app:layout_constraintEnd_toStartOf="@id/explanationText"
+			app:layout_constraintBottom_toBottomOf="@id/explanationText"/>
 
-			<LinearLayout
-				android:layout_width="match_parent"
-				android:layout_height="wrap_content"
-				android:background="@drawable/border_explanation"
-				android:orientation="vertical"
-				android:padding="@dimen/margin_large">
-
-				<ImageView
-					android:layout_width="match_parent"
-					android:layout_height="wrap_content"
-					android:adjustViewBounds="true"
-					android:padding="@dimen/margin_medium"
-					android:src="@drawable/qr_code_explanation"
-					tools:ignore="ContentDescription"/>
+		<ImageView
+			android:id="@+id/explanationImage"
+			android:layout_width="0dp"
+			android:layout_height="wrap_content"
+			android:layout_weight="1"
+			android:adjustViewBounds="true"
+			android:paddingTop="@dimen/margin_large"
+			android:paddingLeft="@dimen/margin_large"
+			android:paddingRight="@dimen/margin_large"
+			android:scaleType="fitCenter"
+			android:src="@drawable/qr_code_explanation"
+			tools:ignore="ContentDescription"
+			app:layout_constraintTop_toTopOf="parent"
+			app:layout_constraintStart_toEndOf="@id/diagram"
+			app:layout_constraintEnd_toEndOf="parent"
+			app:layout_constraintBottom_toTopOf="@id/explanationText"/>
 
-				<TextView
-					style="@style/BriarTextBody"
-					android:layout_width="match_parent"
-					android:layout_height="wrap_content"
-					android:layout_marginTop="@dimen/margin_medium"
-					android:text="@string/face_to_face"/>
+		<TextView
+			android:id="@+id/explanationText"
+			style="@style/BriarTextBody"
+			android:layout_width="0dp"
+			android:layout_height="wrap_content"
+			android:layout_weight="1"
+			android:padding="@dimen/margin_large"
+			android:text="@string/face_to_face"
+			app:layout_constraintTop_toBottomOf="@id/explanationImage"
+			app:layout_constraintStart_toEndOf="@id/diagram"
+			app:layout_constraintEnd_toEndOf="parent"/>
 
-			</LinearLayout>
+		<View
+			android:id="@+id/explanationBorder"
+			android:layout_width="0dp"
+			android:layout_height="0dp"
+			android:background="@drawable/border_explanation"
+			app:layout_constraintTop_toTopOf="@id/explanationImage"
+			app:layout_constraintStart_toStartOf="@id/explanationImage"
+			app:layout_constraintEnd_toEndOf="@id/explanationImage"
+			app:layout_constraintBottom_toBottomOf="@id/explanationText"/>
 
-			<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"/>
+		<android.support.constraint.Barrier
+			android:id="@+id/barrier"
+			android:layout_width="wrap_content"
+			android:layout_height="wrap_content"
+			app:barrierDirection="bottom"
+			app:constraint_referenced_ids="diagram,explanationBorder"/>
 
-		</LinearLayout>
+		<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"
+			app:layout_constraintTop_toBottomOf="@id/barrier"
+			app:layout_constraintBottom_toBottomOf="parent"/>
 
-	</ScrollView>
+	</android.support.constraint.ConstraintLayout>
 
-</LinearLayout>
\ No newline at end of file
+</ScrollView>
\ No newline at end of file
diff --git a/briar-android/src/main/res/layout-land/fragment_keyagreement_qr.xml b/briar-android/src/main/res/layout-land/fragment_keyagreement_qr.xml
new file mode 100644
index 0000000000000000000000000000000000000000..e9a174c77812fdc41074268acede649e1c3a08e8
--- /dev/null
+++ b/briar-android/src/main/res/layout-land/fragment_keyagreement_qr.xml
@@ -0,0 +1,96 @@
+<?xml version="1.0" encoding="utf-8"?>
+<FrameLayout
+	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">
+
+	<org.briarproject.briar.android.keyagreement.CameraView
+		android:id="@+id/camera_view"
+		android:layout_width="match_parent"
+		android:layout_height="match_parent"/>
+
+	<LinearLayout
+		android:id="@+id/camera_overlay"
+		android:layout_width="match_parent"
+		android:layout_height="match_parent"
+		android:orientation="horizontal"
+		android:weightSum="2">
+
+		<FrameLayout
+			android:layout_width="0dp"
+			android:layout_height="match_parent"
+			android:layout_weight="1">
+
+			<LinearLayout
+				android:id="@+id/status_container"
+				android:layout_width="match_parent"
+				android:layout_height="match_parent"
+				android:background="@android:color/background_light"
+				android:gravity="center"
+				android:orientation="vertical"
+				android:padding="@dimen/margin_medium"
+				android:visibility="invisible">
+
+				<ProgressBar
+					style="?android:attr/progressBarStyleLarge"
+					android:layout_width="wrap_content"
+					android:layout_height="wrap_content"/>
+
+				<TextView
+					android:id="@+id/connect_status"
+					android:layout_width="match_parent"
+					android:layout_height="wrap_content"
+					android:gravity="center"
+					android:paddingTop="@dimen/margin_large"
+					tools:text="Connection failed"/>
+			</LinearLayout>
+		</FrameLayout>
+
+		<FrameLayout
+			android:layout_width="0dp"
+			android:layout_height="match_parent"
+			android:layout_weight="1"
+			android:background="@android:color/white">
+
+			<ProgressBar
+				style="?android:attr/progressBarStyleLarge"
+				android:layout_width="wrap_content"
+				android:layout_height="wrap_content"
+				android:layout_gravity="center"/>
+
+			<ImageView
+				android:id="@+id/qr_code"
+				android:layout_width="match_parent"
+				android:layout_height="match_parent"
+				android:scaleType="fitCenter"
+				android:layout_gravity="center"/>
+		</FrameLayout>
+	</LinearLayout>
+
+	<RelativeLayout
+		android:id="@+id/container_progress"
+		android:layout_width="match_parent"
+		android:layout_height="match_parent"
+		android:background="@android:color/white"
+		android:visibility="invisible">
+
+		<ProgressBar
+			android:id="@+id/progress_bar"
+			style="?android:attr/progressBarStyleLarge"
+			android:layout_width="wrap_content"
+			android:layout_height="wrap_content"
+			android:layout_above="@+id/title_progress_bar"
+			android:layout_centerHorizontal="true"/>
+
+		<TextView
+			android:id="@+id/title_progress_bar"
+			android:layout_width="match_parent"
+			android:layout_height="wrap_content"
+			android:layout_centerInParent="true"
+			android:gravity="center"
+			android:paddingTop="@dimen/margin_large"
+			tools:text="@string/waiting_for_contact_to_scan"/>
+	</RelativeLayout>
+
+</FrameLayout>
diff --git a/briar-android/src/main/res/layout/fragment_keyagreement_id.xml b/briar-android/src/main/res/layout/fragment_keyagreement_id.xml
index 61041ef2f49d55f846fdc1ead7acb4b835a00801..dc584118261da93611c8f53471cdeb12695706bd 100644
--- a/briar-android/src/main/res/layout/fragment_keyagreement_id.xml
+++ b/briar-android/src/main/res/layout/fragment_keyagreement_id.xml
@@ -2,56 +2,62 @@
 <ScrollView
 	android:id="@+id/scrollView"
 	xmlns:android="http://schemas.android.com/apk/res/android"
+	xmlns:app="http://schemas.android.com/apk/res-auto"
 	xmlns:tools="http://schemas.android.com/tools"
 	android:layout_width="match_parent"
-	android:layout_height="match_parent">
+	android:layout_height="wrap_content"
+	android:orientation="vertical">
 
-	<LinearLayout
+	<android.support.constraint.ConstraintLayout
 		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">
+		android:padding="@dimen/margin_large">
 
 		<ImageView
-			android:id="@+id/imageView"
-			android:layout_width="match_parent"
+			android:id="@+id/diagram"
+			android:layout_width="wrap_content"
 			android:layout_height="wrap_content"
+			android:layout_gravity="center_vertical"
 			android:adjustViewBounds="true"
+			android:paddingBottom="@dimen/margin_large"
 			android:scaleType="fitCenter"
 			android:src="@drawable/qr_code_intro"
-			tools:ignore="ContentDescription"/>
+			app:layout_constraintTop_toTopOf="parent"
+			app:layout_constraintBottom_toTopOf="@id/explanationImage"/>
 
-		<LinearLayout
-			android:layout_width="match_parent"
+		<ImageView
+			android:id="@+id/explanationImage"
+			android:layout_width="wrap_content"
 			android:layout_height="wrap_content"
-			android:layout_marginLeft="@dimen/margin_small"
-			android:layout_marginRight="@dimen/margin_small"
-			android:layout_marginTop="@dimen/margin_xlarge"
-			android:background="@drawable/border_explanation"
-			android:orientation="vertical"
-			android:padding="@dimen/margin_large">
-
-			<ImageView
-				android:layout_width="match_parent"
-				android:layout_height="wrap_content"
-				android:adjustViewBounds="true"
-				android:padding="@dimen/margin_medium"
-				android:src="@drawable/qr_code_explanation"
-				android:contentDescription="@string/face_to_face"/>
+			android:adjustViewBounds="true"
+			android:paddingTop="@dimen/margin_large"
+			android:paddingLeft="@dimen/margin_large"
+			android:paddingRight="@dimen/margin_large"
+			android:scaleType="fitCenter"
+			android:src="@drawable/qr_code_explanation"
+			tools:ignore="ContentDescription"
+			app:layout_constraintTop_toBottomOf="@id/diagram"
+			app:layout_constraintBottom_toTopOf="@id/explanationText"/>
 
-			<TextView
-				style="@style/BriarTextBody"
-				android:layout_width="match_parent"
-				android:layout_height="wrap_content"
-				android:layout_marginTop="@dimen/margin_medium"
-				android:text="@string/face_to_face"/>
+		<TextView
+			android:id="@+id/explanationText"
+			style="@style/BriarTextBody"
+			android:layout_width="match_parent"
+			android:layout_height="wrap_content"
+			android:padding="@dimen/margin_large"
+			android:text="@string/face_to_face"
+			app:layout_constraintTop_toBottomOf="@id/explanationImage"
+			app:layout_constraintBottom_toTopOf="@id/continueButton"/>
 
-		</LinearLayout>
+		<View
+			android:id="@+id/explanationBorder"
+			android:layout_width="0dp"
+			android:layout_height="0dp"
+			android:background="@drawable/border_explanation"
+			app:layout_constraintTop_toTopOf="@id/explanationImage"
+			app:layout_constraintStart_toStartOf="@id/explanationImage"
+			app:layout_constraintEnd_toEndOf="@id/explanationImage"
+			app:layout_constraintBottom_toBottomOf="@id/explanationText"/>
 
 		<Button
 			android:id="@+id/continueButton"
@@ -60,8 +66,10 @@
 			android:layout_height="wrap_content"
 			android:layout_gravity="center_horizontal"
 			android:layout_marginTop="@dimen/margin_medium"
-			android:text="@string/continue_button"/>
+			android:text="@string/continue_button"
+			app:layout_constraintTop_toBottomOf="@id/explanationText"
+			app:layout_constraintBottom_toBottomOf="parent"/>
 
-	</LinearLayout>
+	</android.support.constraint.ConstraintLayout>
 
 </ScrollView>
\ No newline at end of file