diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index e9dfd8090b5542eb6fa562ae1c19f357b769dc0f..799292896076ba4d1e6f9dde0ab63b54c972806c 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -3,11 +3,23 @@
     package="net.sf.briar"
     android:versionCode="1"
     android:versionName="1.0" >
+
     <uses-sdk android:minSdkVersion="7" android:targetSdkVersion="16" />
+
     <uses-permission android:name="android.permission.BLUETOOTH" />
     <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
     <uses-permission android:name="android.permission.INTERNET" />
-    <application android:label="@string/app_name" >
+    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
+    <uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />
+
+    <application
+        android:icon="@drawable/ic_launcher"
+        android:label="@string/app_name" >
+        <service android:name=".HelloWorldService" android:exported="false" >
+            <intent-filter>
+                <action android:name=".HelloWorldService" />
+            </intent-filter>
+        </service>
         <activity
             android:name=".HelloWorldActivity"
             android:label="@string/app_name" >
@@ -16,10 +28,37 @@
                 <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
         </activity>
-        <service android:name=".HelloWorldService" android:exported="false" >
-            <intent-filter>
-                <action android:name="net.sf.briar.HelloWorldService" />
-            </intent-filter>
-        </service>
+        <activity
+            android:name=".android.invitation.NetworkSetupActivity"
+            android:label="@string/title" >
+        </activity>
+        <activity
+            android:name=".android.invitation.InvitationCodeActivity"
+            android:label="@string/title" >
+        </activity>
+        <activity
+            android:name=".android.invitation.ConnectionActivity"
+            android:label="@string/title" >
+        </activity>
+        <activity
+            android:name=".android.invitation.ConnectionFailedActivity"
+            android:label="@string/title" >
+        </activity>
+        <activity
+            android:name=".android.invitation.ConfirmationCodeActivity"
+            android:label="@string/title" >
+        </activity>
+        <activity
+            android:name=".android.invitation.WaitForContactActivity"
+            android:label="@string/title" >
+        </activity>
+        <activity
+            android:name=".android.invitation.CodesDoNotMatchActivity"
+            android:label="@string/title" >
+        </activity>
+        <activity
+            android:name=".android.invitation.ContactAddedActivity"
+            android:label="@string/title" >
+        </activity>
     </application>
 </manifest>
diff --git a/res/drawable-hdpi/ic_launcher.png b/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000000000000000000000000000000000000..1b63e455be32834b8e0a9240bfaed25b5c69f5f0
Binary files /dev/null and b/res/drawable-hdpi/ic_launcher.png differ
diff --git a/res/drawable-ldpi/ic_launcher.png b/res/drawable-ldpi/ic_launcher.png
new file mode 100644
index 0000000000000000000000000000000000000000..e0119f08678c54c5efd851a1860c9a18978ec3cd
Binary files /dev/null and b/res/drawable-ldpi/ic_launcher.png differ
diff --git a/res/drawable-ldpi/iconic_check_alt_green.png b/res/drawable-ldpi/iconic_check_alt_green.png
new file mode 100644
index 0000000000000000000000000000000000000000..0751e8d49172a3b2746ac1d442383d838586075f
Binary files /dev/null and b/res/drawable-ldpi/iconic_check_alt_green.png differ
diff --git a/res/drawable-ldpi/iconic_x_alt_red.png b/res/drawable-ldpi/iconic_x_alt_red.png
new file mode 100644
index 0000000000000000000000000000000000000000..d560b26460ba89d7cb1cb8465841b7f8d7dd639c
Binary files /dev/null and b/res/drawable-ldpi/iconic_x_alt_red.png differ
diff --git a/res/drawable-mdpi/ic_launcher.png b/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000000000000000000000000000000000000..9873a76f01b7952e9ca72018763aa519a5b8f1e8
Binary files /dev/null and b/res/drawable-mdpi/ic_launcher.png differ
diff --git a/res/drawable-xhdpi/ic_launcher.png b/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 0000000000000000000000000000000000000000..0b22fd5d7a57db85fccf364f07052262aaf8ec22
Binary files /dev/null and b/res/drawable-xhdpi/ic_launcher.png differ
diff --git a/res/layout/activity_add_contact.xml b/res/layout/activity_add_contact.xml
new file mode 100644
index 0000000000000000000000000000000000000000..c37b80fec002560fe9afdadabb4851b2f32d89d1
--- /dev/null
+++ b/res/layout/activity_add_contact.xml
@@ -0,0 +1,5 @@
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/add_contact_container"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+	android:orientation="vertical" />
diff --git a/res/layout/activity_codes_do_not_match.xml b/res/layout/activity_codes_do_not_match.xml
new file mode 100644
index 0000000000000000000000000000000000000000..4bd1965bc9ce73dd643c1c0d4a27ca789b591530
--- /dev/null
+++ b/res/layout/activity_codes_do_not_match.xml
@@ -0,0 +1,5 @@
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/codes_do_not_match_container"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:orientation="vertical" />
diff --git a/res/layout/activity_connection.xml b/res/layout/activity_connection.xml
new file mode 100644
index 0000000000000000000000000000000000000000..367b75fc552e993b3e7876fadbef2b845b0fd74f
--- /dev/null
+++ b/res/layout/activity_connection.xml
@@ -0,0 +1,5 @@
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/connection_container"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:orientation="vertical" />
\ No newline at end of file
diff --git a/res/layout/activity_connection_failed.xml b/res/layout/activity_connection_failed.xml
new file mode 100644
index 0000000000000000000000000000000000000000..9672914f4ca5721234515e26b994ed1455d249a3
--- /dev/null
+++ b/res/layout/activity_connection_failed.xml
@@ -0,0 +1,5 @@
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/connection_failed_container"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:orientation="vertical" />
diff --git a/res/layout/activity_connection_succeeded.xml b/res/layout/activity_connection_succeeded.xml
new file mode 100644
index 0000000000000000000000000000000000000000..0b6962125d6d24eed4d9440a18c9fa0374106649
--- /dev/null
+++ b/res/layout/activity_connection_succeeded.xml
@@ -0,0 +1,5 @@
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/connection_succeeded_container"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:orientation="vertical" />
\ No newline at end of file
diff --git a/res/layout/activity_contact_added.xml b/res/layout/activity_contact_added.xml
new file mode 100644
index 0000000000000000000000000000000000000000..05a3c684f1d1adfc729a5013e6dfe58195586838
--- /dev/null
+++ b/res/layout/activity_contact_added.xml
@@ -0,0 +1,5 @@
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/contact_added_container"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:orientation="vertical" />
diff --git a/res/layout/activity_invitation_code.xml b/res/layout/activity_invitation_code.xml
new file mode 100644
index 0000000000000000000000000000000000000000..193e86aaf5497c14f487d6b76b601d502a40f3a5
--- /dev/null
+++ b/res/layout/activity_invitation_code.xml
@@ -0,0 +1,5 @@
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/invitation_code_container"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+	android:orientation="vertical" />
diff --git a/res/layout/activity_network_setup.xml b/res/layout/activity_network_setup.xml
new file mode 100644
index 0000000000000000000000000000000000000000..48d2de513f0178f509f0b2a2f47bf63675cd55fa
--- /dev/null
+++ b/res/layout/activity_network_setup.xml
@@ -0,0 +1,5 @@
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/network_setup_container"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:orientation="vertical" />
\ No newline at end of file
diff --git a/res/layout/activity_test_bluetooth.xml b/res/layout/activity_test_bluetooth.xml
new file mode 100644
index 0000000000000000000000000000000000000000..eaf4032a4c172d97ced3df021eaed863ae993ee2
--- /dev/null
+++ b/res/layout/activity_test_bluetooth.xml
@@ -0,0 +1,71 @@
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:orientation="vertical" >
+
+    <TextView
+        android:id="@+id/test_bt_screen_title"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="Testing Bluetooth actions"
+        android:textAppearance="?android:attr/textAppearanceLarge" />
+
+    <Button
+        android:id="@+id/button1"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="Show paired devices info"
+        android:onClick="showBtPairedDevicesButtonClicked" />
+
+    <Button
+        android:id="@+id/test_bt_conn_btn"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="Connect bluetooh"
+        android:onClick="testBtConnButtonClicked" />
+
+    <LinearLayout
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content" >
+
+        <Button
+            android:id="@+id/test_bt_sendData_btn"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:onClick="testBtSendDataButtonClicked"
+            android:text="Send data" />
+
+        <Button
+            android:id="@+id/test_bt_recvData_btn"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:onClick="testBtReceiveDataButtonClicked"
+            android:text="Recive data" />
+    </LinearLayout>
+
+    <ScrollView
+        android:id="@+id/test_bt_log_view"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content" >
+
+        <LinearLayout
+            android:layout_width="fill_parent"
+            android:layout_height="fill_parent"
+            android:orientation="vertical" >
+
+            <TextView
+                android:id="@+id/test_bt_log_console_title"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="Bluetooth actions log" />
+
+            <TextView
+                android:id="@+id/test_bt_log_console_msgs"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="actions log..." />
+
+        </LinearLayout>
+    </ScrollView>
+
+</LinearLayout>
diff --git a/res/layout/activity_wait_for_contact.xml b/res/layout/activity_wait_for_contact.xml
new file mode 100644
index 0000000000000000000000000000000000000000..cd96d411b253935b9d8819f08a72ea6ff0e39401
--- /dev/null
+++ b/res/layout/activity_wait_for_contact.xml
@@ -0,0 +1,5 @@
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/wait_for_contact_container"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:orientation="vertical" />
\ No newline at end of file
diff --git a/res/values/strings.xml b/res/values/strings.xml
index eeabdb397e19999f3f41484dd608c492eaeb6cf4..0a2f6313dfc5356be2094f881d70130fd15abed8 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -1,4 +1,40 @@
 <?xml version="1.0" encoding="utf-8"?>
 <resources>
     <string name="app_name">Briar</string>
