From 2046ed0cac7e5ebc973867bd6fcf2be6ab085d9f Mon Sep 17 00:00:00 2001
From: akwizgran <michael@briarproject.org>
Date: Tue, 6 Nov 2012 15:04:01 +0000
Subject: [PATCH] Merged Android invitation UI from add_contact repo.

---
 AndroidManifest.xml                           |  51 +++++++--
 res/drawable-hdpi/ic_launcher.png             | Bin 0 -> 2364 bytes
 res/drawable-ldpi/ic_launcher.png             | Bin 0 -> 1276 bytes
 res/drawable-ldpi/iconic_check_alt_green.png  | Bin 0 -> 499 bytes
 res/drawable-ldpi/iconic_x_alt_red.png        | Bin 0 -> 552 bytes
 res/drawable-mdpi/ic_launcher.png             | Bin 0 -> 1591 bytes
 res/drawable-xhdpi/ic_launcher.png            | Bin 0 -> 3192 bytes
 res/layout/activity_add_contact.xml           |   5 +
 res/layout/activity_codes_do_not_match.xml    |   5 +
 res/layout/activity_connection.xml            |   5 +
 res/layout/activity_connection_failed.xml     |   5 +
 res/layout/activity_connection_succeeded.xml  |   5 +
 res/layout/activity_contact_added.xml         |   5 +
 res/layout/activity_invitation_code.xml       |   5 +
 res/layout/activity_network_setup.xml         |   5 +
 res/layout/activity_test_bluetooth.xml        |  71 +++++++++++++
 res/layout/activity_wait_for_contact.xml      |   5 +
 res/values/strings.xml                        |  36 +++++++
 src/net/sf/briar/HelloWorldActivity.java      |  30 +++++-
 .../invitation/BluetoothStateListener.java    |   6 ++
 .../android/invitation/BluetoothWidget.java   |  71 +++++++++++++
 .../android/invitation/CodeEntryListener.java |   6 ++
 .../android/invitation/CodeEntryWidget.java   |  76 ++++++++++++++
 .../invitation/CodesDoNotMatchActivity.java   |  54 ++++++++++
 .../invitation/ConfirmationCodeActivity.java  |  69 ++++++++++++
 .../invitation/ConfirmationListener.java      |   8 ++
 .../invitation/ConnectionActivity.java        |  99 ++++++++++++++++++
 .../invitation/ConnectionFailedActivity.java  |  96 +++++++++++++++++
 .../invitation/ConnectionListener.java        |   8 ++
 .../invitation/ContactAddedActivity.java      |  91 ++++++++++++++++
 .../invitation/InvitationCodeActivity.java    |  48 +++++++++
 .../android/invitation/InvitationManager.java |  26 +++++
 .../invitation/InvitationManagerFactory.java  |  14 +++
 .../invitation/InvitationManagerImpl.java     |  67 ++++++++++++
 .../invitation/NetworkSetupActivity.java      |  88 ++++++++++++++++
 .../invitation/WaitForContactActivity.java    |  76 ++++++++++++++
 .../android/invitation/WifiStateListener.java |   6 ++
 .../briar/android/invitation/WifiWidget.java  |  77 ++++++++++++++
 38 files changed, 1209 insertions(+), 10 deletions(-)
 create mode 100644 res/drawable-hdpi/ic_launcher.png
 create mode 100644 res/drawable-ldpi/ic_launcher.png
 create mode 100644 res/drawable-ldpi/iconic_check_alt_green.png
 create mode 100644 res/drawable-ldpi/iconic_x_alt_red.png
 create mode 100644 res/drawable-mdpi/ic_launcher.png
 create mode 100644 res/drawable-xhdpi/ic_launcher.png
 create mode 100644 res/layout/activity_add_contact.xml
 create mode 100644 res/layout/activity_codes_do_not_match.xml
 create mode 100644 res/layout/activity_connection.xml
 create mode 100644 res/layout/activity_connection_failed.xml
 create mode 100644 res/layout/activity_connection_succeeded.xml
 create mode 100644 res/layout/activity_contact_added.xml
 create mode 100644 res/layout/activity_invitation_code.xml
 create mode 100644 res/layout/activity_network_setup.xml
 create mode 100644 res/layout/activity_test_bluetooth.xml
 create mode 100644 res/layout/activity_wait_for_contact.xml
 create mode 100644 src/net/sf/briar/android/invitation/BluetoothStateListener.java
 create mode 100644 src/net/sf/briar/android/invitation/BluetoothWidget.java
 create mode 100644 src/net/sf/briar/android/invitation/CodeEntryListener.java
 create mode 100644 src/net/sf/briar/android/invitation/CodeEntryWidget.java
 create mode 100644 src/net/sf/briar/android/invitation/CodesDoNotMatchActivity.java
 create mode 100644 src/net/sf/briar/android/invitation/ConfirmationCodeActivity.java
 create mode 100644 src/net/sf/briar/android/invitation/ConfirmationListener.java
 create mode 100644 src/net/sf/briar/android/invitation/ConnectionActivity.java
 create mode 100644 src/net/sf/briar/android/invitation/ConnectionFailedActivity.java
 create mode 100644 src/net/sf/briar/android/invitation/ConnectionListener.java
 create mode 100644 src/net/sf/briar/android/invitation/ContactAddedActivity.java
 create mode 100644 src/net/sf/briar/android/invitation/InvitationCodeActivity.java
 create mode 100644 src/net/sf/briar/android/invitation/InvitationManager.java
 create mode 100644 src/net/sf/briar/android/invitation/InvitationManagerFactory.java
 create mode 100644 src/net/sf/briar/android/invitation/InvitationManagerImpl.java
 create mode 100644 src/net/sf/briar/android/invitation/NetworkSetupActivity.java
 create mode 100644 src/net/sf/briar/android/invitation/WaitForContactActivity.java
 create mode 100644 src/net/sf/briar/android/invitation/WifiStateListener.java
 create mode 100644 src/net/sf/briar/android/invitation/WifiWidget.java

diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index e9dfd8090b..7992928960 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
GIT binary patch
literal 2364
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

literal 0
HcmV?d00001

diff --git a/res/drawable-ldpi/ic_launcher.png b/res/drawable-ldpi/ic_launcher.png
new file mode 100644
index 0000000000000000000000000000000000000000..e0119f08678c54c5efd851a1860c9a18978ec3cd
GIT binary patch
literal 1276
zcmV<Y1OxktP)<h;3K|Lk000e1NJLTq001Na001Ni1^@s6;Q*MJ000ETNkl<Zc-qxi
zZAe>J7*^M=9jz<1l=a7;47NenQA+X0V1u;<*PnKO>@q6oYAvi3wEk?Xvo#Ik4_jFx
zmPOVrR%;TL>8g%6N4ARAFG&4NP;`PK1wR+@BPM6>b9?UP-kTe})o9s)2g1#Jp6AJX
z&w1Z-5*PP>AMNe!Z%<84eKR{dYYzkhzNMulUpO2N0r-q(_-=A?G8?hm<s;tb^L@6k
zu<%DP7+fK7w$VTgVlfAE!4VT99UUFVXJ%$xYinz4vjcO%0j{kWLsq_`*RY@`BYH9h
zE^uzfRKm!}Na5=0>Kcpj04KPiq1%k#?=PbFA3@gl_%Zwa`gIAN7K^4Rp3CDfPd>m6
z4LzpfJ3BiK%gf7}nW@XlV&BEZN%-;fX-z{9&z@Dp{d(zAL<}^bwchKCudS{9XnJ}&
z5N*xG(hxMD<#0GoL^HLkzrVju>s4)sA@SAq_4U<5TPr&$^Yine^%i7=p)+SR<La+m
zVHKxNN$}jh9hDQB&{k(@cTZ2xpYrUq4>YhJp4^b|%im@}NN(t-loZz7{YVi9R5sj-
zfI0^JVwRu{50aaVjEp1W<Kv17T-9u17mXiExM@AFY3RQ5nj%gwZ0@_#T7ot_NZZ_Q
zw|`6hN0G{%GK2wbco56Ln;ws+fvV_hGX!mTfEO6rNA+Mxy}R69Rm^C+D8c&VrlujM
z=e{CNFTgL~ZQb47et3bQ1GM$#_4KQ{%a?WSGS8hm%waG{=&GpLu+Nc)f8hm&4pRLI
z>M|7LIwUs)59B2YhLXHqZ*aYWxZQ@J4G-ky5DXn^YikRr7xhjWf;OEl1EQg%#>U3c
zxw*Lw4Ph<VFI<oitpckJ;fJg@pxxBe^pE(;4%lqAzvv9C2tozEYfFD+IB3`XeD0jW
zP<?hbRqK33T(fMKK^q>#MZLeYwDjKK;2@J9Q`L1yFYY<6Y8tv_{Zg|IL33zmh`|HA
zz|fwww6srKT3W)?=yu!?G@+fIp8h$!z|b3ncdS;ccX)W1iMwql3_%N;m6esx;emS*
zMq*A*PL|v44x_K2E~vXbV5P3DhB^inSj&*>saX-%*8iK(!=MRmc#t1K(E8skFE8)v
z?d`?!GHL~Wken<5tz-*FSOzU<LR)eC;LZ{a2E+O4>gtuz(NWwEwyh3<8yYn=H7n3m
zXGc215{~5O=a;!$E;cqc7GWwDhQJAKXcQC_n4zh4gcY;&7U8|3qM~}I(<v-CR8g_d
zZN(73C<>h5h6c2_X4IQU0#Dxug@uK+RII|(V{i?Ac_63zRa0)RgbuSg>b*=J=dG#|
zIKTx?a6^M@ZFo0B0RFXjKQAxOWU*M5X(tH33^hYf6P7nk%)wl6fD4@9<{J8Mue@p=
zO-)Vx+GH|$sZODQv4MdBHZd_FY}v)dMPW^ZLLmY8jA!@`F^I(+%moLyz{$B2Uip4c
z*vl8)@yyK3Tq5<Cm6e4k?P!LqwY3#D2ApLC;4_}#JH#Lsb1)YioNMo9%*Yq|F8;1j
zD?X<21*QFaqtW=RxVU(#q@?5pfpNURGkk{_#9|Jgo5;Ch^FI+UTp&Hn(|(L_l8{XJ
mgd>?hJH}%U^Etcq8u<?*kyu0?-&{NZ0000<MNUMnLSTZ%K5K^n

literal 0
HcmV?d00001

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
GIT binary patch
literal 499
zcmV<P0Sx|$P)<h;3K|Lk000e1NJLTq001BW001Be1^@s6b9#F80000PbVXQnQ*UN;
zcVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUzl}SWFRCwCVm(gv*AP|Ou2{J-Qh~F#4
z*}&N#o*?gi>r3A=fwMu`z>d^+dxK^KZ_wUFXA2XI!9bLgPCSI%_j4R);LOp!e)260
z1^5m;pPANxU%}<ad(*kZ<qqH@3@7j{^D`811|gSK0s#C5LuFa5t6^435OQ-C0Pup~
z8y3fU5+N4_P|7Qo#!7ulxMAhrEUg{XOmJ<_+lXnmZAUiLgiwB{o&aaW7`Ip``<_Pz
z;W3;$f;lnA44e|c_mJk&BI3^E)Zy9$@Ls7eO!?6;HxtxeQ^GfvE?Nlc?J424MZiut
z6kkUk8-6cF;S!j<iwtLaAD9hH!A&B9;7bA~W)Kk!UlM>pY6_~0xWs@Dec;cxzzko|
zRq!DL&BC0AOvHjCThJYjtq~CjN4uf`={R{JA_5#uathXZ1j3p)(8VPL-a%bu!znm>
z{1KtQf$)*3-iYc1cY@jCgVpc#K2XwiDYf;)nl>W3qm@q(FnJ>gt=-DRA|BobulF!{
piKkh_$LXhO)m{QpKhOUK7yxF9%o==(s|)}D002ovPDHLkV1j^s*5CjD

literal 0
HcmV?d00001

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
GIT binary patch
literal 552
zcmV+@0@wYCP)<h;3K|Lk000e1NJLTq001BW001Be1^@s6b9#F80000PbVXQnQ*UN;
zcVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUz%1J~)RCwCFmw}OjFc3vU#saM1=1RZ{
zJUY-0paN^Lg3y6<fL5R-+*}28pqrb|onVsPKyYU=8HSMmvdJ!+l@i+hL*>-%sBNiz
zd4`>-U8ptLkGe_b%>=?Fbq8v>iZcsvB#?R%011CmX9BC0n)OOWAm6T++9PgVeN7;)
zFvk^h@&Iu8iP~DFGdtG_bNc~6Y1DFN#-7lQJzv@kv|c=561zRL9cBQ4z2OdAjnI;W
zU(FrN{yg*^IB?<=Miuo117Y747|TKYYHDP-Tp+Hf?*b6Lc!#kZL~gY;(yL+y{+$gE
z#6TR|x)x;5okngC0#IuM5I*r%0I<zy@I4~8yM)w4aepd~@x&bQSO6!)9Pudt$t@P(
z<s2~4krcpr2n8X^0-R?=K+A+ETdZi*a8t!|L7oWN#R4=OfFRU-5F9|GQN4x+mNc;u
zgxUZC-;~V4ZYw+-nPS93s3(_mdE&;1_nQAzF@4k~Dndn-GOfNChoSv_c5e{xpf$_`
zsN1VI01WO3dX&wYg&`_v_%k7{Qv1Z)VdvVru4&Fx49bl#OG>V0oAJuoe}Ua6oYX4z
q2lJFc?Wzmky-;*JUiy0e5MTiNRMD9C*ppEJ0000<MNUMnLSTZnr}8oY

literal 0
HcmV?d00001

diff --git a/res/drawable-mdpi/ic_launcher.png b/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000000000000000000000000000000000000..9873a76f01b7952e9ca72018763aa519a5b8f1e8
GIT binary patch
literal 1591
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

literal 0
HcmV?d00001

diff --git a/res/drawable-xhdpi/ic_launcher.png b/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 0000000000000000000000000000000000000000..0b22fd5d7a57db85fccf364f07052262aaf8ec22
GIT binary patch
literal 3192
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

literal 0
HcmV?d00001

diff --git a/res/layout/activity_add_contact.xml b/res/layout/activity_add_contact.xml
new file mode 100644
index 0000000000..c37b80fec0
--- /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 0000000000..4bd1965bc9
--- /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 0000000000..367b75fc55
--- /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 0000000000..9672914f4c
--- /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 0000000000..0b6962125d
--- /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 0000000000..05a3c684f1
--- /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 0000000000..193e86aaf5
--- /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 0000000000..48d2de513f
--- /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 0000000000..eaf4032a4c
--- /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 0000000000..cd96d411b2
--- /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 eeabdb397e..0a2f6313df 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 53607532d3..76868cb806 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 0000000000..fa7846c7c4
--- /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 0000000000..e97d7d86e9
--- /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 0000000000..1a8f7b3d8d
--- /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 0000000000..5565782753
--- /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 0000000000..c981bc0670
--- /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 0000000000..e4bb901df0
--- /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 0000000000..af2024676a
--- /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 0000000000..2564adf002
--- /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 0000000000..0b868a1740
--- /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 0000000000..a1bc643dc3
--- /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 0000000000..846c25bf1a
--- /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 0000000000..79e7a22cd9
--- /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 0000000000..f77baa4c33
--- /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 0000000000..a38ac435ad
--- /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 0000000000..c5afcacec1
--- /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 0000000000..2488e4d125
--- /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 0000000000..ed778ff23f
--- /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 0000000000..c668b0d2d3
--- /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 0000000000..d27d6e8b3a
--- /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));
+	}
+}
-- 
GitLab