+    <string name="menu_settings">Settings</string>
+    <string name="title">Add a Contact</string>
+    <string name="welcome">Welcome to Briar! Add a contact to get started.</string>
+    <string name="add_contact_button">Add a contact</string>
+    <string name="face_to_face">For security reasons you must be face to face with someone to add them as a contact.</string>
+    <string name="same_network">Briar can add contacts via Wi-Fi or Bluetooth. To use Wi-Fi you must both be connected to the same network.</string>
+    <string name="wifi_not_available">Wi-Fi is not available on this device.</string>
+    <string name="wifi_disabled">Wi-Fi is OFF.</string>
+    <string name="turn_on_wifi_button">Turn on Wi-Fi</string>
+    <string name="wifi_disconnected">Wi-Fi is DISCONNECTED.</string>
+    <string name="connect_to_wifi_button">Connect to Wi-Fi</string>
+    <string name="wifi_connected">Wi-Fi is CONNECTED to %1$s.</string>
+    <string name="bluetooth_not_available">Bluetooth is not available on this device.</string>
+    <string name="bluetooth_disabled">Bluetooth is OFF.</string>
+    <string name="turn_on_bluetooth_button">Turn on Bluetooth</string>
+    <string name="bluetooth_not_discoverable">Bluetooth is NOT DISCOVERABLE.</string>
+    <string name="make_bluetooth_discoverable_button">Make Bluetooth discoverable</string>
+    <string name="bluetooth_enabled">Bluetooth is ON.</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="connecting_wifi">Connecting via %1$s\u2026</string>
+    <string name="connecting_bluetooth">Connecting via Bluetooth\u2026</string>
+    <string name="connection_failed">Connection failed.</string>
+    <string name="check_same_network">Please check that you are both using the same network.</string>
+    <string name="try_again_button">Try again</string>
+    <string name="connected_to_contact">Connected to contact.</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="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">Contact added.</string>
+    <string name="enter_nickname">Please enter a nickname for this contact:</string>
+    <string name="add_another_contact_button">Add another contact</string>
+    <string name="done_button">Done</string>
 </resources>
diff --git a/src/net/sf/briar/HelloWorldActivity.java b/src/net/sf/briar/HelloWorldActivity.java
index 53607532d3557428ee3790a27a0d6d86a7be7ef7..76868cb80646b61a2855cb60ceb95dd25834b655 100644
--- a/src/net/sf/briar/HelloWorldActivity.java
+++ b/src/net/sf/briar/HelloWorldActivity.java
@@ -1,19 +1,41 @@
 package net.sf.briar;
 
+import net.sf.briar.android.invitation.NetworkSetupActivity;
 import android.app.Activity;
 import android.content.Intent;
 import android.os.Bundle;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.Button;
+import android.widget.LinearLayout;
 import android.widget.TextView;
 
-public class HelloWorldActivity extends Activity {
+public class HelloWorldActivity extends Activity implements OnClickListener {
 
 	@Override
 	public void onCreate(Bundle savedInstanceState) {
 		super.onCreate(savedInstanceState);
-		TextView text = new TextView(this);
-		text.setText("Hello world");
-		setContentView(text);
+		setContentView(R.layout.activity_add_contact);
+		LinearLayout layout = (LinearLayout) findViewById(
+				R.id.add_contact_container);
+
+		TextView welcome = new TextView(this);
+		welcome.setText(R.string.welcome);
+		layout.addView(welcome);
+		Button addContact = new Button(this);
+		addContact.setText(R.string.add_contact_button);
+		addContact.setOnClickListener(this);
+		layout.addView(addContact);
+		TextView faceToFace = new TextView(this);
+		faceToFace.setText(R.string.face_to_face);
+		layout.addView(faceToFace);
+
 		Intent intent = new Intent("net.sf.briar.HelloWorldService");
 		startService(intent);
 	}
+
+	public void onClick(View view) {
+		startActivity(new Intent(this, NetworkSetupActivity.class));
+		finish();
+	}
 }
diff --git a/src/net/sf/briar/android/invitation/BluetoothStateListener.java b/src/net/sf/briar/android/invitation/BluetoothStateListener.java
new file mode 100644
index 0000000000000000000000000000000000000000..fa7846c7c4d79bac01fa724623fdd80b25e8632d
--- /dev/null
+++ b/src/net/sf/briar/android/invitation/BluetoothStateListener.java
@@ -0,0 +1,6 @@
+package net.sf.briar.android.invitation;
+
+interface BluetoothStateListener {
+
+	void bluetoothStateChanged(boolean enabled);
+}
diff --git a/src/net/sf/briar/android/invitation/BluetoothWidget.java b/src/net/sf/briar/android/invitation/BluetoothWidget.java
new file mode 100644
index 0000000000000000000000000000000000000000..e97d7d86e91f6a8f4b6cd2bb4beaf5775865096a
--- /dev/null
+++ b/src/net/sf/briar/android/invitation/BluetoothWidget.java
@@ -0,0 +1,71 @@
+package net.sf.briar.android.invitation;
+
+import static android.bluetooth.BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE;
+import static android.provider.Settings.ACTION_BLUETOOTH_SETTINGS;
+import static android.view.Gravity.CENTER_HORIZONTAL;
+import net.sf.briar.R;
+import android.bluetooth.BluetoothAdapter;
+import android.content.Context;
+import android.content.Intent;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.Button;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+public class BluetoothWidget extends LinearLayout implements OnClickListener {
+
+	private BluetoothStateListener listener = null;
+
+	public BluetoothWidget(Context ctx) {
+		super(ctx);
+	}
+
+	void init(BluetoothStateListener listener) {
+		this.listener = listener;
+		setOrientation(VERTICAL);
+		setPadding(0, 10, 0, 10);
+		populate();
+	}
+
+	void populate() {
+		removeAllViews();
+		Context ctx = getContext();
+		TextView status = new TextView(ctx);
+		status.setGravity(CENTER_HORIZONTAL);
+		BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+		if(adapter == null) {
+			bluetoothStateChanged(false);
+			status.setText(R.string.bluetooth_not_available);
+			addView(status);
+		} else if(adapter.getScanMode() == SCAN_MODE_CONNECTABLE_DISCOVERABLE) {
+			bluetoothStateChanged(true);
+			status.setText(R.string.bluetooth_enabled);
+			addView(status);
+		} else if(adapter.isEnabled()) {
+			bluetoothStateChanged(false);
+			status.setText(R.string.bluetooth_not_discoverable);
+			addView(status);
+			Button turnOn = new Button(ctx);
+			turnOn.setText(R.string.make_bluetooth_discoverable_button);
+			turnOn.setOnClickListener(this);
+			addView(turnOn);
+		} else {
+			bluetoothStateChanged(false);
+			status.setText(R.string.bluetooth_disabled);
+			addView(status);
+			Button turnOn = new Button(ctx);
+			turnOn.setText(R.string.turn_on_bluetooth_button);
+			turnOn.setOnClickListener(this);
+			addView(turnOn);
+		}
+	}
+
+	private void bluetoothStateChanged(boolean enabled) {
+		listener.bluetoothStateChanged(enabled);
+	}
+
+	public void onClick(View view) {
+		getContext().startActivity(new Intent(ACTION_BLUETOOTH_SETTINGS));
+	}
+}
diff --git a/src/net/sf/briar/android/invitation/CodeEntryListener.java b/src/net/sf/briar/android/invitation/CodeEntryListener.java
new file mode 100644
index 0000000000000000000000000000000000000000..1a8f7b3d8d58a9925c030bdf83d428ed57023c6c
--- /dev/null
+++ b/src/net/sf/briar/android/invitation/CodeEntryListener.java
@@ -0,0 +1,6 @@
+package net.sf.briar.android.invitation;
+
+interface CodeEntryListener {
+
+	void codeEntered(String code);
+}
diff --git a/src/net/sf/briar/android/invitation/CodeEntryWidget.java b/src/net/sf/briar/android/invitation/CodeEntryWidget.java
new file mode 100644
index 0000000000000000000000000000000000000000..5565782753448cb19fad83b62ec4872d9f9344a3
--- /dev/null
+++ b/src/net/sf/briar/android/invitation/CodeEntryWidget.java
@@ -0,0 +1,76 @@
+package net.sf.briar.android.invitation;
+
+import static android.text.InputType.TYPE_CLASS_NUMBER;
+import static android.view.Gravity.CENTER;
+import static android.view.Gravity.CENTER_HORIZONTAL;
+import net.sf.briar.R;
+import android.content.Context;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+import android.widget.TextView.OnEditorActionListener;
+
+public class CodeEntryWidget extends LinearLayout implements
+OnEditorActionListener, OnClickListener {
+
+	private CodeEntryListener listener = null;
+	private EditText codeEntry = null;
+
+	public CodeEntryWidget(Context ctx) {
+		super(ctx);
+	}
+
+	void init(CodeEntryListener listener, String prompt) {
+		this.listener = listener;
+		setOrientation(VERTICAL);
+
+		Context ctx = getContext();
+		TextView enterCode = new TextView(ctx);
+		enterCode.setGravity(CENTER_HORIZONTAL);
+		enterCode.setText(prompt);
+		addView(enterCode);
+
+		final Button continueButton = new Button(ctx);
+		continueButton.setText(R.string.continue_button);
+		continueButton.setEnabled(false);
+		continueButton.setOnClickListener(this);
+
+		codeEntry = new EditText(ctx) {
+			@Override
+			protected void onTextChanged(CharSequence text, int start,
+					int lengthBefore, int lengthAfter) {
+				continueButton.setEnabled(text.length() == 6);
+			}
+		};
+		codeEntry.setOnEditorActionListener(this);
+		codeEntry.setMinEms(5);
+		codeEntry.setMaxEms(5);
+		codeEntry.setMaxLines(1);
+		codeEntry.setInputType(TYPE_CLASS_NUMBER);
+
+		LinearLayout innerLayout = new LinearLayout(ctx);
+		innerLayout.setOrientation(HORIZONTAL);
+		innerLayout.setGravity(CENTER);
+		innerLayout.addView(codeEntry);
+		innerLayout.addView(continueButton);
+		addView(innerLayout);
+	}
+
+	public boolean onEditorAction(TextView textView, int actionId, KeyEvent e) {
+		validateAndReturnCode();
+		return true;
+	}
+
+	public void onClick(View view) {
+		validateAndReturnCode();
+	}
+
+	private void validateAndReturnCode() {
+		CharSequence code = codeEntry.getText();
+		if(code.length() == 6) listener.codeEntered(code.toString());
+	}
+}
diff --git a/src/net/sf/briar/android/invitation/CodesDoNotMatchActivity.java b/src/net/sf/briar/android/invitation/CodesDoNotMatchActivity.java
new file mode 100644
index 0000000000000000000000000000000000000000..c981bc067092c6acbae39b7063e8fabad76352b3
--- /dev/null
+++ b/src/net/sf/briar/android/invitation/CodesDoNotMatchActivity.java
@@ -0,0 +1,54 @@
+package net.sf.briar.android.invitation;
+
+import static android.view.Gravity.CENTER;
+import static android.widget.LinearLayout.HORIZONTAL;
+import net.sf.briar.R;
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+public class CodesDoNotMatchActivity extends Activity
+implements OnClickListener {
+
+	@Override
+	public void onCreate(Bundle savedInstanceState) {
+		super.onCreate(savedInstanceState);
+		setContentView(R.layout.activity_codes_do_not_match);
+		LinearLayout outerLayout = (LinearLayout) findViewById(
+				R.id.codes_do_not_match_container);
+
+		LinearLayout innerLayout = new LinearLayout(this);
+		innerLayout.setOrientation(HORIZONTAL);
+		innerLayout.setGravity(CENTER);
+		ImageView icon = new ImageView(this);
+		icon.setImageResource(R.drawable.iconic_x_alt_red);
+		icon.setPadding(10, 10, 10, 10);
+		innerLayout.addView(icon);
+		TextView failed = new TextView(this);
+		failed.setTextSize(20);
+		failed.setText(R.string.codes_do_not_match);
+		innerLayout.addView(failed);
+		outerLayout.addView(innerLayout);
+
+		TextView interfering = new TextView(this);
+		interfering.setText(R.string.interfering);
+		outerLayout.addView(interfering);
+		Button tryAgain = new Button(this);
+		tryAgain.setText(R.string.try_again_button);
+		tryAgain.setOnClickListener(this);
+		outerLayout.addView(tryAgain);
+	}
+
+	public void onClick(View view) {
+		Intent intent = new Intent(this, InvitationCodeActivity.class);
+		intent.putExtras(getIntent().getExtras());
+		startActivity(intent);
+		finish();
+	}
+}
diff --git a/src/net/sf/briar/android/invitation/ConfirmationCodeActivity.java b/src/net/sf/briar/android/invitation/ConfirmationCodeActivity.java
new file mode 100644
index 0000000000000000000000000000000000000000..e4bb901df00ad5b414edca1fba0510365a485ed5
--- /dev/null
+++ b/src/net/sf/briar/android/invitation/ConfirmationCodeActivity.java
@@ -0,0 +1,69 @@
+package net.sf.briar.android.invitation;
+
+import static android.view.Gravity.CENTER;
+import static android.view.Gravity.CENTER_HORIZONTAL;
+import static android.widget.LinearLayout.HORIZONTAL;
+import net.sf.briar.R;
+import android.app.Activity;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.os.Bundle;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+public class ConfirmationCodeActivity extends Activity
+implements CodeEntryListener {
+
+	@Override
+	public void onCreate(Bundle savedInstanceState) {
+		super.onCreate(savedInstanceState);
+		setContentView(R.layout.activity_connection_succeeded);
+		LinearLayout outerLayout = (LinearLayout) findViewById(
+				R.id.connection_succeeded_container);
+
+		LinearLayout innerLayout = new LinearLayout(this);
+		innerLayout.setOrientation(HORIZONTAL);
+		innerLayout.setGravity(CENTER);
+		ImageView icon = new ImageView(this);
+		icon.setImageResource(R.drawable.iconic_check_alt_green);
+		icon.setPadding(10, 10, 10, 10);
+		innerLayout.addView(icon);
+		TextView failed = new TextView(this);
+		failed.setTextSize(20);
+		failed.setText(R.string.connected_to_contact);
+		innerLayout.addView(failed);
+		outerLayout.addView(innerLayout);
+
+		TextView checkNetwork = new TextView(this);
+		checkNetwork.setGravity(CENTER_HORIZONTAL);
+		checkNetwork.setText(R.string.your_confirmation_code);
+		outerLayout.addView(checkNetwork);
+		TextView code = new TextView(this);
+		code.setGravity(CENTER_HORIZONTAL);
+		InvitationManager im = InvitationManagerFactory.getInvitationManager();
+		String localConfirmationCode = im.getLocalConfirmationCode();
+		code.setText(localConfirmationCode);
+		code.setTextSize(50);
+		outerLayout.addView(code);
+		CodeEntryWidget codeEntry = new CodeEntryWidget(this);
+		Resources res = getResources();
+		codeEntry.init(this, res.getString(R.string.enter_confirmation_code));
+		outerLayout.addView(codeEntry);
+	}
+
+	public void codeEntered(String code) {
+		InvitationManager im = InvitationManagerFactory.getInvitationManager();
+		String remoteConfirmationCode = im.getRemoteConfirmationCode();
+		if(code.equals(String.valueOf(remoteConfirmationCode))) {
+			Intent intent = new Intent(this, WaitForContactActivity.class);
+			intent.putExtras(getIntent().getExtras());
+			startActivity(intent);
+		} else {
+			Intent intent = new Intent(this, CodesDoNotMatchActivity.class);
+			intent.putExtras(getIntent().getExtras());
+			startActivity(intent);
+		}
+		finish();
+	}
+}
diff --git a/src/net/sf/briar/android/invitation/ConfirmationListener.java b/src/net/sf/briar/android/invitation/ConfirmationListener.java
new file mode 100644
index 0000000000000000000000000000000000000000..af2024676a4731cb7aa4f73479fa7fa50cbda756
--- /dev/null
+++ b/src/net/sf/briar/android/invitation/ConfirmationListener.java
@@ -0,0 +1,8 @@
+package net.sf.briar.android.invitation;
+
+interface ConfirmationListener {
+
+	void confirmationReceived();
+
+	void confirmationNotReceived();
+}
diff --git a/src/net/sf/briar/android/invitation/ConnectionActivity.java b/src/net/sf/briar/android/invitation/ConnectionActivity.java
new file mode 100644
index 0000000000000000000000000000000000000000..2564adf002c7077c750bc77013fa1cda8cff634b
--- /dev/null
+++ b/src/net/sf/briar/android/invitation/ConnectionActivity.java
@@ -0,0 +1,99 @@
+package net.sf.briar.android.invitation;
+
+import static android.view.Gravity.CENTER;
+import static android.view.Gravity.CENTER_HORIZONTAL;
+import static android.widget.LinearLayout.HORIZONTAL;
+import net.sf.briar.R;
+import android.app.Activity;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.os.Bundle;
+import android.widget.LinearLayout;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+
+public class ConnectionActivity extends Activity implements ConnectionListener {
+
+	private final InvitationManager manager =
+			InvitationManagerFactory.getInvitationManager();
+
+	@Override
+	public void onCreate(Bundle savedInstanceState) {
+		super.onCreate(savedInstanceState);
+		setContentView(R.layout.activity_connection);
+		LinearLayout outerLayout = (LinearLayout) findViewById(
+				R.id.connection_container);
+
+		Bundle b = getIntent().getExtras();
+		String networkName = b.getString(
+				"net.sf.briar.android.invitation.NETWORK_NAME");
+		boolean useBluetooth = b.getBoolean(
+				"net.sf.briar.android.invitation.USE_BLUETOOTH");
+
+		TextView yourCode = new TextView(this);
+		yourCode.setGravity(CENTER_HORIZONTAL);
+		yourCode.setText(R.string.your_invitation_code);
+		outerLayout.addView(yourCode);
+		TextView code = new TextView(this);
+		code.setGravity(CENTER_HORIZONTAL);
+		code.setText(manager.getLocalInvitationCode());
+		code.setTextSize(50);
+		outerLayout.addView(code);
+
+		if(networkName != null) {
+			LinearLayout innerLayout = new LinearLayout(this);
+			innerLayout.setOrientation(HORIZONTAL);
+			innerLayout.setGravity(CENTER);
+			ProgressBar progress = new ProgressBar(this);
+			progress.setIndeterminate(true);
+			progress.setPadding(0, 10, 10, 0);
+			innerLayout.addView(progress);
+			TextView connecting = new TextView(this);
+			Resources res = getResources();
+			String text = res.getString(R.string.connecting_wifi);
+			text = String.format(text, networkName);
+			connecting.setText(text);
+			innerLayout.addView(connecting);
+			outerLayout.addView(innerLayout);
+			manager.startWifiConnectionWorker(this);
+		}
+
+		if(useBluetooth) {
+			LinearLayout innerLayout = new LinearLayout(this);
+			innerLayout.setOrientation(HORIZONTAL);
+			innerLayout.setGravity(CENTER);
+			ProgressBar progress = new ProgressBar(this);
+			progress.setPadding(0, 10, 10, 0);
+			progress.setIndeterminate(true);
+			innerLayout.addView(progress);
+			TextView connecting = new TextView(this);
+			connecting.setText(R.string.connecting_bluetooth);
+			innerLayout.addView(connecting);
+			outerLayout.addView(innerLayout);
+			manager.startBluetoothConnectionWorker(this);
+		}
+
+		manager.tryToConnect(this);
+	}
+
+	public void connectionEstablished() {
+		final Intent intent = new Intent(this, ConfirmationCodeActivity.class);
+		intent.putExtras(getIntent().getExtras());
+		runOnUiThread(new Runnable() {
+			public void run() {
+				startActivity(intent);
+				finish();
+			}
+		});
+	}
+
+	public void connectionNotEstablished() {
+		final Intent intent = new Intent(this, ConnectionFailedActivity.class);
+		runOnUiThread(new Runnable() {
+			public void run() {
+				startActivity(intent);
+				finish();
+			}
+		});
+	}
+}
diff --git a/src/net/sf/briar/android/invitation/ConnectionFailedActivity.java b/src/net/sf/briar/android/invitation/ConnectionFailedActivity.java
new file mode 100644
index 0000000000000000000000000000000000000000..0b868a1740276af0387873cac79e8e02bd7e1ffe
--- /dev/null
+++ b/src/net/sf/briar/android/invitation/ConnectionFailedActivity.java
@@ -0,0 +1,96 @@
+package net.sf.briar.android.invitation;
+
+import static android.view.Gravity.CENTER;
+import static android.view.View.INVISIBLE;
+import static android.view.View.VISIBLE;
+import static android.widget.LinearLayout.HORIZONTAL;
+import net.sf.briar.R;
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+public class ConnectionFailedActivity extends Activity
+implements WifiStateListener, BluetoothStateListener, OnClickListener {
+
+	private WifiWidget wifi = null;
+	private BluetoothWidget bluetooth = null;
+	private Button tryAgainButton = null;
+	private String networkName = null;
+	private boolean useBluetooth = false;
+
+	@Override
+	public void onCreate(Bundle savedInstanceState) {
+		super.onCreate(savedInstanceState);
+		setContentView(R.layout.activity_connection_failed);
+		LinearLayout outerLayout = (LinearLayout) findViewById(
+				R.id.connection_failed_container);
+
+		LinearLayout innerLayout = new LinearLayout(this);
+		innerLayout.setOrientation(HORIZONTAL);
+		innerLayout.setGravity(CENTER);
+		ImageView icon = new ImageView(this);
+		icon.setImageResource(R.drawable.iconic_x_alt_red);
+		icon.setPadding(10, 10, 10, 10);
+		innerLayout.addView(icon);
+		TextView failed = new TextView(this);
+		failed.setTextSize(20);
+		failed.setText(R.string.connection_failed);
+		innerLayout.addView(failed);
+		outerLayout.addView(innerLayout);
+
+		TextView checkNetwork = new TextView(this);
+		checkNetwork.setText(R.string.check_same_network);
+		outerLayout.addView(checkNetwork);
+		wifi = new WifiWidget(this);
+		wifi.init(this);
+		outerLayout.addView(wifi);
+		bluetooth = new BluetoothWidget(this);
+		bluetooth.init(this);
+		outerLayout.addView(bluetooth);
+		tryAgainButton = new Button(this);
+		tryAgainButton.setText(R.string.try_again_button);
+		tryAgainButton.setOnClickListener(this);
+		setTryAgainButtonVisibility();
+		outerLayout.addView(tryAgainButton);
+	}
+
+	@Override
+	public void onResume() {
+		super.onResume();
+		wifi.populate();
+		bluetooth.populate();
+	}
+
+	public void wifiStateChanged(String networkName) {
+		this.networkName = networkName;
+		setTryAgainButtonVisibility();
+	}
+
+	public void bluetoothStateChanged(boolean enabled) {
+		useBluetooth = enabled;
+		setTryAgainButtonVisibility();
+	}
+
+	private void setTryAgainButtonVisibility() {
+		if(tryAgainButton == null) return;
+		if(useBluetooth || networkName != null)
+			tryAgainButton.setVisibility(VISIBLE);
+		else tryAgainButton.setVisibility(INVISIBLE);
+	}
+
+	public void onClick(View view) {
+		Intent intent = new Intent(this, InvitationCodeActivity.class);
+		intent.putExtra("net.sf.briar.android.invitation.NETWORK_NAME",
+				networkName);
+		intent.putExtra("net.sf.briar.android.invitation.USE_BLUETOOTH",
+				useBluetooth);
+		startActivity(intent);
+		finish();
+	}
+}
diff --git a/src/net/sf/briar/android/invitation/ConnectionListener.java b/src/net/sf/briar/android/invitation/ConnectionListener.java
new file mode 100644
index 0000000000000000000000000000000000000000..a1bc643dc3bdc53b17f748759c824104feece6a2
--- /dev/null
+++ b/src/net/sf/briar/android/invitation/ConnectionListener.java
@@ -0,0 +1,8 @@
+package net.sf.briar.android.invitation;
+
+interface ConnectionListener {
+
+	void connectionEstablished();
+
+	void connectionNotEstablished();
+}
diff --git a/src/net/sf/briar/android/invitation/ContactAddedActivity.java b/src/net/sf/briar/android/invitation/ContactAddedActivity.java
new file mode 100644
index 0000000000000000000000000000000000000000..846c25bf1ae9932d1a1dd83322b5b305eb77bb46
--- /dev/null
+++ b/src/net/sf/briar/android/invitation/ContactAddedActivity.java
@@ -0,0 +1,91 @@
+package net.sf.briar.android.invitation;
+
+import static android.view.Gravity.CENTER;
+import static android.view.Gravity.CENTER_HORIZONTAL;
+import static android.widget.LinearLayout.HORIZONTAL;
+import net.sf.briar.R;
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+import android.widget.TextView.OnEditorActionListener;
+
+public class ContactAddedActivity extends Activity implements OnClickListener,
+OnEditorActionListener {
+
+	private volatile Button done = null;
+
+	@Override
+	public void onCreate(Bundle savedInstanceState) {
+		super.onCreate(savedInstanceState);
+		setContentView(R.layout.activity_contact_added);
+		LinearLayout outerLayout = (LinearLayout) findViewById(
+				R.id.contact_added_container);
+
+		LinearLayout innerLayout = new LinearLayout(this);
+		innerLayout.setOrientation(HORIZONTAL);
+		innerLayout.setGravity(CENTER);
+		ImageView icon = new ImageView(this);
+		icon.setImageResource(R.drawable.iconic_check_alt_green);
+		icon.setPadding(10, 10, 10, 10);
+		innerLayout.addView(icon);
+		TextView failed = new TextView(this);
+		failed.setTextSize(20);
+		failed.setText(R.string.contact_added);
+		innerLayout.addView(failed);
+		outerLayout.addView(innerLayout);
+
+		TextView enterNickname = new TextView(this);
+		enterNickname.setGravity(CENTER_HORIZONTAL);
+		enterNickname.setText(R.string.enter_nickname);
+		outerLayout.addView(enterNickname);
+		final Button addAnother = new Button(this);
+		final Button done = new Button(this);
+		this.done = done;
+		EditText nicknameEntry = new EditText(this) {
+			@Override
+			protected void onTextChanged(CharSequence text, int start,
+					int lengthBefore, int lengthAfter) {
+				addAnother.setEnabled(text.length() > 0);
+				done.setEnabled(text.length() > 0);
+			}
+		};
+		nicknameEntry.setMinEms(10);
+		nicknameEntry.setMaxEms(20);
+		nicknameEntry.setMaxLines(1);
+		nicknameEntry.setOnEditorActionListener(this);
+		outerLayout.addView(nicknameEntry);
+
+		innerLayout = new LinearLayout(this);
+		innerLayout.setOrientation(HORIZONTAL);
+		innerLayout.setGravity(CENTER);
+		addAnother.setText(R.string.add_another_contact_button);
+		addAnother.setEnabled(false);
+		addAnother.setOnClickListener(this);
+		innerLayout.addView(addAnother);
+		done.setText(R.string.done_button);
+		done.setEnabled(false);
+		done.setOnClickListener(this);
+		innerLayout.addView(done);
+		outerLayout.addView(innerLayout);
+	}
+
+	public boolean onEditorAction(TextView textView, int actionId, KeyEvent e) {
+		if(textView.getText().length() > 0) finish();
+		return true;
+	}
+
+	public void onClick(View view) {
+		if(done == null) return;
+		if(view != done)
+			startActivity(new Intent(this, NetworkSetupActivity.class));
+		finish();
+	}
+}
diff --git a/src/net/sf/briar/android/invitation/InvitationCodeActivity.java b/src/net/sf/briar/android/invitation/InvitationCodeActivity.java
new file mode 100644
index 0000000000000000000000000000000000000000..79e7a22cd97bd0351696b74406b9d88b45def5ea
--- /dev/null
+++ b/src/net/sf/briar/android/invitation/InvitationCodeActivity.java
@@ -0,0 +1,48 @@
+package net.sf.briar.android.invitation;
+
+import static android.view.Gravity.CENTER_HORIZONTAL;
+import net.sf.briar.R;
+import android.app.Activity;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.os.Bundle;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+public class InvitationCodeActivity extends Activity
+implements CodeEntryListener {
+
+	private final InvitationManager manager =
+			InvitationManagerFactory.getInvitationManager();
+
+	@Override
+	public void onCreate(Bundle savedInstanceState) {
+		super.onCreate(savedInstanceState);
+		setContentView(R.layout.activity_invitation_code);
+		LinearLayout layout = (LinearLayout) findViewById(
+				R.id.invitation_code_container);
+
+		TextView yourCode = new TextView(this);
+		yourCode.setGravity(CENTER_HORIZONTAL);
+		yourCode.setText(R.string.your_invitation_code);
+		layout.addView(yourCode);
+		TextView code = new TextView(this);
+		code.setGravity(CENTER_HORIZONTAL);
+		String localInvitationCode = manager.getLocalInvitationCode();
+		code.setText(localInvitationCode);
+		code.setTextSize(50);
+		layout.addView(code);
+		CodeEntryWidget codeEntry = new CodeEntryWidget(this);
+		Resources res = getResources();
+		codeEntry.init(this, res.getString(R.string.enter_invitation_code));
+		layout.addView(codeEntry);
+	}
+
+	public void codeEntered(String code) {
+		manager.setRemoteInvitationCode(code);
+		Intent intent = new Intent(this, ConnectionActivity.class);
+		intent.putExtras(getIntent().getExtras());
+		startActivity(intent);
+		finish();
+	}
+}
diff --git a/src/net/sf/briar/android/invitation/InvitationManager.java b/src/net/sf/briar/android/invitation/InvitationManager.java
new file mode 100644
index 0000000000000000000000000000000000000000..f77baa4c33aa76824a0603eb6eb7e52cde5682b7
--- /dev/null
+++ b/src/net/sf/briar/android/invitation/InvitationManager.java
@@ -0,0 +1,26 @@
+package net.sf.briar.android.invitation;
+
+import android.content.Context;
+
+interface InvitationManager {
+
+	int TIMEOUT = 20 * 1000;
+
+	void tryToConnect(ConnectionListener listener);
+
+	String getLocalInvitationCode();
+
+	String getRemoteInvitationCode();
+
+	void setRemoteInvitationCode(String code);
+
+	void startWifiConnectionWorker(Context ctx);
+
+	void startBluetoothConnectionWorker(Context ctx);
+
+	String getLocalConfirmationCode();
+
+	String getRemoteConfirmationCode();
+
+	void startConfirmationWorker(ConfirmationListener listener);
+}
diff --git a/src/net/sf/briar/android/invitation/InvitationManagerFactory.java b/src/net/sf/briar/android/invitation/InvitationManagerFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..a38ac435ade195507a70316a2890c7da29afe586
--- /dev/null
+++ b/src/net/sf/briar/android/invitation/InvitationManagerFactory.java
@@ -0,0 +1,14 @@
+package net.sf.briar.android.invitation;
+
+class InvitationManagerFactory {
+
+	private static final Object LOCK = new Object();
+	private static InvitationManager instance = null; // Locking: lock
+
+	static InvitationManager getInvitationManager() {
+		synchronized(LOCK) {
+			if(instance == null) instance = new InvitationManagerImpl();
+			return instance;
+		}
+	}
+}
diff --git a/src/net/sf/briar/android/invitation/InvitationManagerImpl.java b/src/net/sf/briar/android/invitation/InvitationManagerImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..c5afcacec161d9bd116b7846a8bd8aa61640ef96
--- /dev/null
+++ b/src/net/sf/briar/android/invitation/InvitationManagerImpl.java
@@ -0,0 +1,67 @@
+package net.sf.briar.android.invitation;
+
+import android.content.Context;
+import android.util.Log;
+
+class InvitationManagerImpl implements InvitationManager {
+
+	public void tryToConnect(final ConnectionListener listener) {
+		new Thread() {
+			@Override
+			public void run() {
+				try {
+					// FIXME
+					Thread.sleep((long) (Math.random() * TIMEOUT));
+					if(Math.random() < 0.5) listener.connectionEstablished();
+					else listener.connectionNotEstablished();
+				} catch(InterruptedException e) {
+					Log.w(getClass().getName(), e.toString());
+					listener.connectionNotEstablished();
+				}
+			}
+		}.start();
+	}
+
+	public String getLocalInvitationCode() {
+		// FIXME
+		return "123456";
+	}
+
+	public String getRemoteInvitationCode() {
+		// FIXME
+		return "123456";
+	}
+
+	public void setRemoteInvitationCode(String code) {
+		// FIXME
+	}
+
+	public void startWifiConnectionWorker(Context ctx) {
+		// FIXME
+	}
+
+	public void startBluetoothConnectionWorker(Context ctx) {
+		// FIXME
+	}
+
+	public String getLocalConfirmationCode() {
+		// FIXME
+		return "123456";
+	}
+
+	public String getRemoteConfirmationCode() {
+		// FIXME
+		return "123456";
+	}
+
+	public void startConfirmationWorker(ConfirmationListener listener) {
+		// FIXME
+		try {
+			Thread.sleep(1000 + (int) (Math.random() * 4 * 1000));
+		} catch(InterruptedException e) {
+			Log.w(getClass().getName(), e.toString());
+			Thread.currentThread().interrupt();
+		}
+		listener.confirmationReceived();
+	}
+}
diff --git a/src/net/sf/briar/android/invitation/NetworkSetupActivity.java b/src/net/sf/briar/android/invitation/NetworkSetupActivity.java
new file mode 100644
index 0000000000000000000000000000000000000000..2488e4d12525968f971b73d8aed3a0ad92c1999b
--- /dev/null
+++ b/src/net/sf/briar/android/invitation/NetworkSetupActivity.java
@@ -0,0 +1,88 @@
+package net.sf.briar.android.invitation;
+
+import static android.view.View.INVISIBLE;
+import static android.view.View.VISIBLE;
+import net.sf.briar.R;
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.Button;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+public class NetworkSetupActivity extends Activity
+implements WifiStateListener, BluetoothStateListener, OnClickListener {
+
+	private WifiWidget wifi = null;
+	private BluetoothWidget bluetooth = null;
+	private Button continueButton = null;
+	private String networkName = null;
+	private boolean useBluetooth = false;
+
+	@Override
+	public void onCreate(Bundle savedInstanceState) {
+		super.onCreate(savedInstanceState);
+		setContentView(R.layout.activity_network_setup);
+		LinearLayout layout = (LinearLayout) findViewById(
+				R.id.network_setup_container);
+
+		TextView sameNetwork = new TextView(this);
+		sameNetwork.setText(R.string.same_network);
+		layout.addView(sameNetwork);
+		wifi = new WifiWidget(this);
+		wifi.init(this);
+		layout.addView(wifi);
+		bluetooth = new BluetoothWidget(this);
+		bluetooth.init(this);
+		layout.addView(bluetooth);
+		continueButton = new Button(this);
+		continueButton.setText(R.string.continue_button);
+		continueButton.setOnClickListener(this);
+		setContinueButtonVisibility();
+		layout.addView(continueButton);
+	}
+
+	@Override
+	public void onResume() {
+		super.onResume();
+		wifi.populate();
+		bluetooth.populate();
+	}
+
+	public void wifiStateChanged(final String name) {
+		runOnUiThread(new Runnable() {
+			public void run() {
+				networkName = name;
+				setContinueButtonVisibility();
+			}
+		});
+	}
+
+	public void bluetoothStateChanged(final boolean enabled) {
+		runOnUiThread(new Runnable() {
+			public void run() {
+				useBluetooth = enabled;
+				setContinueButtonVisibility();
+			}
+		});
+	}
+
+	private void setContinueButtonVisibility() {
+		if(continueButton == null) return;
+		if(useBluetooth || networkName != null)
+			continueButton.setVisibility(VISIBLE);
+		else continueButton.setVisibility(INVISIBLE);
+	}
+
+	public void onClick(View view) {
+		Intent intent = new Intent(this, InvitationCodeActivity.class);
+		intent.putExtra("net.sf.briar.android.invitation.NETWORK_NAME",
+				networkName);
+		intent.putExtra("net.sf.briar.android.invitation.USE_BLUETOOTH",
+				useBluetooth);
+		startActivity(intent);
+		finish();
+	}
+}
diff --git a/src/net/sf/briar/android/invitation/WaitForContactActivity.java b/src/net/sf/briar/android/invitation/WaitForContactActivity.java
new file mode 100644
index 0000000000000000000000000000000000000000..ed778ff23ffc1db084b4625db0139f42ef8ab88f
--- /dev/null
+++ b/src/net/sf/briar/android/invitation/WaitForContactActivity.java
@@ -0,0 +1,76 @@
+package net.sf.briar.android.invitation;
+
+import static android.view.Gravity.CENTER;
+import static android.view.Gravity.CENTER_HORIZONTAL;
+import static android.widget.LinearLayout.HORIZONTAL;
+import net.sf.briar.R;
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+
+public class WaitForContactActivity extends Activity
+implements ConfirmationListener {
+
+	@Override
+	public void onCreate(Bundle savedInstanceState) {
+		super.onCreate(savedInstanceState);
+		setContentView(R.layout.activity_wait_for_contact);
+		LinearLayout outerLayout = (LinearLayout) findViewById(
+				R.id.wait_for_contact_container);
+
+		LinearLayout innerLayout = new LinearLayout(this);
+		innerLayout.setOrientation(HORIZONTAL);
+		innerLayout.setGravity(CENTER);
+		ImageView icon = new ImageView(this);
+		icon.setImageResource(R.drawable.iconic_check_alt_green);
+		icon.setPadding(10, 10, 10, 10);
+		innerLayout.addView(icon);
+		TextView failed = new TextView(this);
+		failed.setTextSize(20);
+		failed.setText(R.string.connected_to_contact);
+		innerLayout.addView(failed);
+		outerLayout.addView(innerLayout);
+
+		TextView yourCode = new TextView(this);
+		yourCode.setGravity(CENTER_HORIZONTAL);
+		yourCode.setText(R.string.your_confirmation_code);
+		outerLayout.addView(yourCode);
+		TextView code = new TextView(this);
+		code.setGravity(CENTER_HORIZONTAL);
+		InvitationManager im = InvitationManagerFactory.getInvitationManager();
+		String localConfirmationCode = im.getLocalConfirmationCode();
+		code.setText(localConfirmationCode);
+		code.setTextSize(50);
+		outerLayout.addView(code);
+
+		innerLayout = new LinearLayout(this);
+		innerLayout.setOrientation(HORIZONTAL);
+		innerLayout.setGravity(CENTER);
+		ProgressBar progress = new ProgressBar(this);
+		progress.setIndeterminate(true);
+		progress.setPadding(0, 10, 10, 0);
+		innerLayout.addView(progress);
+		TextView connecting = new TextView(this);
+		connecting.setText(R.string.waiting_for_contact);
+		innerLayout.addView(connecting);
+		outerLayout.addView(innerLayout);
+
+		im.startConfirmationWorker(this);
+	}
+
+	public void confirmationReceived() {
+		startActivity(new Intent(this, ContactAddedActivity.class));
+		finish();
+	}
+
+	public void confirmationNotReceived() {
+		Intent intent = new Intent(this, CodesDoNotMatchActivity.class);
+		intent.putExtras(getIntent().getExtras());
+		startActivity(intent);
+		finish();
+	}
+}
diff --git a/src/net/sf/briar/android/invitation/WifiStateListener.java b/src/net/sf/briar/android/invitation/WifiStateListener.java
new file mode 100644
index 0000000000000000000000000000000000000000..c668b0d2d37df6b224445777eed662509b2347f4
--- /dev/null
+++ b/src/net/sf/briar/android/invitation/WifiStateListener.java
@@ -0,0 +1,6 @@
+package net.sf.briar.android.invitation;
+
+interface WifiStateListener {
+
+	void wifiStateChanged(String networkName);
+}
diff --git a/src/net/sf/briar/android/invitation/WifiWidget.java b/src/net/sf/briar/android/invitation/WifiWidget.java
new file mode 100644
index 0000000000000000000000000000000000000000..d27d6e8b3a871c98479e456aacb883e5429f3f0f
--- /dev/null
+++ b/src/net/sf/briar/android/invitation/WifiWidget.java
@@ -0,0 +1,77 @@
+package net.sf.briar.android.invitation;
+
+import static android.content.Context.WIFI_SERVICE;
+import static android.provider.Settings.ACTION_WIFI_SETTINGS;
+import static android.view.Gravity.CENTER_HORIZONTAL;
+import net.sf.briar.R;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.net.wifi.WifiManager;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.Button;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+public class WifiWidget extends LinearLayout implements OnClickListener {
+
+	private WifiStateListener listener = null;
+
+	public WifiWidget(Context ctx) {
+		super(ctx);
+	}
+
+	void init(WifiStateListener listener) {
+		this.listener = listener;
+		setOrientation(VERTICAL);
+		setPadding(0, 10, 0, 0);
+		populate();
+	}
+
+	void populate() {
+		removeAllViews();
+		Context ctx = getContext();
+		TextView status = new TextView(ctx);
+		status.setGravity(CENTER_HORIZONTAL);
+		WifiManager wifi = (WifiManager) ctx.getSystemService(WIFI_SERVICE);
+		if(wifi == null) {
+			wifiStateChanged(null);
+			status.setText(R.string.wifi_not_available);
+			addView(status);
+		} else if(wifi.isWifiEnabled()) { 
+			String networkName =  wifi.getConnectionInfo().getSSID();
+			if(networkName == null) {
+				wifiStateChanged(null);
+				status.setText(R.string.wifi_disconnected);
+				addView(status);
+				Button connect = new Button(ctx);
+				connect.setText(R.string.connect_to_wifi_button);
+				connect.setOnClickListener(this);
+				addView(connect);
+			} else {
+				wifiStateChanged(networkName);
+				Resources res = getResources();
+				String connected = res.getString(R.string.wifi_connected);
+				status.setText(String.format(connected, networkName));
+				addView(status);
+			}
+		} else {
+			wifiStateChanged(null);
+			status.setText(R.string.wifi_disabled);
+			addView(status);
+			Button connect = new Button(ctx);
+			connect.setText(R.string.connect_to_wifi_button);
+			connect.setOnClickListener(this);
+			addView(connect);
+		}
+	}
+
+	private void wifiStateChanged(String networkName) {
+		if(listener != null) listener.wifiStateChanged(networkName);
+	}
+
+	public void onClick(View view) {
+		getContext().startActivity(new Intent(ACTION_WIFI_SETTINGS));
+	}
+}